dkl9 commited on 2025-174 14:21:36
Showing 10 changed files, with 230 additions and 119 deletions.
| ... | ... |
@@ -1,26 +1,12 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
|
| 3 |
-# https://iquilezles.org/articles/intersectors/ |
|
| 4 |
-def intersect_tri(ro, rd, tv): |
|
| 5 |
- e1 = tv[1] - tv[0] |
|
| 6 |
- e2 = tv[2] - tv[0] |
|
| 7 |
- to = ro - tv[0] |
|
| 8 |
- n = np.cross(e1, e2) |
|
| 9 |
- q = np.cross(to, rd) |
|
| 10 |
- d = 1.0 / np.dot(rd, n) |
|
| 11 |
- u = d * np.dot(-q, e2) |
|
| 12 |
- v = d * np.dot(q, e1) |
|
| 13 |
- t = d * np.dot(-n, to) |
|
| 14 |
- if u > 0.0 and v > 0.0 and u + v < 1.0 and t > 1e-3: |
|
| 15 |
- return t |
|
| 16 |
- |
|
| 17 | 3 |
class Camera: |
| 18 | 4 |
def __init__(self, **kwargs): |
| 19 | 5 |
self.pos = vec([0, 0, 0]) |
| 20 | 6 |
self.theta = 0 |
| 21 | 7 |
self.phi = 0 |
| 22 | 8 |
self.fovy = kwargs.get("fovy", 90)
|
| 23 |
- self.aspect = 1 |
|
| 9 |
+ self.viewport = (640, 480) |
|
| 24 | 10 |
|
| 25 | 11 |
def looking(self): |
| 26 | 12 |
return vec([ |
| ... | ... |
@@ -32,6 +18,9 @@ class Camera: |
| 32 | 18 |
def up(self): |
| 33 | 19 |
return vec([0, 1, 0]) |
| 34 | 20 |
|
| 21 |
+ def aspect(self): |
|
| 22 |
+ return self.viewport[0] / self.viewport[1] |
|
| 23 |
+ |
|
| 35 | 24 |
def view_mat(self): |
| 36 | 25 |
f = normalize(self.looking()) |
| 37 | 26 |
s = normalize(np.cross(f, self.up())) |
| ... | ... |
@@ -47,7 +36,7 @@ class Camera: |
| 47 | 36 |
f = 1.0 / np.tan(np.radians(self.fovy) / 2) |
| 48 | 37 |
depth = near - far |
| 49 | 38 |
return vec([ |
| 50 |
- [f / self.aspect, 0, 0, 0], |
|
| 39 |
+ [f / self.aspect(), 0, 0, 0], |
|
| 51 | 40 |
[0, f, 0, 0], |
| 52 | 41 |
[0, 0, (near + far) / depth, (2 * near * far) / depth], |
| 53 | 42 |
[0, 0, -1, 0] |
| ... | ... |
@@ -56,7 +45,7 @@ class Camera: |
| 56 | 45 |
def mvp(self, ent): |
| 57 | 46 |
if ent.hud: |
| 58 | 47 |
am = np.identity(4, dtype=FLOAT) |
| 59 |
- am[0, 0] = 1 / self.aspect |
|
| 48 |
+ am[0, 0] = 1 / self.aspect() |
|
| 60 | 49 |
return ent.translate() @ am @ ent.orient |
| 61 | 50 |
else: |
| 62 | 51 |
return self.persp_mat() @ self.view_mat() @ ent.translate() @ ent.orient |
| ... | ... |
@@ -72,16 +61,7 @@ class Camera: |
| 72 | 61 |
l = self.looking() |
| 73 | 62 |
d, c, n = None, None, None |
| 74 | 63 |
for ent in scene: |
| 75 |
- if ent.hud: |
|
| 76 |
- continue |
|
| 77 |
- elif ent.draw_mode == GL_TRIANGLE_STRIP: |
|
| 78 |
- tl = ((i, ent.verts[i:i + 3]) for i in range(len(ent.verts) - 2)) |
|
| 79 |
- elif ent.draw_mode == GL_TRIANGLES: |
|
| 80 |
- tl = ((i, ent.verts[3 * i:3 * (i + 1)]) for i in range(len(ent.verts) // 3)) |
|
| 81 |
- else: |
|
| 82 |
- raise NotImplementedError |
|
| 83 |
- for (i, t) in tl: |
|
| 84 |
- r = intersect_tri(self.pos, l, np.dot(t, ent.orient[:3, :3].T) + ent.pos) |
|
| 64 |
+ r, i = ent.raycast(self.pos, l) |
|
| 85 | 65 |
if r and (d is None or r < d): |
| 86 | 66 |
d, c, n = r, ent, i |
| 87 | 67 |
return c, n |
| ... | ... |
@@ -1,5 +1,19 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
|
| 3 |
+# https://iquilezles.org/articles/intersectors/ |
|
| 4 |
+def intersect_tri(ro, rd, tv): |
|
| 5 |
+ e1 = tv[1] - tv[0] |
|
| 6 |
+ e2 = tv[2] - tv[0] |
|
| 7 |
+ to = ro - tv[0] |
|
| 8 |
+ n = np.cross(e1, e2) |
|
| 9 |
+ q = np.cross(to, rd) |
|
| 10 |
+ d = 1.0 / np.dot(rd, n) |
|
| 11 |
+ u = d * np.dot(-q, e2) |
|
| 12 |
+ v = d * np.dot(q, e1) |
|
| 13 |
+ t = d * np.dot(-n, to) |
|
| 14 |
+ if u > 0.0 and v > 0.0 and u + v < 1.0 and t > 1e-3: |
|
| 15 |
+ return t |
|
| 16 |
+ |
|
| 3 | 17 |
class Entity: |
| 4 | 18 |
def __init__(self, verts, col, pos, **kwargs): |
| 5 | 19 |
self.verts = vec(verts) |
| ... | ... |
@@ -53,3 +67,19 @@ class Entity: |
| 53 | 67 |
|
| 54 | 68 |
def click(self, button, face): |
| 55 | 69 |
self.onclick(self, button, face) |
| 70 |
+ |
|
| 71 |
+ def raycast(self, ro, rd): |
|
| 72 |
+ if self.hud or self.draw_mode == GL_LINES: |
|
| 73 |
+ return |
|
| 74 |
+ elif self.draw_mode == GL_TRIANGLE_STRIP: |
|
| 75 |
+ tl = ((i, self.verts[i:i + 3]) for i in range(len(self.verts) - 2)) |
|
| 76 |
+ elif self.draw_mode == GL_TRIANGLES: |
|
| 77 |
+ tl = ((i, self.verts[3 * i:3 * (i + 1)]) for i in range(len(self.verts) // 3)) |
|
| 78 |
+ else: |
|
| 79 |
+ raise NotImplementedError |
|
| 80 |
+ d, n = None, None |
|
| 81 |
+ for (i, t) in tl: |
|
| 82 |
+ r = intersect_tri(ro, rd, np.dot(t, self.orient[:3, :3].T) + self.pos) |
|
| 83 |
+ if r and (d is None or r < d): |
|
| 84 |
+ d, n = r, i |
|
| 85 |
+ return d, n |
| ... | ... |
@@ -1,6 +1,8 @@ |
| 1 | 1 |
from OpenGL.GL import shaders |
| 2 | 2 |
from libs import * |
| 3 | 3 |
from entity import Entity |
| 4 |
+from mapgen import MapGen |
|
| 5 |
+from mesh import SquareMesh |
|
| 4 | 6 |
from player import Player |
| 5 | 7 |
from region import WorldRegion |
| 6 | 8 |
|
| ... | ... |
@@ -24,28 +26,27 @@ void main() {
|
| 24 | 26 |
} |
| 25 | 27 |
""" |
| 26 | 28 |
|
| 27 |
-FPS = 240 |
|
| 29 |
+FPS = 60 |
|
| 28 | 30 |
|
| 29 |
-def debug_mode(): |
|
| 31 |
+def debug_mode(scene, cam): |
|
| 30 | 32 |
pass |
| 31 | 33 |
|
| 32 |
-def click_mesh(self, button, ind): |
|
| 33 |
- a, p = self.faces[ind] |
|
| 34 |
+def click_mesh(self, button, coords): |
|
| 34 | 35 |
match button: |
| 35 | 36 |
case 1: |
| 36 |
- self.del_voxel(ind) |
|
| 37 |
+ self.set_voxel(coords[1]) |
|
| 37 | 38 |
case 2: |
| 38 |
- print(f"face {a} of {p}")
|
|
| 39 |
+ print(f"coords {coords}")
|
|
| 39 | 40 |
case 3: |
| 40 |
- self.add_voxel(p + np.sign(a) * np.roll([0, 0, 1], abs(a))) |
|
| 41 |
+ offset = axis2offset(coords[0]) |
|
| 42 |
+ self.set_voxel(coords[1] + offset, 2) |
|
| 41 | 43 |
if button % 2 == 1: |
| 42 |
- self.remesh() |
|
| 43 | 44 |
self.update_attr(0) |
| 44 | 45 |
self.update_attr(1) |
| 45 | 46 |
|
| 46 |
-def build_scene(): |
|
| 47 |
+def build_scene(mg): |
|
| 47 | 48 |
box = WorldRegion() |
| 48 |
- print(f"map generated with {box.gen_perlin(np.random.rand(2, 2, 2, 3))} voxels")
|
|
| 49 |
+ print(f"map generated with {mg.world_natural(box)} voxels")
|
|
| 49 | 50 |
box.remesh() |
| 50 | 51 |
box.onclick = click_mesh |
| 51 | 52 |
return [box] |
| ... | ... |
@@ -58,13 +59,14 @@ def main(): |
| 58 | 59 |
shaders.compileShader(VS, GL_VERTEX_SHADER), |
| 59 | 60 |
shaders.compileShader(FS, GL_FRAGMENT_SHADER) |
| 60 | 61 |
) |
| 62 |
+ loc_mvp = glGetUniformLocation(sp, "MVP") |
|
| 61 | 63 |
glUseProgram(sp) |
| 62 | 64 |
glEnable(GL_DEPTH_TEST) |
| 63 |
- loc_mvp = glGetUniformLocation(sp, "MVP") |
|
| 64 | 65 |
glClearColor(0.1, 0.1, 0.1, 1.0) |
| 65 |
- scene = build_scene() |
|
| 66 |
+ mg = MapGen() |
|
| 67 |
+ scene = build_scene(mg) |
|
| 66 | 68 |
clock = pygame.time.Clock() |
| 67 |
- cam = Player() |
|
| 69 |
+ cam = Player(speed=10/FPS) |
|
| 68 | 70 |
cam.pos = [8, 16, 8] |
| 69 | 71 |
cam.set_focus(True) |
| 70 | 72 |
while True: |
| ... | ... |
@@ -73,16 +75,15 @@ def main(): |
| 73 | 75 |
pygame.quit() |
| 74 | 76 |
return |
| 75 | 77 |
elif ev.type == pygame.KEYUP and ev.unicode == "d": |
| 76 |
- debug_mode() |
|
| 78 |
+ debug_mode(scene, cam) |
|
| 77 | 79 |
cam.handle_event(ev, scene) |
| 78 | 80 |
cam.update_pos() |
| 79 | 81 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) |
| 80 |
- perf = clock.get_fps() / FPS |
|
| 81 |
- cam.hud["fps"].col = GREEN if 0.9 < perf < 1.1 else YELLOW if perf > 0.5 else RED |
|
| 82 |
- cam.hud["fps"].update_attr(1) |
|
| 83 |
- for ent in itertools.chain(scene, cam.hud.values()): |
|
| 82 |
+ for ent in scene: |
|
| 84 | 83 |
cam.draw(ent, loc_mvp) |
| 84 |
+ perf = clock.get_fps() / FPS |
|
| 85 |
+ cam.draw_hud(loc_mvp, scene[0], perf) |
|
| 85 | 86 |
pygame.display.flip() |
| 86 |
- clock.tick(240) |
|
| 87 |
+ clock.tick(FPS) |
|
| 87 | 88 |
|
| 88 | 89 |
main() |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 1 |
+from libs import * |
|
| 2 |
+ |
|
| 3 |
+def smerp(ab, w): |
|
| 4 |
+ assert all([abs(x) < 2 for x in ab]) |
|
| 5 |
+ for i in range(w.shape[0]): |
|
| 6 |
+ ab = [a + w[i] ** 2 * (3 - 2 * w[i]) * (b - a) for (a, b) in zip(ab[::2], ab[1::2])] |
|
| 7 |
+ return ab[0] |
|
| 8 |
+ |
|
| 9 |
+class MapGen: |
|
| 10 |
+ def __init__(self): |
|
| 11 |
+ self.grads = [{} for _ in range(4)]
|
|
| 12 |
+ |
|
| 13 |
+ def perlin(self, name, pos, period): |
|
| 14 |
+ dim = pos.shape[0] |
|
| 15 |
+ gt = self.grads[dim].setdefault(name, {})
|
|
| 16 |
+ corner, fract = np.divmod(pos, period) |
|
| 17 |
+ fract /= period |
|
| 18 |
+ # https://stackoverflow.com/a/58827205 |
|
| 19 |
+ corners = np.arange(1 << dim)[:, None] >> np.arange(dim) & 1 |
|
| 20 |
+ dots = [] |
|
| 21 |
+ for c in corners: |
|
| 22 |
+ ci = tuple(np.add(c, corner, dtype=int, casting="unsafe")) |
|
| 23 |
+ dots.append(np.dot(fract - c, gt.setdefault(ci, np.random.rand(dim)))) |
|
| 24 |
+ return smerp(dots, fract) |
|
| 25 |
+ |
|
| 26 |
+ def world_natural(self, wr): |
|
| 27 |
+ n = 0 |
|
| 28 |
+ for i in range(wr.r): |
|
| 29 |
+ for j in range(wr.r): |
|
| 30 |
+ h = 8 + 4 * self.perlin("height", np.add(wr.pos[[0, 2]], (i, j)), np.array([10, 5]))
|
|
| 31 |
+ for k in range(int(h)): |
|
| 32 |
+ wr.grid[i, k, j] = 1 |
|
| 33 |
+ n += 1 |
|
| 34 |
+ return n |
|
| 35 |
+ |
|
| 36 |
+ def world_min(self, wr): |
|
| 37 |
+ wr.add_voxel((0, 0, 0)) |
|
| 38 |
+ return 1 |
|
| 39 |
+ |
|
| 40 |
+ def world_grid(self, wr): |
|
| 41 |
+ n = 0 |
|
| 42 |
+ for i in range(0, wr.r, 2): |
|
| 43 |
+ for j in range(0, wr.r, 2): |
|
| 44 |
+ for k in range(0, wr.r, 2): |
|
| 45 |
+ wr.grid[i, j, k] = 1 |
|
| 46 |
+ n += 1 |
|
| 47 |
+ return n |
|
| 48 |
+ |
|
| 49 |
+ def world_checker(self, wr): |
|
| 50 |
+ n = 0 |
|
| 51 |
+ for i in range(wr.r): |
|
| 52 |
+ for j in range(wr.r): |
|
| 53 |
+ for k in range(wr.r): |
|
| 54 |
+ if not (i + j + k) % 2: |
|
| 55 |
+ wr.grid[i, j, k] = 1 |
|
| 56 |
+ n += 1 |
|
| 57 |
+ return n |
| ... | ... |
@@ -1,21 +1,14 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
import entity |
| 3 | 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 | 4 |
class SquareMesh(entity.Entity): |
| 13 |
- def __init__(self): |
|
| 5 |
+ def __init__(self, **kwargs): |
|
| 14 | 6 |
super().__init__( |
| 15 | 7 |
np.empty((0, 3), dtype=FLOAT), |
| 16 | 8 |
[0.0, 1.0, 0.0, 1.0], |
| 17 | 9 |
[0.0, 0.0, 0.0], |
| 18 |
- mode=GL_TRIANGLES |
|
| 10 |
+ mode=GL_TRIANGLES, |
|
| 11 |
+ **kwargs |
|
| 19 | 12 |
) |
| 20 | 13 |
self.faces = [] |
| 21 | 14 |
|
| ... | ... |
@@ -35,5 +28,31 @@ class SquareMesh(entity.Entity): |
| 35 | 28 |
self.verts = np.delete(self.verts, np.s_[3 * ind:3 * (ind + 2)], 0) |
| 36 | 29 |
del self.faces[ind:ind + 2] |
| 37 | 30 |
|
| 31 |
+ def cube_col(self, fd, axis, corner): |
|
| 32 |
+ if not isinstance(fd, int): |
|
| 33 |
+ fd = self.grid[*fd] |
|
| 34 |
+ match fd: |
|
| 35 |
+ case 0: |
|
| 36 |
+ return vec([0.0, 0.0, 0.0, 0.0]) |
|
| 37 |
+ case 1: |
|
| 38 |
+ return vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]) |
|
| 39 |
+ case 2: |
|
| 40 |
+ top = vec([0.5, 0.5, 1.0, 1.0]) |
|
| 41 |
+ bottom = vec([0.0, 0.0, 0.5, 1.0]) |
|
| 42 |
+ match axis: |
|
| 43 |
+ case 3 | -3: |
|
| 44 |
+ return top if corner < 2 else bottom |
|
| 45 |
+ case 1 | -1: |
|
| 46 |
+ return top if not corner % 2 else bottom |
|
| 47 |
+ case 2: |
|
| 48 |
+ return top |
|
| 49 |
+ case -2: |
|
| 50 |
+ return bottom |
|
| 51 |
+ |
|
| 38 | 52 |
def col_list(self): |
| 39 |
- return np.repeat([cube_col(x[0]) for x in self.faces], 3, 0) |
|
| 53 |
+ cl = np.empty((3 * len(self.faces), 4), dtype=FLOAT) |
|
| 54 |
+ for s in range(len(self.faces) // 2): |
|
| 55 |
+ axis, fd = self.faces[2 * s] |
|
| 56 |
+ for (t, c) in enumerate([0, 1, 2, 3, 2, 1] if axis > 0 else [1, 2, 3, 2, 1, 0]): |
|
| 57 |
+ cl[6 * s + t] = self.cube_col(fd, axis, c) |
|
| 58 |
+ return cl |
| ... | ... |
@@ -1,12 +1,13 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
import camera |
| 3 | 3 |
import entity |
| 4 |
+import mesh |
|
| 4 | 5 |
|
| 5 |
-ARROWS = [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT, pygame.K_SPACE, pygame.K_LSHIFT] |
|
| 6 |
+ARROWS = [pygame.K_RIGHT, pygame.K_LEFT, pygame.K_SPACE, pygame.K_LSHIFT, pygame.K_UP, pygame.K_DOWN] |
|
| 6 | 7 |
|
| 7 | 8 |
def reshape(w, h): |
| 8 | 9 |
glViewport(0, 0, w, h) |
| 9 |
- return w / h |
|
| 10 |
+ return (w, h) |
|
| 10 | 11 |
|
| 11 | 12 |
class Player(camera.Camera): |
| 12 | 13 |
def __init__(self, **kwargs): |
| ... | ... |
@@ -15,6 +16,8 @@ class Player(camera.Camera): |
| 15 | 16 |
self.rv = vec([0, 0, 0]) |
| 16 | 17 |
self.speed = kwargs.get("speed", 0.05)
|
| 17 | 18 |
self.hud = {
|
| 19 |
+ "skybox": mesh.SquareMesh(), |
|
| 20 |
+ "shl": mesh.SquareMesh(), |
|
| 18 | 21 |
"crosshair": entity.Entity( |
| 19 | 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]], |
| 20 | 23 |
[1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0], mode=GL_LINES, hud=True |
| ... | ... |
@@ -24,7 +27,14 @@ class Player(camera.Camera): |
| 24 | 27 |
YELLOW, [-1.0, 1.0, 0.0], mode=GL_TRIANGLE_STRIP, hud=True |
| 25 | 28 |
), |
| 26 | 29 |
} |
| 27 |
- self.viewport = (640, 480) |
|
| 30 |
+ offset = [0, 0, 20] |
|
| 31 |
+ for d in range(3): |
|
| 32 |
+ roll_vec3(offset, 1) |
|
| 33 |
+ for s in range(2): |
|
| 34 |
+ self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 40, (-1) ** s * vec(offset), 2) |
|
| 35 |
+ self.hud["shl"].draw_mode = GL_LINE_STRIP |
|
| 36 |
+ self.hud["shl"].add_sq(2, 0.96, [0, 0, 0], 2) |
|
| 37 |
+ self.hud["shl"].verts[:] = self.hud["shl"].verts[[0, 1, 3, 2, 0, 3]] |
|
| 28 | 38 |
|
| 29 | 39 |
def set_focus(self, f): |
| 30 | 40 |
pygame.event.set_grab(f) |
| ... | ... |
@@ -40,11 +50,7 @@ class Player(camera.Camera): |
| 40 | 50 |
self.phi = min(np.pi / 2, max(-np.pi / 2, self.phi + self.sensitivity * dy)) |
| 41 | 51 |
|
| 42 | 52 |
def update_motion(self, pk): |
| 43 |
- self.rv = vec([ |
|
| 44 |
- pk[pygame.K_RIGHT] - pk[pygame.K_LEFT], |
|
| 45 |
- pk[pygame.K_SPACE] - pk[pygame.K_LSHIFT], |
|
| 46 |
- pk[pygame.K_UP] - pk[pygame.K_DOWN] |
|
| 47 |
- ]) |
|
| 53 |
+ self.rv = vec([pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])]) |
|
| 48 | 54 |
|
| 49 | 55 |
def vdof(self): |
| 50 | 56 |
l = self.looking() |
| ... | ... |
@@ -62,17 +68,26 @@ class Player(camera.Camera): |
| 62 | 68 |
self.set_focus(False) |
| 63 | 69 |
elif ev.key in ARROWS: |
| 64 | 70 |
self.update_motion(pygame.key.get_pressed()) |
| 65 |
- case pygame.MOUSEMOTION: |
|
| 66 |
- if self.get_focus(): |
|
| 71 |
+ case pygame.MOUSEMOTION if self.get_focus(): |
|
| 67 | 72 |
self.mouse_move(ev.rel[0], -ev.rel[1]) |
| 68 |
- case pygame.MOUSEBUTTONUP: |
|
| 69 |
- if ev.button == 1: |
|
| 73 |
+ case pygame.MOUSEBUTTONUP if ev.button == 1: |
|
| 70 | 74 |
self.set_focus(True) |
| 71 |
- case pygame.MOUSEBUTTONDOWN: |
|
| 72 |
- if self.get_focus(): |
|
| 75 |
+ case pygame.MOUSEBUTTONDOWN if self.get_focus(): |
|
| 73 | 76 |
t, n = self.target(scene) |
| 74 | 77 |
if t: |
| 75 | 78 |
t.click(ev.button, n) |
| 76 | 79 |
case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED: |
| 77 |
- self.aspect = reshape(ev.x, ev.y) |
|
| 78 |
- self.viewport = (ev.x, ev.y) |
|
| 80 |
+ self.viewport = reshape(ev.x, ev.y) |
|
| 81 |
+ |
|
| 82 |
+ def draw_hud(self, loc_mvp, wr, perf): |
|
| 83 |
+ self.hud["skybox"].pos = self.pos |
|
| 84 |
+ c, ap = self.target([wr]) |
|
| 85 |
+ if c: |
|
| 86 |
+ self.hud["shl"].pos = ap[1] + axis2offset(ap[0], 0.52) |
|
| 87 |
+ self.hud["shl"].orient[:3, :3] = np.sign(ap[0]) * np.roll(np.identity(3), abs(ap[0]) + 1, 0) |
|
| 88 |
+ else: |
|
| 89 |
+ self.hud["shl"].pos = vec([0, 0, 0]) |
|
| 90 |
+ self.hud["fps"].col = GREEN if 0.9 < perf < 1.1 else YELLOW if perf > 0.5 else RED |
|
| 91 |
+ self.hud["fps"].update_attr(1) |
|
| 92 |
+ for ent in self.hud.values(): |
|
| 93 |
+ self.draw(ent, loc_mvp) |
| ... | ... |
@@ -1,23 +1,42 @@ |
| 1 | 1 |
from libs import * |
| 2 |
+import heapq |
|
| 2 | 3 |
import mesh |
| 3 | 4 |
|
| 4 |
-def smoothstep(x): |
|
| 5 |
- return x ** 2 * (3 - 2 * x) |
|
| 6 |
- |
|
| 7 |
-def interp(ab, t): |
|
| 8 |
- return ab[0] + smoothstep(t) * (ab[1] - ab[0]) |
|
| 5 |
+def intersect_box(ro, rd, c, s): |
|
| 6 |
+ m = 1 / rd |
|
| 7 |
+ n = m * (ro - c) |
|
| 8 |
+ k = np.abs(m) * s |
|
| 9 |
+ tn = np.max(-n - k) |
|
| 10 |
+ tf = np.min(-n + k) |
|
| 11 |
+ if tf > tn and tf > 0: |
|
| 12 |
+ return tn, tf |
|
| 13 |
+ else: |
|
| 14 |
+ return None, None |
|
| 9 | 15 |
|
| 10 | 16 |
class WorldRegion(mesh.SquareMesh): |
| 11 | 17 |
def __init__(self, size=16): |
| 12 | 18 |
super().__init__() |
| 13 | 19 |
self.grid = np.zeros((size, size, size), dtype=VOXEL_TYPE) |
| 14 | 20 |
self.r = size |
| 21 |
+ self.dirty = 0 |
|
| 15 | 22 |
|
| 16 |
- def add_voxel(self, pos, vt=1): |
|
| 23 |
+ def set_voxel(self, pos, vt=0): |
|
| 17 | 24 |
self.grid[*pos] = vt |
| 18 |
- |
|
| 19 |
- def del_voxel(self, ind): |
|
| 20 |
- self.grid[*self.faces[ind][1]] = 0 |
|
| 25 |
+ bp = bool(vt) |
|
| 26 |
+ for d in range(3): |
|
| 27 |
+ for s in range(2): |
|
| 28 |
+ if pos[d] == self.r - 1 and s == 0 or pos[d] == 0 and s == 1: |
|
| 29 |
+ continue |
|
| 30 |
+ a = (d + 1) * (-1) ** s |
|
| 31 |
+ o = np.array(axis2offset(a), dtype=np.int64) |
|
| 32 |
+ bc = bool(self.grid[*(pos + o)]) |
|
| 33 |
+ if bp ^ bc: |
|
| 34 |
+ a = (bp - bc) * (d + 1) |
|
| 35 |
+ self.add_sq(a, 1, pos + o / 2, pos + (0 if bp else o)) |
|
| 36 |
+ self.dirty += 1 |
|
| 37 |
+ if self.dirty > self.r: |
|
| 38 |
+ self.dirty = 0 |
|
| 39 |
+ self.remesh() |
|
| 21 | 40 |
|
| 22 | 41 |
def remesh(self): |
| 23 | 42 |
offset = vec([0.5, 0.0, 0.0]) |
| ... | ... |
@@ -25,8 +44,7 @@ class WorldRegion(mesh.SquareMesh): |
| 25 | 44 |
prev, curr = 0, 0 |
| 26 | 45 |
vl = [] |
| 27 | 46 |
for d in range(3): |
| 28 |
- offset = np.array([1, 0, 0]) |
|
| 29 |
- roll_vec3(offset, d) |
|
| 47 |
+ offset = np.array(axis2offset(d + 1)) |
|
| 30 | 48 |
for i in range(self.r): |
| 31 | 49 |
for j in range(self.r): |
| 32 | 50 |
for k in range(self.r + 1): |
| ... | ... |
@@ -50,37 +68,19 @@ class WorldRegion(mesh.SquareMesh): |
| 50 | 68 |
self.faces.append(((bp - bc) * (d + 1), p)) |
| 51 | 69 |
self.verts = vec(vl) |
| 52 | 70 |
|
| 53 |
- def prof_min(self): |
|
| 54 |
- self.add_voxel((0, 0, 0)) |
|
| 55 |
- self.remesh() |
|
| 56 |
- |
|
| 57 |
- def prof_med(self): |
|
| 58 |
- for i in range(self.r // 2): |
|
| 59 |
- for j in range(self.r // 2): |
|
| 60 |
- for k in range(self.r // 2): |
|
| 61 |
- self.add_voxel((2 * i, 2 * j, 2 * k)) |
|
| 62 |
- self.remesh() |
|
| 63 |
- |
|
| 64 |
- def prof_max(self): |
|
| 65 |
- for i in range(self.r): |
|
| 66 |
- for j in range(self.r): |
|
| 67 |
- for k in range(self.r): |
|
| 68 |
- if not (i + j + k) % 2: |
|
| 69 |
- self.add_voxel((i, j, k)) |
|
| 70 |
- self.remesh() |
|
| 71 |
- |
|
| 72 |
- def gen_perlin(self, grads): |
|
| 73 |
- corners = np.array([(i, j, k) for i in range(2) for j in range(2) for k in range(2)]) |
|
| 74 |
- n = 0 |
|
| 75 |
- for i in range(self.r): |
|
| 76 |
- for j in range(self.r): |
|
| 77 |
- for k in range(self.r): |
|
| 78 |
- w = np.array((i, j, k)) / self.r |
|
| 79 |
- d = [np.dot(grads[*c], w) for c in corners] |
|
| 80 |
- d = [interp(d[2 * k:2 * (k + 1)], w[2]) for k in range(4)] |
|
| 81 |
- d = [interp(d[:2], w[1]), interp(d[2:4], w[1])] |
|
| 82 |
- p = interp(d, w[0]) |
|
| 83 |
- if p > 2 * w[1] - 0.5: |
|
| 84 |
- self.add_voxel((i, j, k)) |
|
| 85 |
- n += 1 |
|
| 86 |
- return n |
|
| 71 |
+ def raycast(self, ro, rd): |
|
| 72 |
+ vr = np.repeat(self.r, 3) |
|
| 73 |
+ tn, tf = intersect_box(ro, rd, self.pos + vr / 2 - 0.5, vr / 2) |
|
| 74 |
+ if tn is not None: |
|
| 75 |
+ if tn < 0: |
|
| 76 |
+ tn = 0 |
|
| 77 |
+ ids = [[(d, i) for d in ((np.arange(self.r) + 0.5) * np.sign(rd[i]) - np.mod(ro[i] + tn * rd[i], 1)) / rd[i]] for i in range(3)] |
|
| 78 |
+ for (te, i) in heapq.merge(*ids): |
|
| 79 |
+ ts = tn + te |
|
| 80 |
+ p = np.astype(np.round(ro + (ts + 1e-2) * rd - self.pos), np.int16) |
|
| 81 |
+ if any(p < 0) or any(p >= self.r): |
|
| 82 |
+ break |
|
| 83 |
+ elif self.grid[*p]: |
|
| 84 |
+ sa = np.astype((i + 1) * -np.sign(rd[i]), np.int8) |
|
| 85 |
+ return ts, (sa, p) |
|
| 86 |
+ return None, None |
|
| 87 | 87 |