#!/usr/bin/env python3 import numpy as np from typing import Tuple from Tools import hash2d def lerp(a0, a1, w): return a0 + w*(a1 - a0) def dotGridGradient(ix, iy, x, y, raw_val_x, raw_val_y): dx = x - ix dy = y - iy return dx * raw_val_x + dy * raw_val_y class PerlinGenerator(object): def __init__(self, raw_noise_cell_size: int = 8, raw_noise_chunk_size: int = 8, world_seed: int = 0): self.raw_noise_cell_size: int = raw_noise_cell_size self.raw_noise_chunk_size: int = raw_noise_chunk_size self.world_seed: int = world_seed self.raw_noise_chunks = {} def getCellCoords(self, perlin_x: int, perlin_y: int) -> Tuple[float, float]: return (perlin_x / self.raw_noise_cell_size, perlin_y / self.raw_noise_cell_size) def getCellChunk(self, raw_cell_x: int, raw_cell_y: int) -> Tuple[int, int]: return (raw_cell_x // self.raw_noise_chunk_size, raw_cell_y // self.raw_noise_chunk_size) def existRawChunk(self, chunk_x: int, chunk_y: int) -> Tuple[int, int]: return (chunk_x in self.raw_noise_chunks and chunk_y in self.raw_noise_chunks[chunk_x]) def createRawChunk(self, chunk_x: int, chunk_y: int) -> None: if chunk_x not in self.raw_noise_chunks: self.raw_noise_chunks[chunk_x] = {} c = self.raw_noise_chunks[chunk_x][chunk_y] = np.zeros( shape=(self.raw_noise_chunk_size, self.raw_noise_chunk_size, 2), dtype=float) # init random state chunk_seed = (hash2d(chunk_x, chunk_y) + self.world_seed) % 2**32 np.random.seed(chunk_seed) # creating chunk: for x in range(self.raw_noise_chunk_size): for y in range(self.raw_noise_chunk_size): # create random vector on unit circle re = np.random.normal() im = np.random.normal() # TODO: check whether mag == 0 is possible! mag = (re ** 2 + im ** 2) ** .5 c[x, y, 0] = re / mag c[x, y, 1] = im / mag def perlin(self, perlin_x: int, perlin_y: int) -> float: raw_coords_x, raw_coords_y = self.getCellCoords( perlin_x=perlin_x, perlin_y=perlin_y) ix0: int = int(raw_coords_x) iy0: int = int(raw_coords_y) ix1: int = ix0 + 1 iy1: int = iy0 + 1 sx: float = raw_coords_x - ix0 sy: float = raw_coords_y - iy0 cx0, cy0 = self.getCellChunk(raw_cell_x=ix0, raw_cell_y=iy0) cx1, cy1 = self.getCellChunk(raw_cell_x=ix1, raw_cell_y=iy1) if not self.existRawChunk(cx0, cy0): self.createRawChunk(cx0, cy0) if not self.existRawChunk(cx0, cy1): self.createRawChunk(cx0, cy1) if not self.existRawChunk(cx1, cy0): self.createRawChunk(cx1, cy0) if not self.existRawChunk(cx1, cy1): self.createRawChunk(cx1, cy1) local_x0: int = ix0 % self.raw_noise_chunk_size local_y0: int = iy0 % self.raw_noise_chunk_size local_x1: int = ix1 % self.raw_noise_chunk_size local_y1: int = iy1 % self.raw_noise_chunk_size chunk_x0y0 = self.raw_noise_chunks[cx0][cy0] chunk_x0y1 = self.raw_noise_chunks[cx0][cy1] chunk_x1y0 = self.raw_noise_chunks[cx1][cy0] chunk_x1y1 = self.raw_noise_chunks[cx1][cy1] n0_x: float = chunk_x0y0[local_x0, local_y0, 0] n0_y: float = chunk_x0y0[local_x0, local_y0, 1] n1_x: float = chunk_x1y0[local_x1, local_y0, 0] n1_y: float = chunk_x1y0[local_x1, local_y0, 1] n2_x: float = chunk_x0y1[local_x0, local_y1, 0] n2_y: float = chunk_x0y1[local_x0, local_y1, 1] n3_x: float = chunk_x1y1[local_x1, local_y1, 0] n3_y: float = chunk_x1y1[local_x1, local_y1, 1] n0: float = dotGridGradient( ix0, iy0, raw_coords_x, raw_coords_y, n0_x, n0_y) n1: float = dotGridGradient( ix1, iy0, raw_coords_x, raw_coords_y, n1_x, n1_y) n2: float = dotGridGradient( ix0, iy1, raw_coords_x, raw_coords_y, n2_x, n2_y) n3: float = dotGridGradient( ix1, iy1, raw_coords_x, raw_coords_y, n3_x, n3_y) x0: float = lerp(n0, n1, sx) x1: float = lerp(n2, n3, sx) # TODO: this interpolation method is bad! return lerp(x0, x1, sy)