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 |