Compare commits
7 Commits
master
...
3d_density
Author | SHA1 | Date |
---|---|---|
Jonas Weinz | aa5cdb9b88 | |
Jonas Weinz | 720573821e | |
Jonas Weinz | 547d24b0a1 | |
Jonas Weinz | 7e788c3cd0 | |
Jonas Weinz | 7a3f2d6cc5 | |
Jonas Weinz | ee1fb43119 | |
Jonas Weinz | ce31155bab |
File diff suppressed because one or more lines are too long
|
@ -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 |
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue