Load multiple regions and fix colours
dkl9

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