""" Perlin Noise 1 If Python and Arcade are installed, this example can be run from the command line with: python -m arcade.examples.perlin_noise_1 TODO: This code doesn't work properly, and isn't currently listed in the examples. """ import arcade import numpy as np from PIL import Image # Set how many rows and columns we will have ROW_COUNT = 30 COLUMN_COUNT = 30 # This sets the WIDTH and HEIGHT of each grid location WIDTH = 10 HEIGHT = 10 # This sets the margin between each cell # and on the edges of the screen. MARGIN = 2 # Do the math to figure out our screen dimensions SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN SCREEN_TITLE = "Perlin Noise 1 Example" # Perlin noise generator from: # https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy def perlin(x, y, seed=0): # permutation table np.random.seed(seed) p = np.arange(256, dtype=int) np.random.shuffle(p) p = np.stack([p, p]).flatten() # coordinates of the top-left xi = x.astype(int) yi = y.astype(int) # internal coordinates xf = x - xi yf = y - yi # fade factors u = fade(xf) v = fade(yf) # noise components n00 = gradient(p[p[xi] + yi], xf, yf) n01 = gradient(p[p[xi] + yi + 1], xf, yf - 1) n11 = gradient(p[p[xi + 1] + yi + 1], xf - 1, yf - 1) n10 = gradient(p[p[xi + 1] + yi], xf - 1, yf) # combine noises x1 = lerp(n00, n10, u) x2 = lerp(n01, n11, u) # FIX1: I was using n10 instead of n01 return lerp(x1, x2, v) # FIX2: I also had to reverse x1 and x2 here def lerp(a, b, x): """linear interpolation""" return a + x * (b - a) def fade(t): """6t^5 - 15t^4 + 10t^3""" return 6 * t ** 5 - 15 * t ** 4 + 10 * t ** 3 def gradient(h, x, y): """grad converts h to the right gradient vector and return the dot product with (x,y)""" vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]]) g = vectors[h % 4] return g[:, :, 0] * x + g[:, :, 1] * y class MyGame(arcade.Window): """ Main application class. """ def __init__(self, width, height, title): """ Set up the application. """ super().__init__(width, height, title) self.shape_list = None arcade.set_background_color(arcade.color.BLACK) self.grid = None self.recreate_grid() def recreate_grid(self): lin = np.linspace(0, 5, ROW_COUNT, endpoint=False) y, x = np.meshgrid(lin, lin) self.grid = (perlin(x, y, seed=0)) self.grid *= 255 self.grid += 128 # for row in range(ROW_COUNT): # for column in range(COLUMN_COUNT): # print(f"{self.grid[row][column]:5.2f} ", end="") # print() self.shape_list = arcade.ShapeElementList() for row in range(ROW_COUNT): for column in range(COLUMN_COUNT): color = self.grid[row][column], 0, 0 x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2 y = (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2 current_rect = arcade.create_rectangle_filled(x, y, WIDTH, HEIGHT, color) self.shape_list.append(current_rect) im = Image.fromarray(np.uint8(self.grid), "L") im.save("test.png") def on_draw(self): """ Render the screen. """ # This command has to happen before we start drawing arcade.start_render() self.shape_list.draw() def on_mouse_press(self, x, y, button, modifiers): """ Called when the user presses a mouse button. """ # Change the x/y screen coordinates to grid coordinates column = x // (WIDTH + MARGIN) row = y // (HEIGHT + MARGIN) print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})") # Make sure we are on-grid. It is possible to click in the upper right # corner in the margin and go to a grid location that doesn't exist if row < ROW_COUNT and column < COLUMN_COUNT: # Flip the location between 1 and 0. if self.grid[row][column] == 0: self.grid[row][column] = 1 else: self.grid[row][column] = 0 self.recreate_grid() def main(): MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) arcade.run() if __name__ == "__main__": main()