dkl9 commited on 2025-175 20:54:47
Showing 7 changed files, with 77 additions and 52 deletions.
| ... | ... |
@@ -3,18 +3,18 @@ from libs import * |
| 3 | 3 |
class Entity: |
| 4 | 4 |
def __init__(self, verts, col, pos, **kwargs): |
| 5 | 5 |
self.verts = vec(verts) |
| 6 |
+ self.hud = kwargs.get("hud", False)
|
|
| 6 | 7 |
self.col = vec(col) |
| 7 |
- self.refls = vec(kwargs.get("refls", [0.5, 0.5, 0.0, 32.0]))
|
|
| 8 |
+ self.refls = vec(kwargs.get("refls", [1.0, 0.0, 0.0, 1.0] if self.hud else [0.5, 0.5, 0.0, 32.0]))
|
|
| 8 | 9 |
self.pos = vec(pos) |
| 9 | 10 |
self.draw_mode = kwargs.get("mode", GL_TRIANGLE_STRIP)
|
| 10 |
- self.hud = kwargs.get("hud", False)
|
|
| 11 | 11 |
self.orient = np.identity(4, dtype=FLOAT) |
| 12 | 12 |
self.onclick = lambda s, b, n: print(f"clicked {s}:{n} with button {b}")
|
| 13 | 13 |
self.vao = None |
| 14 | 14 |
self.vbos = [[None, 0] for _ in VERTEX_ATTRS] |
| 15 | 15 |
|
| 16 |
- def col_list(self): |
|
| 17 |
- return np.repeat(vec([self.col]), len(self.verts), 0) |
|
| 16 |
+ def mat_list(self): |
|
| 17 |
+ return np.repeat(vec([[self.col], [self.refls]]), len(self.verts), 1) |
|
| 18 | 18 |
|
| 19 | 19 |
def norm_list(self): |
| 20 | 20 |
l = [] |
| ... | ... |
@@ -34,9 +34,6 @@ class Entity: |
| 34 | 34 |
raise NotImplementedError |
| 35 | 35 |
return (self.orient[:3, :3] @ vec(l).T).T |
| 36 | 36 |
|
| 37 |
- def refls_list(self): |
|
| 38 |
- return np.repeat(vec([self.refls]), len(self.verts), 0) |
|
| 39 |
- |
|
| 40 | 37 |
def build_vao(self): |
| 41 | 38 |
self.vao = glGenVertexArrays(1) |
| 42 | 39 |
glBindVertexArray(self.vao) |
| ... | ... |
@@ -60,11 +60,12 @@ def update_attr(ent, n, l=None): |
| 60 | 60 |
case "position": |
| 61 | 61 |
l = ent.verts |
| 62 | 62 |
case "col": |
| 63 |
- l = ent.col_list() |
|
| 63 |
+ l = ent.mat_list()[0] |
|
| 64 | 64 |
case "normal": |
| 65 | 65 |
l = ent.norm_list() |
| 66 |
+ assert all([abs(np.linalg.norm(v) - 1) < 1e-3 for v in l]) |
|
| 66 | 67 |
case "refls": |
| 67 |
- l = ent.refls_list() |
|
| 68 |
+ l = ent.mat_list()[1] |
|
| 68 | 69 |
if ent.vao: |
| 69 | 70 |
glBindBuffer(GL_ARRAY_BUFFER, ent.vbos[i][0]) |
| 70 | 71 |
if l.shape[0] != ent.vbos[i][1]: |
| ... | ... |
@@ -33,10 +33,10 @@ void main() {{
|
| 33 | 33 |
|
| 34 | 34 |
FPS = 60 |
| 35 | 35 |
|
| 36 |
-def debug_mode(scene, cam): |
|
| 36 |
+def debug_mode(mg, cam): |
|
| 37 | 37 |
pass |
| 38 | 38 |
|
| 39 |
-def click_mesh(self, button, coords): |
|
| 39 |
+def click_mesh(self, button, coords, mg): |
|
| 40 | 40 |
match button: |
| 41 | 41 |
case 1: |
| 42 | 42 |
self.set_voxel(coords[1]) |
| ... | ... |
@@ -44,22 +44,24 @@ def click_mesh(self, button, coords): |
| 44 | 44 |
print(f"coords {coords}")
|
| 45 | 45 |
case 3: |
| 46 | 46 |
offset = axis2offset(coords[0]) |
| 47 |
- self.set_voxel(coords[1] + offset, 2) |
|
| 47 |
+ p = self.set_voxel(coords[1] + offset, 1) |
|
| 48 |
+ if p is not None: |
|
| 49 |
+ rc = self.pos + p // self.r * self.r |
|
| 50 |
+ awr = mg.fetch(tuple(int(x) for x in rc), click_mesh) |
|
| 51 |
+ print(f"placed out-of-bounds at {p}")
|
|
| 48 | 52 |
if button % 2 == 1: |
| 49 | 53 |
for i in range(len(VERTEX_ATTRS)): |
| 50 | 54 |
update_attr(self, i) |
| 51 | 55 |
|
| 52 |
-def build_scene(mg): |
|
| 53 |
- box = WorldRegion() |
|
| 54 |
- print(f"map generated with {mg.world_natural(box)} voxels")
|
|
| 55 |
- box.remesh() |
|
| 56 |
- box.onclick = click_mesh |
|
| 57 |
- return [box] |
|
| 56 |
+def build_scene(): |
|
| 57 |
+ mg = MapGen() |
|
| 58 |
+ mg.fetch((0, 0, 0), click_mesh) |
|
| 59 |
+ return mg |
|
| 58 | 60 |
|
| 59 | 61 |
def main(): |
| 60 | 62 |
pygame.init() |
| 61 | 63 |
pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE) |
| 62 |
- pygame.display.set_caption("OpenGL")
|
|
| 64 |
+ pygame.display.set_caption("Voxels")
|
|
| 63 | 65 |
sp = shaders.compileProgram( |
| 64 | 66 |
shaders.compileShader(VS, GL_VERTEX_SHADER), |
| 65 | 67 |
shaders.compileShader(FS, GL_FRAGMENT_SHADER) |
| ... | ... |
@@ -69,27 +71,27 @@ def main(): |
| 69 | 71 |
glUseProgram(sp) |
| 70 | 72 |
glEnable(GL_DEPTH_TEST) |
| 71 | 73 |
glClearColor(0.1, 0.1, 0.1, 1.0) |
| 72 |
- glUniform4fv(UNIFORMS["lightPos"][2], 1, vec([5, 10, 10, 10])) |
|
| 73 |
- mg = MapGen() |
|
| 74 |
- scene = build_scene(mg) |
|
| 74 |
+ glUniform4fv(UNIFORMS["lightPos"][2], 1, vec([5, 30, 10, 100])) |
|
| 75 |
+ mg = build_scene() |
|
| 75 | 76 |
clock = pygame.time.Clock() |
| 76 | 77 |
cam = Player(speed=10/FPS) |
| 77 | 78 |
cam.pos = [8, 16, 8] |
| 78 | 79 |
cam.set_focus(True) |
| 79 | 80 |
while True: |
| 81 |
+ scene = list(mg.regions.values()) |
|
| 80 | 82 |
for ev in pygame.event.get(): |
| 81 | 83 |
if ev.type == pygame.QUIT or ev.type == pygame.KEYUP and ev.unicode == "q": |
| 82 | 84 |
pygame.quit() |
| 83 | 85 |
return |
| 84 | 86 |
elif ev.type == pygame.KEYUP and ev.unicode == "d": |
| 85 |
- debug_mode(scene, cam) |
|
| 87 |
+ debug_mode(mg, cam) |
|
| 86 | 88 |
cam.handle_event(ev, scene) |
| 87 | 89 |
cam.update_pos() |
| 88 | 90 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) |
| 89 | 91 |
for ent in scene: |
| 90 | 92 |
cam.draw(ent) |
| 91 | 93 |
perf = clock.get_fps() / FPS |
| 92 |
- cam.draw_hud(scene[0], perf) |
|
| 94 |
+ cam.draw_hud(scene, perf) |
|
| 93 | 95 |
pygame.display.flip() |
| 94 | 96 |
clock.tick(FPS) |
| 95 | 97 |
|
| ... | ... |
@@ -1,4 +1,5 @@ |
| 1 | 1 |
from libs import * |
| 2 |
+import region |
|
| 2 | 3 |
|
| 3 | 4 |
def smerp(ab, w): |
| 4 | 5 |
assert all([abs(x) < 2 for x in ab]) |
| ... | ... |
@@ -9,6 +10,20 @@ def smerp(ab, w): |
| 9 | 10 |
class MapGen: |
| 10 | 11 |
def __init__(self): |
| 11 | 12 |
self.grads = [{} for _ in range(4)]
|
| 13 |
+ self.regions = {}
|
|
| 14 |
+ |
|
| 15 |
+ def fetch(self, pos, onclick=None): |
|
| 16 |
+ if pos in self.regions: |
|
| 17 |
+ return self.regions[pos] |
|
| 18 |
+ else: |
|
| 19 |
+ wr = region.WorldRegion() |
|
| 20 |
+ wr.pos = vec(pos) |
|
| 21 |
+ self.world_natural(wr) |
|
| 22 |
+ wr.remesh() |
|
| 23 |
+ if onclick: |
|
| 24 |
+ wr.onclick = lambda s, b, c: onclick(s, b, c, self) |
|
| 25 |
+ self.regions[pos] = wr |
|
| 26 |
+ return wr |
|
| 12 | 27 |
|
| 13 | 28 |
def perlin(self, name, pos, period): |
| 14 | 29 |
dim = pos.shape[0] |
| ... | ... |
@@ -29,15 +44,16 @@ class MapGen: |
| 29 | 44 |
n = 0 |
| 30 | 45 |
for i in range(wr.r): |
| 31 | 46 |
for j in range(wr.r): |
| 32 |
- h = 8 + 4 * self.perlin("height", np.add(wr.pos[[0, 2]], (i, j)), np.array([10, 5]))
|
|
| 33 |
- for k in range(int(h)): |
|
| 47 |
+ h = 4 + 4 * self.perlin("height", np.add(wr.pos[[0, 2]], (i, j)), np.array([10, 5])) - wr.pos[1]
|
|
| 48 |
+ for k in range(min(int(h), wr.r)): |
|
| 34 | 49 |
wr.grid[i, k, j] = 3 |
| 35 | 50 |
n += 1 |
| 51 |
+ if h > 0 and h < wr.r: |
|
| 36 | 52 |
wr.grid[i, int(h), j] = 4 |
| 37 | 53 |
return n |
| 38 | 54 |
|
| 39 | 55 |
def world_min(self, wr): |
| 40 |
- wr.add_voxel((0, 0, 0)) |
|
| 56 |
+ wr.set_voxel((0, 0, 0), 1) |
|
| 41 | 57 |
return 1 |
| 42 | 58 |
|
| 43 | 59 |
def world_grid(self, wr): |
| ... | ... |
@@ -29,35 +29,42 @@ class SquareMesh(entity.Entity): |
| 29 | 29 |
self.verts = np.delete(self.verts, np.s_[3 * ind:3 * (ind + 2)], 0) |
| 30 | 30 |
del self.faces[ind:ind + 2] |
| 31 | 31 |
|
| 32 |
- def cube_col(self, fd, axis, corner): |
|
| 32 |
+ def cube_mat(self, fd, axis, corner): |
|
| 33 | 33 |
if not isinstance(fd, int): |
| 34 | 34 |
fd = self.grid[*fd] |
| 35 | 35 |
match fd: |
| 36 | 36 |
case 0: |
| 37 |
- return vec([0.0, 0.0, 0.0, 0.0]) |
|
| 37 |
+ return (vec([0.0, 0.0, 0.0, 0.0]), vec([1.0, 0.0, 0.0, 1.0])) |
|
| 38 | 38 |
case 1: |
| 39 |
- return vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]) |
|
| 39 |
+ return (vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]), vec([1.0, 0.0, 0.1, 128.0])) |
|
| 40 | 40 |
case 2: |
| 41 |
- top = vec([0.5, 0.5, 1.0, 1.0]) |
|
| 42 |
- bottom = vec([0.0, 0.0, 0.5, 1.0]) |
|
| 41 |
+ top = (vec([0.5, 0.5, 1.0, 1.0]), vec([1.0, 0.0, 0.0, 1.0])) |
|
| 42 |
+ bottom = (vec([0.0, 0.0, 0.5, 1.0]), vec([1.0, 0.0, 0.0, 1.0])) |
|
| 43 | 43 |
match axis: |
| 44 |
- case 3 | -3: |
|
| 45 |
- return top if corner < 2 else bottom |
|
| 46 |
- case 1 | -1: |
|
| 47 |
- return top if not corner % 2 else bottom |
|
| 48 | 44 |
case 2: |
| 49 | 45 |
return top |
| 50 | 46 |
case -2: |
| 51 | 47 |
return bottom |
| 48 |
+ case a: |
|
| 49 |
+ return bottom if corner & (1 << ((a % 4) // 2)) else top |
|
| 52 | 50 |
case 3: |
| 53 |
- return vec([0.7, 0.7, 0.8, 1.0]) |
|
| 51 |
+ return (vec([0.7, 0.7, 0.8, 1.0]), vec([0.5, 0.5, 0.01, 2.0])) |
|
| 54 | 52 |
case 4: |
| 55 |
- return vec([0.5, 0.2, 0.0, 1.0]) |
|
| 53 |
+ top = (vec([0.2, 0.6, 0.2, 1.0]), vec([0.6, 0.4, 0.0, 1.0])) |
|
| 54 |
+ bottom = (vec([0.5, 0.2, 0.0, 1.0]), vec([0.8, 0.2, 0.0, 1.0])) |
|
| 55 |
+ match axis: |
|
| 56 |
+ case 2: |
|
| 57 |
+ return top |
|
| 58 |
+ case -2: |
|
| 59 |
+ return bottom |
|
| 60 |
+ case a: |
|
| 61 |
+ return bottom if corner & (2 >> ((a % 4) // 2)) else top |
|
| 62 |
+ return |
|
| 56 | 63 |
|
| 57 |
- def col_list(self): |
|
| 58 |
- cl = np.empty((3 * len(self.faces), 4), dtype=FLOAT) |
|
| 64 |
+ def mat_list(self): |
|
| 65 |
+ cl = np.empty((2, 3 * len(self.faces), 4), dtype=FLOAT) |
|
| 59 | 66 |
for s in range(len(self.faces) // 2): |
| 60 | 67 |
axis, fd = self.faces[2 * s] |
| 61 |
- for (t, c) in enumerate([0, 1, 2, 3, 2, 1] if axis > 0 else [1, 2, 3, 2, 1, 0]): |
|
| 62 |
- cl[6 * s + t] = self.cube_col(fd, axis, c) |
|
| 68 |
+ for (t, c) in enumerate([0, 1, 2, 3, 2, 1] if (True or axis > 0) else [1, 2, 3, 2, 1, 0]): |
|
| 69 |
+ cl[0][6 * s + t], cl[1][6 * s + t] = self.cube_mat(fd, axis, c) |
|
| 63 | 70 |
return cl |
| ... | ... |
@@ -25,13 +25,13 @@ class Player(camera.Camera): |
| 25 | 25 |
"fps": entity.Entity( |
| 26 | 26 |
[[0.0, 0.0, 0.0], [0.1, 0.0, 0.0], [0.0, -0.1, 0.0], [0.1, -0.1, 0.0]], |
| 27 | 27 |
YELLOW, [-1.0, 1.0, 0.0], mode=GL_TRIANGLE_STRIP, hud=True |
| 28 |
- ), |
|
| 28 |
+ ) |
|
| 29 | 29 |
} |
| 30 |
- offset = [0, 0, 20] |
|
| 30 |
+ offset = [0, 0, 30] |
|
| 31 | 31 |
for d in range(3): |
| 32 | 32 |
roll_vec3(offset, 1) |
| 33 | 33 |
for s in range(2): |
| 34 |
- self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 40, (-1) ** s * vec(offset), 2) |
|
| 34 |
+ self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 60, (-1) ** s * vec(offset), 2) |
|
| 35 | 35 |
self.hud["shl"].draw_mode = GL_LINE_STRIP |
| 36 | 36 |
self.hud["shl"].add_sq(2, 0.96, [0, 0, 0], 2) |
| 37 | 37 |
self.hud["shl"].verts[:] = self.hud["shl"].verts[[0, 1, 3, 2, 0, 3]] |
| ... | ... |
@@ -79,11 +79,11 @@ class Player(camera.Camera): |
| 79 | 79 |
case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED: |
| 80 | 80 |
self.viewport = reshape(ev.x, ev.y) |
| 81 | 81 |
|
| 82 |
- def draw_hud(self, wr, perf): |
|
| 82 |
+ def draw_hud(self, wrs, perf): |
|
| 83 | 83 |
self.hud["skybox"].pos = self.pos |
| 84 |
- c, ap = self.target([wr]) |
|
| 84 |
+ c, ap = self.target(wrs) |
|
| 85 | 85 |
if c: |
| 86 |
- self.hud["shl"].pos = ap[1] + axis2offset(ap[0], 0.52) |
|
| 86 |
+ self.hud["shl"].pos = c.pos + ap[1] + axis2offset(ap[0], 0.52) |
|
| 87 | 87 |
self.hud["shl"].orient[:3, :3] = np.sign(ap[0]) * np.roll(np.identity(3), abs(ap[0]) + 1, 0) |
| 88 | 88 |
else: |
| 89 | 89 |
self.hud["shl"].pos = vec([0, 0, 0]) |
| ... | ... |
@@ -14,12 +14,14 @@ def intersect_box(ro, rd, c, s): |
| 14 | 14 |
return None, None |
| 15 | 15 |
|
| 16 | 16 |
class WorldRegion(mesh.SquareMesh): |
| 17 |
- def __init__(self, size=16): |
|
| 17 |
+ def __init__(self, size=8): |
|
| 18 | 18 |
super().__init__(refls=[0.5, 0.5, 0.5, 32.0]) |
| 19 | 19 |
self.grid = np.zeros((size, size, size), dtype=VOXEL_TYPE) |
| 20 | 20 |
self.r = size |
| 21 | 21 |
|
| 22 | 22 |
def set_voxel(self, pos, vt=0): |
| 23 |
+ if any(pos < 0) or any(pos >= self.r): |
|
| 24 |
+ return pos |
|
| 23 | 25 |
self.grid[*pos] = vt |
| 24 | 26 |
if vt == 0: |
| 25 | 27 |
self.remesh() |
| ... | ... |
@@ -33,7 +35,7 @@ class WorldRegion(mesh.SquareMesh): |
| 33 | 35 |
o = np.array(axis2offset(a), dtype=np.int64) |
| 34 | 36 |
bc = bool(self.grid[*(pos + o)]) |
| 35 | 37 |
if bp ^ bc: |
| 36 |
- a *= bc - bp |
|
| 38 |
+ a *= bp - bc |
|
| 37 | 39 |
self.add_sq(a, 1, pos + o / 2, pos + (0 if bp else o)) |
| 38 | 40 |
|
| 39 | 41 |
def remesh(self): |
| ... | ... |
@@ -79,12 +81,12 @@ class WorldRegion(mesh.SquareMesh): |
| 79 | 81 |
ids = [] |
| 80 | 82 |
for i in range(3): |
| 81 | 83 |
k = np.arange(self.r + 1) if rd[i] >= 0 else np.arange(self.r + 1, 0, -1) |
| 82 |
- ids.append([(d, i) for d in (k - 0.5 - ro[i]) / rd[i]]) |
|
| 84 |
+ ids.append([(d, i) for d in (self.pos[i] + k - 0.5 - ro[i]) / rd[i]]) |
|
| 83 | 85 |
q, p = None, None |
| 84 | 86 |
for (te, i) in heapq.merge(*ids): |
| 85 | 87 |
if te < 0 or te < tn or te > tf - 1e-3: |
| 86 | 88 |
continue |
| 87 |
- q, p = p, np.astype(np.round(ro + (te + 1e-8) * rd - self.pos), np.int16) |
|
| 89 |
+ q, p = p, np.astype(np.round(ro + (te + 1e-4) * rd - self.pos), np.int16) |
|
| 88 | 90 |
assert q is None or abs(np.sum(p - q)) == 1 |
| 89 | 91 |
if self.grid[*p]: |
| 90 | 92 |
sa = np.astype((i + 1) * -np.sign(rd[i]), np.int8) |
| 91 | 93 |