Simple place/break RGB voxel prototype
dkl9

dkl9 commited on 2025-170 19:41:48
Showing 5 changed files, with 99 additions and 13 deletions.

... ...
@@ -65,16 +65,16 @@ class Camera:
65 65
 
66 66
     def target(self, scene):
67 67
         l = self.looking()
68
-        d, c = None, None
68
+        d, c, n = None, None, None
69 69
         for ent in scene:
70 70
             if ent.draw_mode == GL_TRIANGLE_STRIP:
71
-                tl = (ent.verts[i:i + 3] for i in range(len(ent.verts) - 2))
71
+                tl = ((i, ent.verts[i:i + 3]) for i in range(len(ent.verts) - 2))
72 72
             elif ent.draw_mode == GL_TRIANGLES:
73
-                tl = (ent.verts[3 * i:3 * (i + 1)] for i in range(len(ent.verts) // 3))
73
+                tl = ((i, ent.verts[3 * i:3 * (i + 1)]) for i in range(len(ent.verts) // 3))
74 74
             else:
75 75
                 raise NotImplementedError
76
-            for t in tl:
76
+            for (i, t) in tl:
77 77
                 r = intersect_tri(self.pos, l, np.dot(t, ent.orient[:3, :3].T) + ent.pos)
78 78
                 if r and (d is None or r < d):
79
-                    d, c = r, ent
80
-        return c
79
+                    d, c, n = r, ent, i
80
+        return c, n
... ...
@@ -7,7 +7,7 @@ class Entity:
7 7
         self.pos = vec(pos)
8 8
         self.draw_mode = mode
9 9
         self.orient = np.identity(4, dtype=FLOAT)
10
-        self.onclick = lambda s, b: print(f"clicked {s} with button {b}")
10
+        self.onclick = lambda s, b, n: print(f"clicked {s}:{n} with button {b}")
11 11
         self.vao = None
12 12
         self.vbos = [[None, 0], [None, 0]]
13 13
 
... ...
@@ -19,7 +19,7 @@ class Entity:
19 19
             l = self.verts if i == 0 else self.col_list()
20 20
         if self.vao:
21 21
             glBindBuffer(GL_ARRAY_BUFFER, self.vbos[i][0])
22
-            if l.shape[0] > self.vbos[i][1]:
22
+            if l.shape[0] != self.vbos[i][1]:
23 23
                 glBufferData(GL_ARRAY_BUFFER, l.nbytes, l, GL_DYNAMIC_DRAW)
24 24
                 self.vbos[i][1] = l.shape[0]
25 25
             else:
... ...
@@ -50,5 +50,5 @@ class Entity:
50 50
             [0, 0, 0, 1]
51 51
         ]) @ self.orient
52 52
 
53
-    def click(self, button):
54
-        self.onclick(self, button)
53
+    def click(self, button, face):
54
+        self.onclick(self, button, face)
... ...
@@ -24,9 +24,45 @@ void main() {
24 24
 }
25 25
 """
26 26
 
27
+WORLD_SIZE = 8
28
+
29
+world = np.zeros((WORLD_SIZE, WORLD_SIZE, WORLD_SIZE), dtype=np.uint8)
30
+for i in range(2):
31
+    for j in range(2):
32
+        for k in range(2):
33
+            world[-i % WORLD_SIZE, -j % WORLD_SIZE, -k % WORLD_SIZE] = 1
34
+cubes = []
35
+
36
+def place_voxel(mesh, ind):
37
+    a = mesh.faces[ind][0]
38
+    p = cubes[ind // 12] + np.sign(a) * np.roll([0, 0, 1], abs(a))
39
+    world[p] = 1
40
+    mesh.add_cube(1, p)
41
+    cubes.append(p)
42
+    mesh.update_attr(0)
43
+    mesh.update_attr(1)
44
+
45
+def break_voxel(mesh, ind):
46
+    p = cubes[ind // 12]
47
+    world[p] = 0
48
+    mesh.del_cube((ind // 12) * 12)
49
+    del cubes[ind // 12]
50
+
51
+def click_mesh(self, button, ind):
52
+    if button == 1:
53
+        break_voxel(self, ind)
54
+    elif button == 3:
55
+        place_voxel(self, ind)
56
+
27 57
 def build_scene():
28 58
     box = SquareMesh()
29
-    box.add_cube(2, [0, 0, 0])
59
+    for i in range(world.shape[0]):
60
+        for j in range(world.shape[1]):
61
+            for k in range(world.shape[2]):
62
+                if world[i, j, k] == 1:
63
+                    box.add_cube(1, [i, j, k])
64
+                    cubes.append(np.array((i, j, k), dtype=np.uint8))
65
+    box.onclick = click_mesh
30 66
     return [box]
31 67
 
32 68
 def main():
... ...
@@ -0,0 +1,49 @@
1
+from libs import *
2
+import entity
3
+
4
+def cube_col(axis):
5
+    return vec([
6
+        [0.0, 0.0, 0.0, 1.0],
7
+        [1.0, 0.0, 0.0, 1.0],
8
+        [0.0, 1.0, 0.0, 1.0],
9
+        [0.0, 0.0, 1.0, 1.0]
10
+    ][abs(axis)]) + 0.4 * np.sign(axis, dtype=FLOAT)
11
+
12
+class SquareMesh(entity.Entity):
13
+    def __init__(self):
14
+        super().__init__(
15
+            np.empty((0, 3), dtype=FLOAT),
16
+            [0.0, 1.0, 0.0, 1.0],
17
+            [0.0, 0.0, 0.0],
18
+            GL_TRIANGLES
19
+        )
20
+        self.faces = []
21
+
22
+    def add_sq(self, axis, width, pos):
23
+        w = width / 2
24
+        sq = pos + np.roll(vec([
25
+            [w, w, 0], [-w, w, 0], [w, -w, 0],
26
+            [-w, -w, 0], [w, -w, 0], [-w, w, 0]
27
+        ]), abs(axis), 1)
28
+        if axis < 0:
29
+            sq = np.flip(sq, 0)
30
+        self.verts = np.concatenate((self.verts, sq), dtype=FLOAT)
31
+        for i in range(2):
32
+            self.faces.append((axis, 1))
33
+
34
+    def add_cube(self, width, pos):
35
+        offset = vec([width / 2, 0, 0])
36
+        for a in range(1, 4):
37
+            self.add_sq(a, width, pos + offset)
38
+            self.add_sq(-a, width, pos - offset)
39
+            offset = np.roll(offset, 1, 0)
40
+        return len(self.faces) - 12
41
+
42
+    def del_cube(self, ind):
43
+        self.verts = np.delete(self.verts, np.s_[3 * ind:3 * (ind + 12)], 0)
44
+        del self.faces[ind:ind + 12]
45
+        self.update_attr(0)
46
+        self.update_attr(1)
47
+
48
+    def col_list(self):
49
+        return np.repeat([cube_col(x[0]) for x in self.faces], 3, 0)
... ...
@@ -55,8 +55,9 @@ class Player(camera.Camera):
55 55
                 if ev.button == 1:
56 56
                     self.set_focus(True)
57 57
             case pygame.MOUSEBUTTONDOWN:
58
-                t = self.target(scene)
58
+                if self.get_focus():
59
+                    t, n = self.target(scene)
59 60
                     if t:
60
-                    t.click(ev.button)
61
+                        t.click(ev.button, n)
61 62
             case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED:
62 63
                 self.aspect = reshape(ev.x, ev.y)
63 64