Compare commits

...

7 Commits

Author SHA1 Message Date
Jonas Weinz aa5cdb9b88 improved speed of simplex noise generation a bit 2019-01-14 21:47:26 +01:00
Jonas Weinz 720573821e fixed bugs in world generation 2019-01-10 20:53:14 +01:00
Jonas Weinz 547d24b0a1 changed test script 2019-01-10 20:52:16 +01:00
Jonas Weinz 7e788c3cd0 new simplex noise 2019-01-09 21:39:14 +01:00
Jonas Weinz 7a3f2d6cc5 3d test 2019-01-08 17:08:12 +01:00
Jonas Weinz ee1fb43119 first simplex tests 2019-01-08 12:33:36 +01:00
Jonas Weinz ce31155bab updated Perlin World notebook 2019-01-07 11:15:38 +01:00
11 changed files with 1195 additions and 74 deletions

File diff suppressed because one or more lines are too long

View File

@ -58,10 +58,10 @@
"metadata": {},
"outputs": [],
"source": [
"n_chunks = 3\n",
"n_chunks = 4\n",
"raw_noise_cell_size = 16\n",
"raw_noise_chunk_size = 4\n",
"world_seed = 42"
"world_seed = 42\n"
]
},
{
@ -99,7 +99,7 @@
"\n",
"for i in range(n_chunks):\n",
" for j in range(n_chunks):\n",
" height_map[i * c:(i+1)* c, j*c:(j+1)*c] = wm.getPerlinMap(i,j)"
" height_map[i * c:(i+1)* c, j*c:(j+1)*c] = wm.getPerlinMap(i-n_chunks//2,j-n_chunks//2)"
]
},
{
@ -117,7 +117,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "23dd04daba304f2d9668d861184e7153",
"model_id": "58ec9ede58de42c28c7d11dcbb6b6859",
"version_major": 2,
"version_minor": 0
},
@ -153,8 +153,8 @@
"for x in range(n_chunks):\n",
" for y in range(2):\n",
" for z in range(n_chunks):\n",
" wm.createEmptyChunk(x,y,z)\n",
" wm.applyPerlinToChunk(x,y,z)\n",
" wm.createEmptyChunk(x-n_chunks // 2,y,z-n_chunks // 2)\n",
" wm.applyPerlinToChunk(x - n_chunks // 2,y,z - n_chunks//2)\n",
" "
]
},
@ -176,7 +176,7 @@
"for x in range(n_chunks):\n",
" for y in range(2):\n",
" for z in range(n_chunks):\n",
" world[x*c:(x+1)*c, y*c:(y+1)*c, z*c:(z+1)*c] = wm.chunks[x][y][z].block_data"
" world[x*c:(x+1)*c, y*c:(y+1)*c, z*c:(z+1)*c] = wm.chunks[x - n_chunks//2][y][z-n_chunks//2].block_data"
]
},
{
@ -201,10 +201,10 @@
"metadata": {},
"outputs": [],
"source": [
"colors = np.zeros(shape=(n_chunks * c, 2*c, n_chunks*c,3))\n",
"colors = np.zeros(shape=(n_chunks * c, 2*c, n_chunks*c,4))\n",
"for x,y,z in np.ndindex(world.shape):\n",
" i = y/(c)\n",
" colors[x,y,z,:] = [0,i,1-i]"
" colors[x,y,z,:] = [0,i,1-i,1]"
]
},
{
@ -215,7 +215,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3010f3d5167449b196314981a687108b",
"model_id": "7e6e00c00e5c4a1aa8afdbc0acf7cc76",
"version_major": 2,
"version_minor": 0
},
@ -232,7 +232,7 @@
"fig2 = plt.figure()\n",
"ax2 = fig2.gca(projection='3d')\n",
"\n",
"p = ax2.voxels(np.rollaxis(world,2), facecolors=np.rollaxis(colors,2))"
"p = ax2.voxels(np.rollaxis(world,2), edgecolors='k' )# , facecolors=np.rollaxis(colors,2))"
]
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -47,7 +47,7 @@ class ConnectionWorker(threading.Thread):
# just for testing purposes!!
if not self.world.isChunkKnown(chunk_x, chunk_y, chunk_z):
self.world.createEmptyChunk(chunk_x, chunk_y, chunk_z)
self.world.applyPerlinToChunk(chunk_x, chunk_y, chunk_z)
self.world.applySimplexToChunk(chunk_x, chunk_y, chunk_z)
json = self.world.getChunkAsJson(chunk_x, chunk_y, chunk_z)
self.clientsocket.sendall(json.encode())

View File

@ -2,7 +2,7 @@
import numpy as np
from typing import Tuple
from Tools import hash2d
from Tools import hash_2d
def lerp(a0, a1, w):
@ -54,7 +54,8 @@ class PerlinGenerator(object):
dtype=float)
# init random state
chunk_seed = (hash2d(chunk_x, chunk_y) + self.world_seed) % 2**32
chunk_seed = np.uint64(
(hash_2d(chunk_x, chunk_y) + self.world_seed) % 2**32)
np.random.seed(chunk_seed)
# creating chunk:

View File

@ -36,17 +36,14 @@ class Server(threading.Thread):
def main():
raw_noise_cell_size = 32
raw_noise_chunk_size = 4
world_seed = 42
world_scale = 4
# create world:
wm = WorldManager(raw_noise_cell_size=raw_noise_cell_size,
raw_noise_chunk_size=raw_noise_chunk_size,
world_seed=world_seed)
wm = WorldManager(world_seed=world_seed, world_scale=world_scale)
wm.createEmptyChunk(0, 0, 0)
wm.applyPerlinToChunk(0, 0, 0)
wm.applySimplexToChunk(0, 0, 0)
server = Server(wm, settings.SERVER_HOST, settings.SERVER_PORT)
server.start()

View File

@ -0,0 +1,152 @@
#!/usr/bin/env python3
import numpy as np
from numpy.core.umath_tests import inner1d
from typing import Tuple
from Tools import hash_2d, hash_3d, hash_nd
def F(d):
return (np.sqrt(d+1) - 1) / d
def G(d):
return (1 - 1/np.sqrt(d+1))/d
def scew_coords(coords):
d = coords.shape[1]
n = coords.shape[0]
f = F(d)
coords_sum = np.sum(coords, axis=1).reshape((n, 1))
return coords + coords_sum * f
def un_scew_coords(coords):
d = coords.shape[1]
n = coords.shape[0]
g = G(d)
coords_sum = np.sum(coords, axis=1).reshape((n, 1))
return coords - coords_sum * g
class SimplexGenerator(object):
def __init__(self,
world_seed: int = 0,
scewed_chunk_size: int = 8):
self.world_seed = world_seed
self.scewed_chunk_size = scewed_chunk_size
self.cached_hashed_chunks = {}
def _is_cached(self, scewed_coord):
return self.cached_hashed_chunks.__contains__(scewed_coord)
def _create_hashed_vector(self, scewed_coord):
np.random.seed(hash_nd(np.array(scewed_coord +
self.world_seed, dtype=int)) %
np.uint64(2**32))
v = np.array([np.random.normal() for i in range(len(scewed_coord))])
mag = np.linalg.norm(v)
return v / mag
def _get_hashed_vector(self, scewed_coord):
key_tuple = tuple(scewed_coord)
try:
return self.cached_hashed_chunks[key_tuple]
except KeyError:
val = self._create_hashed_vector( # noqa
scewed_coord)
self.cached_hashed_chunks[key_tuple] = val
return val
def _get_scewed_corners(self, coords):
# note: a lot of numpy magic is going on here.
# probably this would be a lot of more code
# in every other programming language :)
d = coords.shape[1]
n = coords.shape[0]
corners = np.zeros(shape=(n, d+1, d), dtype=float)
scewed_coords = scew_coords(coords)
# get integer part (base corner)
truncated = np.floor(scewed_coords)
# get remaining part
local_coords = scewed_coords - truncated
# sort remaining part
local_order = np.argsort(local_coords, axis=-1)
base_vectors = np.identity(d)
# get base vectors sorted for every coordinate by local orders:
sorted_base_vectors = base_vectors[local_order]
corners[:, 0, :] = truncated
for i in range(1, d+1):
# -i becease we want decreasing order!
corners[:, i, :] = corners[:, i-1, :] + \
sorted_base_vectors[:, :, -i]
# we're done
return corners
def get_dense_values(self, points, r_sqrd=0.5):
d = points.shape[1]
n = points.shape[0]
scewed_corners = self._get_scewed_corners(points)
# get min and max scewd values:
min_scewed = np.min(scewed_corners, axis=0)
max_scewed = np.max(scewed_corners, axis=0)
min_scewed = np.min(np.array(min_scewed, dtype=int), axis=0)
max_scewed = np.max(np.array(max_scewed, dtype=int), axis=0)
grid_shape = tuple(np.array(max_scewed - min_scewed,
dtype=int) + np.ones(d, dtype=int))
local_grid = np.zeros(tuple(list(grid_shape)+[d]))
# generate grid in [min, max] range:
for index in np.ndindex(grid_shape):
local_grid[index] = self._get_hashed_vector(min_scewed + index)
result = np.zeros(shape=(n, d))
for i in range(d+1):
# unscew corners
uc = un_scew_coords(scewed_corners[:, i, :])
dists = (uc - points)
sqrd_dists = np.sum(dists**2, axis=-1)
corner_indices = np.zeros(shape=(d, n), dtype=int)
for j in range(d):
# TODO: maybe rollaxis is better
corner_indices[j, :] = scewed_corners[:,
i, j] - min_scewed[j]
assert not np.min(corner_indices) < 0
# getting gradients:
g = local_grid[tuple(corner_indices)]
# implement the formula given above:
s = (np.maximum(0, r_sqrd - sqrd_dists))**4 * inner1d(dists, g)
result += (s * g.T).T
return result

View File

@ -1,11 +1,35 @@
#!/usr/bin/env python3
from numpy import uint64
# taken from:
# https://stackoverflow.com/questions/47678568/deterministic-pseudorandom-number-generation/47681859#47681859
def hash2d(i, j):
def hash_2d(i, j):
x = i + (j << 32)
x = (x ^ (x >> 30)) * (0xbf58476d1ce4e5b9)
x = (x ^ (x >> 27)) * (0x94d049bb133111eb)
x = x ^ (x >> 31)
return x
return uint64(x % (1 << 64))
def hash_3d(i, j, k):
x = i + (j << 32) + (k << 64) # hint: maybe produces a lot of costs
x = (x ^ (x >> 30)) * (0xbf58476d1ce4e5b9)
x = (x ^ (x >> 27)) * (0x94d049bb133111eb)
x = x ^ (x >> 31)
return uint64(x % (1 << 64))
def hash_nd(v):
d = len(v)
if d == 2:
return hash_2d(int(v[0]), int(v[1]))
if d == 3:
return hash_3d(int(v[0]), int(v[1]), int(v[2]))
x = v[0] + sum([v[i] << (16 << i) for i in range(1, d)])
x = (x ^ (x >> 30)) * (0xbf58476d1ce4e5b9)
x = (x ^ (x >> 27)) * (0x94d049bb133111eb)
x = x ^ (x >> 31)
return uint64(x % (1 << 64))

View File

@ -3,7 +3,7 @@
import numpy as np
import Chunk
import random
from PerlinGenerator import PerlinGenerator
from SimplexGenerator import SimplexGenerator
from threading import Lock
from typing import Tuple
@ -11,16 +11,12 @@ from typing import Tuple
class WorldManager(object):
def __init__(self, world_seed: int,
raw_noise_cell_size: int,
raw_noise_chunk_size: int):
def __init__(self, world_seed: int, world_scale: int, biome_scale: int = 32):
self.world_seed = world_seed
# create Perlin Generator
self.pg = PerlinGenerator(raw_noise_cell_size=raw_noise_cell_size,
raw_noise_chunk_size=raw_noise_chunk_size,
world_seed=world_seed)
self.simplex_generator_2d = SimplexGenerator()
self.simplex_generator_3d = SimplexGenerator()
# dictionary of dictionaries of dictionaries --> 3D hasmap
# (assume that's more efficient than using tuples as keys,
@ -30,8 +26,8 @@ class WorldManager(object):
# will be a list with locked chunks
self.chunk_locks = {}
# 2Dimensional map. Will be 3D in future [TODO]
self.perlin_maps = {}
self.world_scale = world_scale
self.biome_scale = biome_scale
# create initial chunk
# self.createEmptyChunk(0, 0, 0)
@ -82,52 +78,71 @@ class WorldManager(object):
self.world_lock.release()
def applyPerlinToChunk(self,
chunk_x: int,
chunk_y: int,
chunk_z: int) -> None:
def _getDensesForChunk(self, chunk_coord, simplex_generator, world_scale):
n = len(chunk_coord)
chunk_shape = tuple([Chunk.Chunk.CHUNK_SIDELENGTH] * n)
pm = np.copy(self.getPerlinMap(chunk_x, chunk_z))
pm += 1
pm *= 16 # TODO, FIXME: not adjustable
# NOTE: trick for fast point generation found on:
# https://github.com/numpy/numpy/issues/1234
points = np.indices(chunk_shape).reshape(len(chunk_shape), -1).T
points = np.array(points, dtype=float) / \
(Chunk.Chunk.CHUNK_SIDELENGTH * world_scale)
# adding chunk offset to points:
points += np.array(chunk_shape, dtype=float) * chunk_coord / \
(Chunk.Chunk.CHUNK_SIDELENGTH * world_scale)
denses = simplex_generator.get_dense_values(points)
magnitudes = np.linalg.norm(denses, axis=-1)
denses = denses.reshape(
tuple(list(chunk_shape) + [len(chunk_shape)]))
magnitudes = magnitudes.reshape(chunk_shape)
return denses, magnitudes
def applySimplexToChunk(self,
chunk_x: int,
chunk_y: int,
chunk_z: int) -> None:
# biome map generation:
biome_denses, biome_magnitudes = self._getDensesForChunk(np.array(
[chunk_x, chunk_z], dtype=int), self.simplex_generator_2d, self.biome_scale)
biome_magnitudes *= 200 * Chunk.Chunk.CHUNK_SIDELENGTH
# 2D heightmap generation
denses, magnitudes = self._getDensesForChunk(np.array([chunk_x, chunk_z],
dtype=int),
self.simplex_generator_2d,
self.world_scale)
magnitudes *= 50 * Chunk.Chunk.CHUNK_SIDELENGTH
self.acquireChunk(chunk_x, chunk_y, chunk_z)
c = self.chunks[chunk_x][chunk_y][chunk_z]
for x in range(Chunk.Chunk.CHUNK_SIDELENGTH):
for y in range(Chunk.Chunk.CHUNK_SIDELENGTH):
for z in range(Chunk.Chunk.CHUNK_SIDELENGTH):
c.block_data[x, y, z] = 1 if pm[x, z] > y + \
chunk_y*Chunk.Chunk.CHUNK_SIDELENGTH else 0
for y in range(Chunk.Chunk.CHUNK_SIDELENGTH):
c.block_data[:, y, :] = (magnitudes[:, :] + biome_magnitudes[:, :] > y +
chunk_y*Chunk.Chunk.CHUNK_SIDELENGTH)
self.releaseChunk(chunk_x, chunk_y, chunk_z)
def hasPerlinMap(self, chunk_x: int, chunk_z: int) -> bool:
return (chunk_x in self.perlin_maps and
chunk_z in self.perlin_maps[chunk_x])
# 3D complex generation (e.g. for caves)
def __createPerlinMap(self, chunk_x: int, chunk_z: int) -> None:
pm = self.perlin_maps[chunk_x][chunk_z] = np.zeros(
shape=(Chunk.Chunk.CHUNK_SIDELENGTH, Chunk.Chunk.CHUNK_SIDELENGTH),
dtype=float)
x0 = chunk_x * Chunk.Chunk.CHUNK_SIDELENGTH
z0 = chunk_z * Chunk.Chunk.CHUNK_SIDELENGTH
for i, j in np.ndindex(pm.shape):
pm[i, j] = self.pg.perlin(i + x0, j + z0)
def getPerlinMap(self, x: int, z: int):
if x not in self.perlin_maps:
self.perlin_maps[x] = {}
if z in self.perlin_maps[x]:
return self.perlin_maps[x][z]
self.__createPerlinMap(x, z)
return self.perlin_maps[x][z]
'''
denses, magnitudes = self._getDensesForChunk(np.array([chunk_x, chunk_y, chunk_z],
dtype=int),
self.simplex_generator_3d,
self.world_scale)
magnitudes *= 47 # 47 ~ 1 / max value
c.block_data = np.array(np.multiply(magnitudes < 0.3, c.block_data), dtype=int)
'''
def getChunkAsJson(self, chunk_x: int, chunk_y: int, chunk_z: int) -> str:
assert self.isChunkKnown(chunk_x, chunk_y, chunk_z)

View File

@ -9,20 +9,17 @@ from pycallgraph.output import GraphvizOutput
def main(nchunks=10):
raw_noise_cell_size = 32
raw_noise_chunk_size = 4
world_seed = 42
world_scale = 8
# create world:
wm = WorldManager(raw_noise_cell_size=raw_noise_cell_size,
raw_noise_chunk_size=raw_noise_chunk_size,
world_seed=world_seed)
wm = WorldManager(world_seed=world_seed, world_scale=world_scale)
for x in range(nchunks):
for y in range(nchunks):
for z in range(nchunks):
wm.createEmptyChunk(x, y, z)
wm.applyPerlinToChunk(x, y, z)
wm.applySimplexToChunk(x, y, z)
# print(x,y,z)