TetrArcade/arcade/examples/perlin_noise_1.py

163 lines
4.4 KiB
Python

"""
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()