Spaces:
Runtime error
Runtime error
File size: 5,799 Bytes
c19ca42 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
"""
Simplex noise implementation by Alex Dodge, 2023
References:
Simplex noise demystified, Stefan Gustavson (2005)
http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
"""
import itertools
from typing import Optional
import numpy as np
# fmt: off
PERMUTATION_TABLE_ARRAY = np.array([
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103,
30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197,
62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20,
125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231,
83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143,
54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250,
124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221,
153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224,
232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191,
179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106,
157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222,
114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
], dtype=np.int32)
# fmt: on
# Empirically determined scaling factor for different numbers of dimensions
SCALE = {
2: 50,
3: 39,
4: 32,
5: 28,
6: 26,
# It looks terrible at this many dimensions anyway
}
class SimplexNoise:
def __init__(self, dimensions: int, seed: Optional[int], r2: float = 0.5):
if dimensions <= 0:
raise ValueError
if dimensions == 1:
raise RuntimeError("1D Simplex noise is not implemented here.")
if dimensions > 6:
raise RuntimeError("7D+ Simplex noise is not implemented here.")
self.dimensions = dimensions
self.r2 = r2
self.F = (np.sqrt(self.dimensions + 1) - 1) / self.dimensions
self.G = (1 - 1 / np.sqrt(self.dimensions + 1)) / self.dimensions
"""
For 2D noise, we pick 16 gradients evenly distributed around the unit circle.
For 3D and above, we pick gradients pointing at the midpoints of the edges of a hypercube centered on the origin
"""
if self.dimensions == 2:
n_gradients = 16
self.gradients = np.array(
[
(
np.cos(2 * np.pi * i / n_gradients),
np.sin(2 * np.pi * i / n_gradients),
)
for i in range(n_gradients)
]
)
else:
n_gradients = self.dimensions * 2 ** (self.dimensions - 1)
self.gradients = np.zeros((n_gradients, self.dimensions))
for zero_dim in range(self.dimensions):
for i, vec in enumerate(
itertools.product([-1, 1], repeat=self.dimensions - 1)
):
idx = zero_dim * 2 ** (self.dimensions - 1) + i
self.gradients[idx, :zero_dim] = vec[:zero_dim]
self.gradients[idx, zero_dim + 1 :] = vec[zero_dim:]
if seed is None:
# Use the canonical table from the reference implementation
self.permutation_table = PERMUTATION_TABLE_ARRAY
else:
np.random.seed(seed)
self.permutation_table = np.arange(self.gradients.shape[0] * 16)
np.random.shuffle(self.permutation_table)
def evaluate(self, points: np.ndarray):
n_points = points.shape[0]
assert points.shape == (n_points, self.dimensions)
skewed_points = points + (points.sum(axis=1) * self.F).reshape((n_points, 1))
skewed_bases, skewed_points_remainder = np.divmod(skewed_points, 1)
skewed_simplex_verts = np.full(
(n_points, self.dimensions + 1, self.dimensions),
fill_value=skewed_bases.reshape((n_points, 1, -1)),
dtype="int32",
)
skewed_simplex_verts[:, self.dimensions, :] += 1
for i in range(1, self.dimensions):
largest_dimension = np.argmax(skewed_points_remainder, axis=1)
for o in range(self.dimensions):
skewed_simplex_verts[
(largest_dimension == o), i : self.dimensions, o
] += 1
if i != self.dimensions - 1:
skewed_points_remainder[(largest_dimension == o), o] = -1
gradient_index = np.zeros(skewed_simplex_verts.shape[:2], dtype="int32")
for i in range(skewed_simplex_verts.shape[2]):
gradient_index = (
gradient_index + skewed_simplex_verts[:, :, i]
) % self.permutation_table.size
gradient_index = self.permutation_table[gradient_index]
gradients = self.gradients[gradient_index % self.gradients.shape[0]]
simplex_verts = (
skewed_simplex_verts
- skewed_simplex_verts.sum(axis=2).reshape((n_points, -1, 1)) * self.G
)
displacement = np.power(
points.reshape((n_points, 1, -1)) - simplex_verts, 2
).sum(axis=2)
dot_gradient = np.sum(
(points.reshape((n_points, 1, -1)) - simplex_verts) * gradients, axis=2
)
contributions = (
np.power(np.maximum(0, self.r2 - displacement), 4) * dot_gradient
)
return np.sum(contributions, axis=1) * SCALE[self.dimensions] + 0.5
|