Player collides with world and loads regions
dkl9

dkl9 commited on 2025-176 00:13:04
Showing 6 changed files, with 123 additions and 89 deletions.

... ...
@@ -0,0 +1,58 @@
1
+from libs import *
2
+import camera
3
+import entity
4
+
5
+def reshape(w, h):
6
+    glViewport(0, 0, w, h)
7
+    return (w, h)
8
+
9
+class InteractiveCamera(camera.Camera):
10
+    def __init__(self, **kwargs):
11
+        super().__init__(**kwargs)
12
+        self.sensitivity = kwargs.get("sensitivity", 0.005)
13
+        self.rv = vec([0, 0, 0])
14
+        self.speed = kwargs.get("speed", 0.05)
15
+        self.hud = {
16
+            "crosshair": entity.Entity(
17
+                [[-0.03, 0.0, 0.0], [0.03, 0.0, 0.0], [0.0, -0.03, 0.0], [0.0, 0.03, 0.0]],
18
+                [1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0], mode=GL_LINES, hud=True
19
+            )
20
+        }
21
+
22
+    def set_focus(self, f): 
23
+        pygame.event.set_grab(f)
24
+        pygame.mouse.set_visible(not f)
25
+        pygame.mouse.set_pos([self.viewport[0] / 2, self.viewport[1] / 2])
26
+        self.hud["crosshair"].pos[0] = 2 * (not f)
27
+
28
+    def get_focus(self):
29
+        return not pygame.mouse.get_visible()
30
+
31
+    def mouse_move(self, dx, dy):
32
+        self.theta = (self.theta - self.sensitivity * dx) % (2 * np.pi)
33
+        self.phi = min(np.pi / 2, max(-np.pi / 2, self.phi + self.sensitivity * dy))
34
+                
35
+    def update_motion(self, pk):
36
+        self.rv = vec([pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])])
37
+
38
+    def vdof(self):
39
+        l = self.looking()
40
+        l[1] = 0
41
+        u = self.up()
42
+        return vec([normalise(np.cross(l, u)), u, normalise(l)])
43
+
44
+    def update_pos(self):
45
+        self.pos += self.speed * self.vdof() @ self.rv.T
46
+
47
+    def handle_event(self, ev, mg):
48
+        match ev.type:
49
+            case pygame.KEYUP | pygame.KEYDOWN if ev.key in ARROWS:
50
+                self.update_motion(pygame.key.get_pressed(), mg)
51
+            case pygame.MOUSEMOTION if self.get_focus():
52
+                self.mouse_move(ev.rel[0], -ev.rel[1])
53
+            case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED:
54
+                self.viewport = reshape(ev.x, ev.y)
55
+
56
+    def draw_hud(self, wrs, perf):
57
+        for ent in self.hud.values():
58
+            self.draw(ent)
... ...
@@ -8,6 +8,10 @@ VOXEL_TYPE = np.uint8
8 8
 VERTEX_ATTRS = ["vec3 position", "vec4 col", "vec3 normal", "vec4 refls"]
9 9
 UNIFORMS = {"MVP": ["vert", "mat4", None], "lightPos": ["frag", "vec4", None], "camPos": ["frag", "vec3", None]}
10 10
 
11
+VIEW_DIST = 30
12
+FPS = 60
13
+ARROWS = [pygame.K_RIGHT, pygame.K_LEFT, pygame.K_SPACE, pygame.K_LSHIFT, pygame.K_UP, pygame.K_DOWN]
14
+
11 15
 def vec(l):
12 16
     return np.array(l, dtype=FLOAT)
13 17
 
... ...
@@ -31,6 +35,12 @@ def axis2offset(a, x=1):
31 35
     roll_vec3(o, abs(a))
32 36
     return o
33 37
 
38
+def smerp(ab, w):
39
+    assert all([abs(x) < 2 for x in ab])
40
+    for i in range(w.shape[0]):
41
+        ab = [a + w[i] ** 2 * (3 - 2 * w[i]) * (b - a) for (a, b) in zip(ab[::2], ab[1::2])]
42
+    return ab[0]
43
+
34 44
 # https://iquilezles.org/articles/intersectors/
35 45
 def intersect_tri(ro, rd, tv):
36 46
     e1 = tv[1] - tv[0]
... ...
@@ -31,31 +31,12 @@ void main() {{
31 31
 }}
32 32
 """
33 33
 
34
-FPS = 60
35
-
36 34
 def debug_mode(mg, cam):
37 35
     pass
38 36
 
39
-def click_mesh(self, button, coords, mg):
40
-    match button:
41
-        case 1:
42
-            self.set_voxel(coords[1])
43
-        case 2:
44
-            print(f"coords {coords}")
45
-        case 3:
46
-            offset = axis2offset(coords[0])
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}")
52
-    if button % 2 == 1:
53
-        for i in range(len(VERTEX_ATTRS)):
54
-            update_attr(self, i)
55
-
56 37
 def build_scene():
57 38
     mg = MapGen()
58
-    mg.fetch((0, 0, 0), click_mesh)
39
+    mg.fetch((0, 0, 0))
59 40
     return mg
60 41
 
61 42
 def main():
... ...
@@ -74,18 +55,19 @@ def main():
74 55
     glUniform4fv(UNIFORMS["lightPos"][2], 1, vec([5, 30, 10, 100]))
75 56
     mg = build_scene()
76 57
     clock = pygame.time.Clock()
77
-    cam = Player(speed=10/FPS)
78
-    cam.pos = [8, 16, 8]
58
+    cam = Player(speed=6/FPS)
59
+    cam.pos = [4, 8, 4]
79 60
     cam.set_focus(True)
80 61
     while True:
81
-        scene = list(mg.regions.values())
62
+        scene = list(filter(lambda wr: np.linalg.norm(wr.pos - cam.pos) < VIEW_DIST, mg.regions.values()))
82 63
         for ev in pygame.event.get():
83 64
             if ev.type == pygame.QUIT or ev.type == pygame.KEYUP and ev.unicode == "q":
84 65
                 pygame.quit()
85 66
                 return
86 67
             elif ev.type == pygame.KEYUP and ev.unicode == "d":
87 68
                 debug_mode(mg, cam)
88
-            cam.handle_event(ev, scene)
69
+            cam.handle_event(ev, mg, scene)
70
+        cam.update_motion(pygame.key.get_pressed(), mg)
89 71
         cam.update_pos()
90 72
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
91 73
         for ent in scene:
... ...
@@ -1,18 +1,12 @@
1 1
 from libs import *
2 2
 import region
3 3
 
4
-def smerp(ab, w):
5
-    assert all([abs(x) < 2 for x in ab])
6
-    for i in range(w.shape[0]):
7
-        ab = [a + w[i] ** 2 * (3 - 2 * w[i]) * (b - a) for (a, b) in zip(ab[::2], ab[1::2])]
8
-    return ab[0]
9
-
10 4
 class MapGen:
11 5
     def __init__(self):
12 6
         self.grads = [{} for _ in range(4)]
13 7
         self.regions = {}
14 8
 
15
-    def fetch(self, pos, onclick=None):
9
+    def fetch(self, pos):
16 10
         if pos in self.regions:
17 11
             return self.regions[pos]
18 12
         else:
... ...
@@ -20,11 +14,35 @@ class MapGen:
20 14
             wr.pos = vec(pos)
21 15
             self.world_natural(wr)
22 16
             wr.remesh()
23
-            if onclick:
24
-                wr.onclick = lambda s, b, c: onclick(s, b, c, self)
17
+            wr.onclick = lambda s, b, c: self.click_region(s, b, c)
25 18
             self.regions[pos] = wr
26 19
             return wr
27 20
 
21
+    def voxel_at(self, pos):
22
+        r = next(iter(self.regions.values())).r
23
+        pr, pf = np.divmod(pos + 0.5, r)
24
+        wr = self.fetch(tuple(int(r * c) for c in pr))
25
+        return wr.grid[*tuple(int(c) for c in pf)]
26
+
27
+    def click_region(self, wr, button, coords):
28
+        match button:
29
+            case 1:
30
+                wr.set_voxel(coords[1])
31
+            case 2:
32
+                print(f"coords {coords}")
33
+            case 3:
34
+                offset = axis2offset(coords[0])
35
+                p = wr.set_voxel(coords[1] + offset, 1)
36
+                if p is not None:
37
+                    rc = wr.pos + p // wr.r * wr.r
38
+                    awr = self.fetch(tuple(int(x) for x in rc))
39
+                    p = p % wr.r
40
+                    if not awr.grid[*p]:
41
+                        awr.set_voxel(p, 1)
42
+        if button % 2 == 1:
43
+            for i in range(len(VERTEX_ATTRS)):
44
+                update_attr(wr, i)
45
+
28 46
     def perlin(self, name, pos, period):
29 47
         dim = pos.shape[0]
30 48
         gt = self.grads[dim].setdefault(name, {})
... ...
@@ -1,83 +1,47 @@
1 1
 from libs import *
2
-import camera
2
+import interactive
3 3
 import entity
4 4
 import mesh
5 5
 
6
-ARROWS = [pygame.K_RIGHT, pygame.K_LEFT, pygame.K_SPACE, pygame.K_LSHIFT, pygame.K_UP, pygame.K_DOWN]
7
-
8
-def reshape(w, h):
9
-    glViewport(0, 0, w, h)
10
-    return (w, h)
11
-
12
-class Player(camera.Camera):
6
+class Player(interactive.InteractiveCamera):
13 7
     def __init__(self, **kwargs):
14 8
         super().__init__(**kwargs)
15
-        self.sensitivity = kwargs.get("sensitivity", 0.005)
16
-        self.rv = vec([0, 0, 0])
17
-        self.speed = kwargs.get("speed", 0.05)
18
-        self.hud = {
19
-            "skybox": mesh.SquareMesh(),
20
-            "shl": mesh.SquareMesh(),
21
-            "crosshair": entity.Entity(
22
-                [[-0.03, 0.0, 0.0], [0.03, 0.0, 0.0], [0.0, -0.03, 0.0], [0.0, 0.03, 0.0]],
23
-                [1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0], mode=GL_LINES, hud=True
24
-            ),
25
-            "fps": entity.Entity(
9
+        self.hud["fps"] = entity.Entity(
26 10
             [[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 11
             YELLOW, [-1.0, 1.0, 0.0], mode=GL_TRIANGLE_STRIP, hud=True
28 12
         )
29
-        }
30
-        offset = [0, 0, 30]
13
+        self.hud["skybox"] = mesh.SquareMesh()
14
+        offset = [0, 0, VIEW_DIST]
31 15
         for d in range(3):
32 16
             roll_vec3(offset, 1)
33 17
             for s in range(2):
34
-                self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 60, (-1) ** s * vec(offset), 2)
18
+                self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 2 * VIEW_DIST, (-1) ** s * vec(offset), 2)
19
+        self.hud["shl"] = mesh.SquareMesh()
35 20
         self.hud["shl"].draw_mode = GL_LINE_STRIP
36 21
         self.hud["shl"].add_sq(2, 0.96, [0, 0, 0], 2)
37 22
         self.hud["shl"].verts[:] = self.hud["shl"].verts[[0, 1, 3, 2, 0, 3]]
38 23
 
39
-    def set_focus(self, f): 
40
-        pygame.event.set_grab(f)
41
-        pygame.mouse.set_visible(not f)
42
-        pygame.mouse.set_pos([self.viewport[0] / 2, self.viewport[1] / 2])
43
-        self.hud["crosshair"].pos[0] = 2 * (not f)
44
-
45
-    def get_focus(self):
46
-        return not pygame.mouse.get_visible()
47
-
48
-    def mouse_move(self, dx, dy):
49
-        self.theta = (self.theta - self.sensitivity * dx) % (2 * np.pi)
50
-        self.phi = min(np.pi / 2, max(-np.pi / 2, self.phi + self.sensitivity * dy))
51
-                
52
-    def update_motion(self, pk):
53
-        self.rv = vec([pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])])
54
-
55
-    def vdof(self):
56
-        l = self.looking()
57
-        l[1] = 0
58
-        u = self.up()
59
-        return vec([np.cross(l, u), u, l])
60
-
61
-    def update_pos(self):
62
-        self.pos += self.speed * self.vdof() @ self.rv.T
24
+    def update_motion(self, pk, mg):
25
+        netv = [pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])]
26
+        for i in range(len(netv)):
27
+            for s in range(2):
28
+                o = (-1) ** s * self.vdof()[i]
29
+                if mg.voxel_at(self.pos + o):
30
+                    netv[i] = (max if s else min)(netv[i], 0)
31
+        self.rv = vec(netv)
63 32
 
64
-    def handle_event(self, ev, scene):
33
+    def handle_event(self, ev, mg, scene):
34
+        super().handle_event(ev, mg)
65 35
         match ev.type:
66
-            case pygame.KEYUP | pygame.KEYDOWN:
67
-                if ev.unicode == "\x1b" and ev.type == pygame.KEYUP:
36
+            case pygame.KEYUP:
37
+                if ev.unicode == "\x1b":
68 38
                     self.set_focus(False)
69
-                elif ev.key in ARROWS:
70
-                    self.update_motion(pygame.key.get_pressed())
71
-            case pygame.MOUSEMOTION if self.get_focus():
72
-                self.mouse_move(ev.rel[0], -ev.rel[1])
73 39
             case pygame.MOUSEBUTTONUP if ev.button == 1:
74 40
                 self.set_focus(True)
75 41
             case pygame.MOUSEBUTTONDOWN if self.get_focus():
76 42
                 t, n = self.target(scene)
77 43
                 if t:
78 44
                     t.click(ev.button, n)
79
-            case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED:
80
-                self.viewport = reshape(ev.x, ev.y)
81 45
 
82 46
     def draw_hud(self, wrs, perf):
83 47
         self.hud["skybox"].pos = self.pos
... ...
@@ -39,6 +39,8 @@ class WorldRegion(mesh.SquareMesh):
39 39
                         self.add_sq(a, 1, pos + o / 2, pos + (0 if bp else o))
40 40
 
41 41
     def remesh(self):
42
+        if not np.sum(self.grid):
43
+            return
42 44
         offset = vec([0.5, 0.0, 0.0])
43 45
         self.faces = []
44 46
         prev, curr = 0, 0
... ...
@@ -87,7 +89,7 @@ class WorldRegion(mesh.SquareMesh):
87 89
                 if te < 0 or te < tn or te > tf - 1e-3:
88 90
                     continue
89 91
                 q, p = p, np.astype(np.round(ro + (te + 1e-4) * rd - self.pos), np.int16)
90
-                assert q is None or abs(np.sum(p - q)) == 1
92
+                assert q is None or np.sum(abs(p - q)) <= 2
91 93
                 if self.grid[*p]:
92 94
                     sa = np.astype((i + 1) * -np.sign(rd[i]), np.int8)
93 95
                     return te, (sa, p)
94 96