dkl9 commited on 2025-179 22:38:38
Showing 13 changed files, with 315 additions and 192 deletions.
| ... | ... |
@@ -29,7 +29,8 @@ class Camera: |
| 29 | 29 |
u = vec([-sp * st, cp, -sp * ct]) |
| 30 | 30 |
m = np.identity(4, dtype=FLOAT) |
| 31 | 31 |
m[:3, :3] = [s, u, -f] |
| 32 |
- m[:3, 3] = [-np.dot(s, self.pos), -np.dot(u, self.pos), np.dot(f, self.pos)] |
|
| 32 |
+ p = self.pos |
|
| 33 |
+ m[:3, 3] = [-s @ p, -u @ p, f @ p] |
|
| 33 | 34 |
return m |
| 34 | 35 |
|
| 35 | 36 |
def persp_mat(self): |
| ... | ... |
@@ -45,12 +46,13 @@ class Camera: |
| 45 | 46 |
]) |
| 46 | 47 |
|
| 47 | 48 |
def mvp(self, ent): |
| 48 |
- if ent.hud: |
|
| 49 | 49 |
am = np.identity(4, dtype=FLOAT) |
| 50 |
+ vp = np.identity(4, dtype=FLOAT) |
|
| 51 |
+ if ent.hud: |
|
| 50 | 52 |
am[0, 0] = 1 / self.aspect() |
| 51 |
- return ent.translate() @ am @ ent.orient |
|
| 52 | 53 |
else: |
| 53 |
- return self.persp_mat() @ self.view_mat() @ ent.translate() @ ent.orient |
|
| 54 |
+ vp = self.persp_mat() @ self.view_mat() |
|
| 55 |
+ return vp @ ent.translate() @ am @ ent.orient |
|
| 54 | 56 |
|
| 55 | 57 |
def draw(self, ent): |
| 56 | 58 |
if not ent.verts.shape[0]: |
| ... | ... |
@@ -0,0 +1,60 @@ |
| 1 |
+from libs import * |
|
| 2 |
+from OpenGL.GL import shaders |
|
| 3 |
+import os |
|
| 4 |
+ |
|
| 5 |
+VS = f""" |
|
| 6 |
+#version 120 |
|
| 7 |
+{"\n".join([f"attribute {a};" for a in VERTEX_ATTRS])}
|
|
| 8 |
+{"\n".join([f"varying {a}F;" for a in VERTEX_ATTRS])}
|
|
| 9 |
+{"\n".join([
|
|
| 10 |
+ f"uniform {v[1]} {k};" for (k, v) in UNIFORMS.items() if v[0] == "vert"
|
|
| 11 |
+])} |
|
| 12 |
+void main() {{
|
|
| 13 |
+ gl_Position = MVP * vec4(position, 1.0); |
|
| 14 |
+{"\n".join([f" {a.split()[1]}F = {a.split()[1]};" for a in VERTEX_ATTRS])}
|
|
| 15 |
+}} |
|
| 16 |
+""" |
|
| 17 |
+ |
|
| 18 |
+FS = f""" |
|
| 19 |
+#version 120 |
|
| 20 |
+{"\n".join([f"varying {a}F;" for a in VERTEX_ATTRS])}
|
|
| 21 |
+{"\n".join([
|
|
| 22 |
+ f"uniform {v[1]} {k};" for (k, v) in UNIFORMS.items() if v[0] == "frag"
|
|
| 23 |
+])} |
|
| 24 |
+void main() {{
|
|
| 25 |
+ float ambient = reflsF.x; |
|
| 26 |
+ vec3 lightDir = normalize(lightPos.xyz - positionF); |
|
| 27 |
+ float diffuse = reflsF.y * lightPos.w * |
|
| 28 |
+ dot(normalF, lightDir) / pow(distance(lightPos.xyz, positionF), 2); |
|
| 29 |
+ float specular = reflsF.z * lightPos.w * pow(max(dot( |
|
| 30 |
+ normalize(camPos - positionF), reflect(-lightDir, normalF) |
|
| 31 |
+ ), 0.0), reflsF.w); |
|
| 32 |
+ gl_FragColor = vec4((ambient + diffuse + specular) * colF.xyz, colF.w); |
|
| 33 |
+}} |
|
| 34 |
+""" |
|
| 35 |
+ |
|
| 36 |
+def init_display(): |
|
| 37 |
+ pygame.init() |
|
| 38 |
+ full = os.environ.get("FULLSCREEN")
|
|
| 39 |
+ pygame.display.set_mode((640, 480), |
|
| 40 |
+ pygame.DOUBLEBUF | pygame.OPENGL | |
|
| 41 |
+ (pygame.FULLSCREEN | pygame.SCALED if full else pygame.RESIZABLE), |
|
| 42 |
+ vsync=1) |
|
| 43 |
+ pygame.event.set_allowed([ |
|
| 44 |
+ pygame.KEYDOWN, pygame.KEYUP, |
|
| 45 |
+ pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, |
|
| 46 |
+ pygame.QUIT, pygame.WINDOWRESIZED, pygame.WINDOWSIZECHANGED |
|
| 47 |
+ ]) |
|
| 48 |
+ pygame.display.set_caption("Voxels")
|
|
| 49 |
+ sp = shaders.compileProgram( |
|
| 50 |
+ shaders.compileShader(VS, GL_VERTEX_SHADER), |
|
| 51 |
+ shaders.compileShader(FS, GL_FRAGMENT_SHADER) |
|
| 52 |
+ ) |
|
| 53 |
+ for (k, v) in UNIFORMS.items(): |
|
| 54 |
+ v[2] = glGetUniformLocation(sp, k) |
|
| 55 |
+ glUseProgram(sp) |
|
| 56 |
+ glEnable(GL_DEPTH_TEST) |
|
| 57 |
+ glEnable(GL_BLEND) |
|
| 58 |
+ glEnable(GL_CULL_FACE) |
|
| 59 |
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) |
|
| 60 |
+ glClearColor(0.1, 0.1, 0.1, 1.0) |
| ... | ... |
@@ -1,15 +1,25 @@ |
| 1 | 1 |
from libs import * |
| 2 |
+from raycast import intersect_tri |
|
| 3 |
+import types |
|
| 4 |
+ |
|
| 5 |
+draw_modes = types.SimpleNamespace() |
|
| 6 |
+draw_modes.TRI_STRIP = GL_TRIANGLE_STRIP |
|
| 7 |
+draw_modes.TRI_FAN = GL_TRIANGLE_FAN |
|
| 8 |
+draw_modes.TRIS = GL_TRIANGLES |
|
| 9 |
+draw_modes.LINE_STRIP = GL_LINE_STRIP |
|
| 10 |
+draw_modes.LINES = GL_LINES |
|
| 2 | 11 |
|
| 3 | 12 |
class Entity: |
| 4 | 13 |
def __init__(self, verts, col, pos, **kwargs): |
| 5 | 14 |
self.verts = vec(verts) |
| 6 | 15 |
self.hud = kwargs.get("hud", False)
|
| 7 | 16 |
self.col = vec(col) |
| 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]))
|
|
| 17 |
+ refl = [1.0, 0.0, 0.0, 1.0] if self.hud else [0.5, 0.5, 0.0, 32.0] |
|
| 18 |
+ self.refls = vec(kwargs.get("refls", refl))
|
|
| 9 | 19 |
self.pos = vec(pos) |
| 10 | 20 |
self.draw_mode = kwargs.get("mode", GL_TRIANGLE_STRIP)
|
| 11 | 21 |
self.orient = np.identity(4, dtype=FLOAT) |
| 12 |
- self.onclick = lambda s, b, n, h: print(f"clicked {s}:{n} with button {b}, hand {h}")
|
|
| 22 |
+ self.onclick = lambda s, b, n, h: print(f"{h}:{b} on {s}:{n}")
|
|
| 13 | 23 |
self.vao = None |
| 14 | 24 |
self.vbos = [[None, 0] for _ in VERTEX_ATTRS] |
| 15 | 25 |
|
| ... | ... |
@@ -18,19 +28,20 @@ class Entity: |
| 18 | 28 |
|
| 19 | 29 |
def norm_list(self): |
| 20 | 30 |
l = [] |
| 21 |
- if self.draw_mode == GL_TRIANGLE_STRIP or self.draw_mode == GL_TRIANGLE_FAN: |
|
| 31 |
+ match self.draw_mode: |
|
| 32 |
+ case draw_modes.TRI_STRIP | draw_modes.TRI_FAN: |
|
| 22 | 33 |
l.append(norm_tri(self.verts[:3])) |
| 23 | 34 |
for i in range(1, len(self.verts) - 1): |
| 24 | 35 |
l.append(norm_tri(self.verts[i - 1:i + 2])) |
| 25 | 36 |
l.append(norm_tri(self.verts[-3:])) |
| 26 |
- elif self.draw_mode == GL_TRIANGLES: |
|
| 37 |
+ case draw_modes.TRIS: |
|
| 27 | 38 |
for i in range(0, len(self.verts), 3): |
| 28 | 39 |
for _ in range(3): |
| 29 | 40 |
l.append(norm_tri(self.verts[i:i + 3])) |
| 30 |
- elif self.draw_mode == GL_LINE_STRIP or self.draw_mode == GL_LINES: |
|
| 41 |
+ case draw_modes.LINE_STRIP | draw_modes.LINES: |
|
| 31 | 42 |
for _ in range(len(self.verts)): |
| 32 | 43 |
l.append([1, 0, 0]) |
| 33 |
- else: |
|
| 44 |
+ case _: |
|
| 34 | 45 |
raise NotImplementedError |
| 35 | 46 |
return (self.orient[:3, :3] @ vec(l).T).T |
| 36 | 47 |
|
| ... | ... |
@@ -66,14 +77,17 @@ class Entity: |
| 66 | 77 |
if self.hud or self.draw_mode == GL_LINES: |
| 67 | 78 |
return |
| 68 | 79 |
elif self.draw_mode == GL_TRIANGLE_STRIP: |
| 69 |
- tl = ((i, self.verts[i:i + 3]) for i in range(len(self.verts) - 2)) |
|
| 80 |
+ ir = range(len(self.verts) - 2) |
|
| 81 |
+ tl = ((i, self.verts[i:i + 3]) for i in ir) |
|
| 70 | 82 |
elif self.draw_mode == GL_TRIANGLES: |
| 71 |
- tl = ((i, self.verts[3 * i:3 * (i + 1)]) for i in range(len(self.verts) // 3)) |
|
| 83 |
+ ir = range(len(self.verts) // 3) |
|
| 84 |
+ tl = ((i, self.verts[3 * i:3 * (i + 1)]) for i in ir) |
|
| 72 | 85 |
else: |
| 73 | 86 |
raise NotImplementedError |
| 74 | 87 |
d, n = None, None |
| 75 | 88 |
for (i, t) in tl: |
| 76 |
- r = intersect_tri(ro, rd, np.dot(t, self.orient[:3, :3].T) + self.pos) |
|
| 89 |
+ t = np.dot(t, self.orient[:3, :3].T) + self.pos |
|
| 90 |
+ r = intersect_tri(ro, rd, t) |
|
| 77 | 91 |
if r and (d is None or r < d): |
| 78 | 92 |
d, n = r, i |
| 79 | 93 |
return d, n |
| ... | ... |
@@ -0,0 +1,58 @@ |
| 1 |
+from libs import * |
|
| 2 |
+import entity |
|
| 3 |
+import mesh |
|
| 4 |
+ |
|
| 5 |
+def crosshair(): |
|
| 6 |
+ return entity.Entity( |
|
| 7 |
+ [[-0.03, 0, 0], [0.03, 0, 0], [0, -0.03, 0], [0, 0.03, 0]], |
|
| 8 |
+ [1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0], mode=GL_LINES, hud=True |
|
| 9 |
+ ) |
|
| 10 |
+ |
|
| 11 |
+def fps(): |
|
| 12 |
+ return entity.Entity( |
|
| 13 |
+ [[0, 0, 0], [0, -0.1, 0], [0.1, 0, 0], [0.1, -0.1, 0]], |
|
| 14 |
+ YELLOW, [-1.0, 1.0, 0.0], mode=GL_TRIANGLE_STRIP, hud=True |
|
| 15 |
+ ) |
|
| 16 |
+ |
|
| 17 |
+def hand(): |
|
| 18 |
+ h = mesh.SquareMesh(hud=True) |
|
| 19 |
+ h.add_sq(1, 1, [0.5, 0, 0], 1) |
|
| 20 |
+ h.add_sq(2, 1, [0, 0.5, 0], 1) |
|
| 21 |
+ h.add_sq(3, 1, [0, 0, 0.5], 1) |
|
| 22 |
+ h.pos = [0.8, -0.8, 0.0] |
|
| 23 |
+ h.rotate(-1, [0, 1, 0]) |
|
| 24 |
+ h.rotate(0.1, [1, 0, 0]) |
|
| 25 |
+ h.orient[:3, :3] *= 0.2 |
|
| 26 |
+ return h |
|
| 27 |
+ |
|
| 28 |
+def selection(): |
|
| 29 |
+ s = mesh.SquareMesh() |
|
| 30 |
+ s.draw_mode = GL_LINE_STRIP |
|
| 31 |
+ s.add_sq(-2, 0.96, [0, 0, 0], 2) |
|
| 32 |
+ s.verts[:] = s.verts[[0, 1, 3, 2, 0, 3]] |
|
| 33 |
+ return s |
|
| 34 |
+ |
|
| 35 |
+def skybox(): |
|
| 36 |
+ sb = mesh.SquareMesh() |
|
| 37 |
+ offset = [0, 0, VIEW_DIST] |
|
| 38 |
+ for d in range(3): |
|
| 39 |
+ roll_vec3(offset, 1) |
|
| 40 |
+ for s in range(2): |
|
| 41 |
+ sf = (-1) ** s |
|
| 42 |
+ sb.add_sq((d + 1) * sf, 2 * VIEW_DIST, sf * vec(offset), 2) |
|
| 43 |
+ i = 6 * (2 * d + s) + 1 |
|
| 44 |
+ for t in range(2): |
|
| 45 |
+ vp = np.s_[i + 3 * t:i + 3 * t + 2] |
|
| 46 |
+ sb.verts[vp] = sb.verts[vp][::-1] |
|
| 47 |
+ return sb |
|
| 48 |
+ |
|
| 49 |
+def sun(): |
|
| 50 |
+ s = entity.Entity( |
|
| 51 |
+ [[0.0, 0.0, 0.0]], [1.0, 1.0, 0.9, 1.0], |
|
| 52 |
+ normalise(LIGHT_POS) * VIEW_DIST, mode=GL_TRIANGLE_FAN |
|
| 53 |
+ ) |
|
| 54 |
+ cv = [] |
|
| 55 |
+ for k in np.arange(0, 1.01, 0.05): |
|
| 56 |
+ cv.append([np.cos(2 * np.pi * k), 0, np.sin(2 * np.pi * k)]) |
|
| 57 |
+ s.verts = np.concatenate((s.verts, np.multiply(3, cv)), dtype=FLOAT) |
|
| 58 |
+ return s |
| ... | ... |
@@ -1,6 +1,6 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
import camera |
| 3 |
-import entity |
|
| 3 |
+import huds |
|
| 4 | 4 |
|
| 5 | 5 |
def reshape(w, h): |
| 6 | 6 |
glViewport(0, 0, w, h) |
| ... | ... |
@@ -10,39 +10,40 @@ class InteractiveCamera(camera.Camera): |
| 10 | 10 |
def __init__(self, **kwargs): |
| 11 | 11 |
super().__init__(**kwargs) |
| 12 | 12 |
self.sensitivity = kwargs.get("sensitivity", 0.005)
|
| 13 |
- self.rv = vec([0, 0, 0]) |
|
| 13 |
+ self.vaa_vel = vec([0, 0, 0]) |
|
| 14 | 14 |
self.speed = kwargs.get("speed", 0.05)
|
| 15 | 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 |
- ) |
|
| 16 |
+ "crosshair": huds.crosshair() |
|
| 20 | 17 |
} |
| 21 | 18 |
|
| 22 |
- def set_focus(self, f): |
|
| 23 |
- pygame.event.set_grab(f) |
|
| 24 |
- pygame.mouse.set_visible(not f) |
|
| 19 |
+ def set_focus(self, focus): |
|
| 20 |
+ pygame.event.set_grab(focus) |
|
| 21 |
+ pygame.mouse.set_visible(not focus) |
|
| 25 | 22 |
pygame.mouse.set_pos([self.viewport[0] / 2, self.viewport[1] / 2]) |
| 26 |
- self.hud["crosshair"].pos[0] = 2 * (not f) |
|
| 23 |
+ self.hud["crosshair"].pos[0] = 2 * (not focus) |
|
| 27 | 24 |
|
| 28 | 25 |
def get_focus(self): |
| 29 | 26 |
return not pygame.mouse.get_visible() |
| 30 | 27 |
|
| 31 | 28 |
def mouse_move(self, dx, dy): |
| 32 | 29 |
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)) |
|
| 30 |
+ self.phi += self.sensitivity * dy |
|
| 31 |
+ self.phi = min(np.pi / 2, max(-np.pi / 2, self.phi)) |
|
| 34 | 32 |
|
| 35 |
- def update_motion(self, pk): |
|
| 36 |
- self.rv = vec([pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])]) |
|
| 33 |
+ def update_motion(self, pressed, mg=None): |
|
| 34 |
+ diffs = [0, 0, 0] |
|
| 35 |
+ for i in range(3): |
|
| 36 |
+ diffs[i] = pressed[ARROWS[2 * i]] - pressed[ARROWS[2 * i + 1]] |
|
| 37 |
+ self.vaa_vel = vec(diffs) |
|
| 37 | 38 |
|
| 38 |
- def vdof(self): |
|
| 39 |
+ def vaa_mat(self): |
|
| 39 | 40 |
l = self.looking() |
| 40 | 41 |
l[1] = 0 |
| 41 | 42 |
u = self.up() |
| 42 | 43 |
return vec([normalise(np.cross(l, u)), u, normalise(l)]) |
| 43 | 44 |
|
| 44 | 45 |
def update_pos(self): |
| 45 |
- self.pos += self.speed * self.vdof() @ self.rv.T |
|
| 46 |
+ self.pos += self.speed * self.vaa_mat() @ self.vaa_vel.T |
|
| 46 | 47 |
|
| 47 | 48 |
def handle_event(self, ev, mg): |
| 48 | 49 |
match ev.type: |
| ... | ... |
@@ -53,6 +54,6 @@ class InteractiveCamera(camera.Camera): |
| 53 | 54 |
case pygame.WINDOWSIZECHANGED | pygame.WINDOWRESIZED: |
| 54 | 55 |
self.viewport = reshape(ev.x, ev.y) |
| 55 | 56 |
|
| 56 |
- def draw_hud(self, wrs, perf): |
|
| 57 |
+ def draw_hud(self, regions, perf): |
|
| 57 | 58 |
for ent in self.hud.values(): |
| 58 | 59 |
self.draw(ent) |
| ... | ... |
@@ -6,13 +6,23 @@ import pygame |
| 6 | 6 |
FLOAT = np.float32 |
| 7 | 7 |
VOXEL_TYPE = np.uint8 |
| 8 | 8 |
VERTEX_ATTRS = ["vec3 position", "vec4 col", "vec3 normal", "vec4 refls"] |
| 9 |
-UNIFORMS = {"MVP": ["vert", "mat4", None], "lightPos": ["frag", "vec4", None], "camPos": ["frag", "vec3", None]}
|
|
| 9 |
+UNIFORMS = {
|
|
| 10 |
+ "MVP": ["vert", "mat4", None], |
|
| 11 |
+ "lightPos": ["frag", "vec4", None], |
|
| 12 |
+ "camPos": ["frag", "vec3", None] |
|
| 13 |
+} |
|
| 10 | 14 |
|
| 11 | 15 |
LIGHT_POS = [5, 30, 10] |
| 16 |
+REGION_SIZE = 8 |
|
| 12 | 17 |
GRAVITY = 1 |
| 13 | 18 |
VIEW_DIST = 30 |
| 14 | 19 |
FPS = 60 |
| 15 |
-ARROWS = [pygame.K_RIGHT, pygame.K_LEFT, pygame.K_SPACE, pygame.K_LSHIFT, pygame.K_UP, pygame.K_DOWN] |
|
| 20 |
+ARROWS = [ |
|
| 21 |
+ ["RIGHT", "e", "d"], ["LEFT", "a"], |
|
| 22 |
+ ["SPACE"], ["LSHIFT"], |
|
| 23 |
+ ["UP", "COMMA", "w"], ["DOWN", "o", "s"], |
|
| 24 |
+] |
|
| 25 |
+ARROWS = [[getattr(pygame, f"K_{n}") for n in opts] for opts in ARROWS]
|
|
| 16 | 26 |
|
| 17 | 27 |
def vec(l): |
| 18 | 28 |
return np.array(l, dtype=FLOAT) |
| ... | ... |
@@ -32,34 +42,21 @@ def roll_vec3(l, k): |
| 32 | 42 |
case 2: |
| 33 | 43 |
l[:] = [l[1], l[2], l[0]] |
| 34 | 44 |
|
| 35 |
-def axis2offset(a, x=1): |
|
| 36 |
- o = [0, 0, x * np.sign(a)] |
|
| 45 |
+def axis2offset(a, mag=1): |
|
| 46 |
+ o = [0, 0, mag * np.sign(a)] |
|
| 37 | 47 |
roll_vec3(o, abs(a)) |
| 38 | 48 |
return o |
| 39 | 49 |
|
| 40 |
-def smerp(ab, w): |
|
| 41 |
- assert all([abs(x) < 2 for x in ab]) |
|
| 42 |
- for i in range(w.shape[0]): |
|
| 43 |
- ab = [a + w[i] ** 2 * (3 - 2 * w[i]) * (b - a) for (a, b) in zip(ab[::2], ab[1::2])] |
|
| 44 |
- return ab[0] |
|
| 50 |
+def smerp(corners, weights): |
|
| 51 |
+ for w in weights: |
|
| 52 |
+ t = w ** 2 * (3 - 2 * w) |
|
| 53 |
+ pairs = zip(corners[::2], corners[1::2]) |
|
| 54 |
+ corners = [a + t * (b - a) for (a, b) in pairs] |
|
| 55 |
+ return corners[0] |
|
| 45 | 56 |
|
| 46 |
-# https://iquilezles.org/articles/intersectors/ |
|
| 47 |
-def intersect_tri(ro, rd, tv): |
|
| 48 |
- e1 = tv[1] - tv[0] |
|
| 49 |
- e2 = tv[2] - tv[0] |
|
| 50 |
- to = ro - tv[0] |
|
| 51 |
- n = np.cross(e1, e2) |
|
| 52 |
- q = np.cross(to, rd) |
|
| 53 |
- d = 1.0 / np.dot(rd, n) |
|
| 54 |
- u = d * np.dot(-q, e2) |
|
| 55 |
- v = d * np.dot(q, e1) |
|
| 56 |
- t = d * np.dot(-n, to) |
|
| 57 |
- if u > 0.0 and v > 0.0 and u + v < 1.0 and t > 1e-3: |
|
| 58 |
- return t |
|
| 59 |
- |
|
| 60 |
-def norm_tri(tv): |
|
| 61 |
- assert tv.shape[0] == 3 |
|
| 62 |
- return normalise(np.cross(tv[2] - tv[0], tv[1] - tv[0])) |
|
| 57 |
+def norm_tri(verts): |
|
| 58 |
+ assert verts.shape[0] == 3 |
|
| 59 |
+ return normalise(np.cross(verts[2] - verts[0], verts[1] - verts[0])) |
|
| 63 | 60 |
|
| 64 | 61 |
def update_attr(ent, n, l=None): |
| 65 | 62 |
if isinstance(n, int): |
| ... | ... |
@@ -1,45 +1,14 @@ |
| 1 |
-from OpenGL.GL import shaders |
|
| 2 | 1 |
from libs import * |
| 2 |
+from display import init_display |
|
| 3 | 3 |
from entity import Entity |
| 4 | 4 |
from mapgen import MapGen |
| 5 | 5 |
from mesh import SquareMesh |
| 6 | 6 |
from player import Player |
| 7 | 7 |
from region import WorldRegion |
| 8 |
-import os |
|
| 9 |
- |
|
| 10 |
-VS = f""" |
|
| 11 |
-#version 120 |
|
| 12 |
-{"\n".join([f"attribute {a};" for a in VERTEX_ATTRS])}
|
|
| 13 |
-{"\n".join([f"varying {a}F;" for a in VERTEX_ATTRS])}
|
|
| 14 |
-{"\n".join([f"uniform {v[1]} {k};" for (k, v) in UNIFORMS.items() if v[0] == "vert"])}
|
|
| 15 |
-void main() {{
|
|
| 16 |
- gl_Position = MVP * vec4(position, 1.0); |
|
| 17 |
-{"\n".join([f" {a.split()[1]}F = {a.split()[1]};" for a in VERTEX_ATTRS])}
|
|
| 18 |
-}} |
|
| 19 |
-""" |
|
| 20 |
- |
|
| 21 |
-FS = f""" |
|
| 22 |
-#version 120 |
|
| 23 |
-{"\n".join([f"varying {a}F;" for a in VERTEX_ATTRS])}
|
|
| 24 |
-{"\n".join([f"uniform {v[1]} {k};" for (k, v) in UNIFORMS.items() if v[0] == "frag"])}
|
|
| 25 |
-void main() {{
|
|
| 26 |
- float ambient = reflsF.x; |
|
| 27 |
- vec3 lightDir = normalize(lightPos.xyz - positionF); |
|
| 28 |
- float lambert = dot(normalF, lightDir); |
|
| 29 |
- float diffuse = reflsF.y * lightPos.w * lambert / pow(distance(lightPos.xyz, positionF), 2); |
|
| 30 |
- float specular = reflsF.z * lightPos.w * pow(max(dot(normalize(camPos - positionF), reflect(-lightDir, normalF)), 0.0), reflsF.w); |
|
| 31 |
- gl_FragColor = vec4((ambient + diffuse + specular) * colF.xyz, colF.w); |
|
| 32 |
-}} |
|
| 33 |
-""" |
|
| 34 | 8 |
|
| 35 | 9 |
def debug_mode(mg, cam): |
| 36 | 10 |
pass |
| 37 | 11 |
|
| 38 |
-def build_scene(): |
|
| 39 |
- mg = MapGen() |
|
| 40 |
- mg.fetch((0, 0, 0)) |
|
| 41 |
- return mg |
|
| 42 |
- |
|
| 43 | 12 |
def in_view(wr, cam): |
| 44 | 13 |
rwrc = wr.pos + wr.r / 2 - cam.pos |
| 45 | 14 |
close = np.linalg.norm(rwrc) < VIEW_DIST |
| ... | ... |
@@ -49,23 +18,9 @@ def in_view(wr, cam): |
| 49 | 18 |
return close and frust |
| 50 | 19 |
|
| 51 | 20 |
def main(): |
| 52 |
- pygame.init() |
|
| 53 |
- pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL | (pygame.FULLSCREEN | pygame.SCALED if os.environ.get("FULLSCREEN") else pygame.RESIZABLE), vsync=1)
|
|
| 54 |
- pygame.event.set_allowed([pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.QUIT, pygame.WINDOWRESIZED, pygame.WINDOWSIZECHANGED]) |
|
| 55 |
- pygame.display.set_caption("Voxels")
|
|
| 56 |
- sp = shaders.compileProgram( |
|
| 57 |
- shaders.compileShader(VS, GL_VERTEX_SHADER), |
|
| 58 |
- shaders.compileShader(FS, GL_FRAGMENT_SHADER) |
|
| 59 |
- ) |
|
| 60 |
- for (k, v) in UNIFORMS.items(): |
|
| 61 |
- v[2] = glGetUniformLocation(sp, k) |
|
| 62 |
- glUseProgram(sp) |
|
| 63 |
- glEnable(GL_DEPTH_TEST) |
|
| 64 |
- glEnable(GL_BLEND) |
|
| 65 |
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) |
|
| 66 |
- glClearColor(0.1, 0.1, 0.1, 1.0) |
|
| 21 |
+ init_display() |
|
| 67 | 22 |
glUniform4fv(UNIFORMS["lightPos"][2], 1, vec([*LIGHT_POS, 100])) |
| 68 |
- mg = build_scene() |
|
| 23 |
+ mg = MapGen() |
|
| 69 | 24 |
clock = pygame.time.Clock() |
| 70 | 25 |
cam = Player(speed=6/FPS) |
| 71 | 26 |
cam.pos = [4, 8, 4] |
| ... | ... |
@@ -76,10 +31,16 @@ def main(): |
| 76 | 31 |
if wr.dirty: |
| 77 | 32 |
wr.remesh() |
| 78 | 33 |
for ev in pygame.event.get(): |
| 79 |
- if ev.type == pygame.QUIT or ev.type == pygame.KEYUP and ev.unicode == "q": |
|
| 34 |
+ match ev.type: |
|
| 35 |
+ case pygame.QUIT: |
|
| 36 |
+ pygame.quit() |
|
| 37 |
+ return |
|
| 38 |
+ case pygame.KEYUP: |
|
| 39 |
+ match ev.unicode: |
|
| 40 |
+ case "q": |
|
| 80 | 41 |
pygame.quit() |
| 81 | 42 |
return |
| 82 |
- elif ev.type == pygame.KEYUP and ev.unicode == "d": |
|
| 43 |
+ case "d": |
|
| 83 | 44 |
debug_mode(mg, cam) |
| 84 | 45 |
cam.handle_event(ev, mg, scene) |
| 85 | 46 |
cam.update_motion(pygame.key.get_pressed(), mg) |
| ... | ... |
@@ -5,7 +5,11 @@ import world |
| 5 | 5 |
class MapGen(world.WorldHolder): |
| 6 | 6 |
def __init__(self): |
| 7 | 7 |
super().__init__() |
| 8 |
- self.grads = [defaultdict(lambda dim=d: defaultdict(lambda: normalise(np.random.normal(size=dim)))) for d in range(4)] |
|
| 8 |
+ self.grads = [] |
|
| 9 |
+ for d in range(4): |
|
| 10 |
+ self.grads.append(defaultdict(lambda dim=d: |
|
| 11 |
+ defaultdict(lambda: normalise(np.random.normal(size=dim))) |
|
| 12 |
+ )) |
|
| 9 | 13 |
self.poissons = {}
|
| 10 | 14 |
|
| 11 | 15 |
def perlin(self, name, pos, period): |
| ... | ... |
@@ -18,9 +22,7 @@ class MapGen(world.WorldHolder): |
| 18 | 22 |
dots = [] |
| 19 | 23 |
for c in corners: |
| 20 | 24 |
ci = tuple(np.add(c, corner, dtype=int, casting="unsafe")) |
| 21 |
- cg = gt[ci] |
|
| 22 |
- assert abs(np.linalg.norm(cg) - 1) < 1e-3 |
|
| 23 |
- dots.append(np.dot(fract - c, cg)) |
|
| 25 |
+ dots.append(np.dot(fract - c, gt[ci])) |
|
| 24 | 26 |
return 2 * smerp(dots, fract) |
| 25 | 27 |
|
| 26 | 28 |
def poisson(self, name, pos, r, period): |
| ... | ... |
@@ -28,7 +30,9 @@ class MapGen(world.WorldHolder): |
| 28 | 30 |
if name in self.poissons: |
| 29 | 31 |
gt = self.poissons[name] |
| 30 | 32 |
else: |
| 31 |
- gt = defaultdict(lambda: np.random.poisson(1 / np.prod(period), np.repeat(r, dim))) |
|
| 33 |
+ gt = defaultdict(lambda: np.random.poisson( |
|
| 34 |
+ 1 / np.prod(period), np.repeat(r, dim) |
|
| 35 |
+ )) |
|
| 32 | 36 |
self.poissons[name] = gt |
| 33 | 37 |
return gt[tuple((x // r) * r for x in pos)] |
| 34 | 38 |
|
| ... | ... |
@@ -36,17 +40,24 @@ class MapGen(world.WorldHolder): |
| 36 | 40 |
n = 0 |
| 37 | 41 |
for i in range(wr.r): |
| 38 | 42 |
for j in range(wr.r): |
| 39 |
- h = 4 + 4 * self.perlin("height", np.add(wr.pos[[0, 2]], (i, j)), np.array([10, 20])) - wr.pos[1]
|
|
| 43 |
+ h = 4 + 4 * self.perlin( |
|
| 44 |
+ "height", |
|
| 45 |
+ np.add(wr.pos[[0, 2]], (i, j)), |
|
| 46 |
+ np.array([10, 20]) |
|
| 47 |
+ ) - wr.pos[1] |
|
| 40 | 48 |
if h > 0: |
| 41 | 49 |
wr.grid[i, :min(int(h), wr.r), j] = 3 |
| 42 | 50 |
if h < wr.r and h > 0: |
| 43 | 51 |
wr.grid[i, int(h), j] = 4 |
| 44 |
- if 2 <= i < wr.r - 2 and 2 <= j < wr.r - 2 and self.poisson("trees", wr.pos[[0, 2]], wr.r, (6, 6))[i, j] >= 1:
|
|
| 52 |
+ if 2 <= i < wr.r - 2 and 2 <= j < wr.r - 2 and self.poisson( |
|
| 53 |
+ "trees", wr.pos[[0, 2]], wr.r, (6, 6) |
|
| 54 |
+ )[i, j] >= 1: |
|
| 45 | 55 |
for u in range(1, 6): |
| 46 | 56 |
self.voxel_at(wr.pos + [0, h + u, 0], 5) |
| 47 | 57 |
for u in range(5, 7): |
| 48 | 58 |
for v in range(-7 + u, 8 - u): |
| 49 |
- for w in range(-7 + u + abs(v), 8 - u - abs(v)): |
|
| 59 |
+ b = 7 - u - abs(v) |
|
| 60 |
+ for w in range(-b, b + 1): |
|
| 50 | 61 |
self.voxel_at(wr.pos + [v, h + u, w], 6) |
| 51 | 62 |
return n |
| 52 | 63 |
|
| ... | ... |
@@ -29,14 +29,18 @@ 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_mat(self, fd, axis, corner): |
|
| 33 |
- if not isinstance(fd, int): |
|
| 34 |
- fd = self.grid[*fd] |
|
| 35 |
- match fd: |
|
| 32 |
+ def cube_mat(self, face_desc, axis, corner): |
|
| 33 |
+ if not isinstance(face_desc, int): |
|
| 34 |
+ face_desc = self.grid[*face_desc] |
|
| 35 |
+ face_orient = ((axis % 4) // 2) |
|
| 36 |
+ match face_desc: |
|
| 36 | 37 |
case 0: |
| 37 | 38 |
return (vec([0.0, 0.0, 0.0, 0.0]), vec([1.0, 0.0, 0.0, 1.0])) |
| 38 | 39 |
case 1: |
| 39 |
- return (vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]), vec([1.0, 0.0, 0.1, 128.0])) |
|
| 40 |
+ return ( |
|
| 41 |
+ vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]), |
|
| 42 |
+ vec([1.0, 0.0, 0.1, 128.0]) |
|
| 43 |
+ ) |
|
| 40 | 44 |
case 2: |
| 41 | 45 |
top = (vec([0.2, 0.2, 1.0, 1.0]), vec([1.0, 0.0, 0.0, 1.0])) |
| 42 | 46 |
bottom = (vec([0.0, 0.0, 0.2, 1.0]), vec([1.0, 0.0, 0.0, 1.0])) |
| ... | ... |
@@ -46,7 +50,7 @@ class SquareMesh(entity.Entity): |
| 46 | 50 |
case -2: |
| 47 | 51 |
return bottom |
| 48 | 52 |
case a: |
| 49 |
- return bottom if corner & (1 << ((a % 4) // 2)) else top |
|
| 53 |
+ return bottom if corner & (2 >> face_orient) else top |
|
| 50 | 54 |
case 3: |
| 51 | 55 |
return (vec([0.7, 0.7, 0.8, 1.0]), vec([0.5, 0.5, 0.01, 2.0])) |
| 52 | 56 |
case 4: |
| ... | ... |
@@ -58,7 +62,7 @@ class SquareMesh(entity.Entity): |
| 58 | 62 |
case -2: |
| 59 | 63 |
return bottom |
| 60 | 64 |
case a: |
| 61 |
- return bottom if corner & (1 << ((a % 4) // 2)) else top |
|
| 65 |
+ return bottom if corner & (1 << face_orient) else top |
|
| 62 | 66 |
return |
| 63 | 67 |
case 5: |
| 64 | 68 |
core = (vec([1.0, 1.0, 0.8, 1.0]), vec([0.4, 1.6, 0.0, 1.0])) |
| ... | ... |
@@ -75,6 +79,7 @@ class SquareMesh(entity.Entity): |
| 75 | 79 |
cl = np.empty((2, 3 * len(self.faces), 4), dtype=FLOAT) |
| 76 | 80 |
for s in range(len(self.faces) // 2): |
| 77 | 81 |
axis, fd = self.faces[2 * s] |
| 78 |
- for (t, c) in enumerate([0, 1, 2, 3, 2, 1] if (True or axis > 0) else [1, 2, 3, 2, 1, 0]): |
|
| 79 |
- cl[0][6 * s + t], cl[1][6 * s + t] = self.cube_mat(fd, axis, c) |
|
| 82 |
+ for (t, c) in enumerate([0, 1, 2, 3, 2, 1]): |
|
| 83 |
+ i = 6 * s + t |
|
| 84 |
+ cl[0][i], cl[1][i] = self.cube_mat(fd, axis, c) |
|
| 80 | 85 |
return cl |
| ... | ... |
@@ -1,61 +1,39 @@ |
| 1 | 1 |
from libs import * |
| 2 | 2 |
import interactive |
| 3 |
-import entity |
|
| 4 |
-import mesh |
|
| 3 |
+import huds |
|
| 5 | 4 |
|
| 6 | 5 |
class Player(interactive.InteractiveCamera): |
| 7 | 6 |
def __init__(self, **kwargs): |
| 8 | 7 |
super().__init__(**kwargs) |
| 9 |
- self.hud["fps"] = entity.Entity( |
|
| 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]], |
|
| 11 |
- YELLOW, [-1.0, 1.0, 0.0], mode=GL_TRIANGLE_STRIP, hud=True |
|
| 12 |
- ) |
|
| 13 |
- self.hud["skybox"] = mesh.SquareMesh() |
|
| 14 |
- offset = [0, 0, VIEW_DIST] |
|
| 15 |
- for d in range(3): |
|
| 16 |
- roll_vec3(offset, 1) |
|
| 17 |
- for s in range(2): |
|
| 18 |
- self.hud["skybox"].add_sq((d + 1) * (-1) ** s, 2 * VIEW_DIST, (-1) ** s * vec(offset), 2) |
|
| 19 |
- self.hud["sun"] = entity.Entity( |
|
| 20 |
- [[0.0, 0.0, 0.0], *[[2 * np.cos(2 * np.pi * k), 0, 2 * np.sin(2 * np.pi * k)] for k in np.arange(0, 1.01, 0.05)]], |
|
| 21 |
- [1.0, 1.0, 0.8, 1.0], normalise(LIGHT_POS) * VIEW_DIST, mode=GL_TRIANGLE_FAN |
|
| 22 |
- ) |
|
| 23 |
- h = mesh.SquareMesh(hud=True) |
|
| 24 |
- self.hud["hand"] = h |
|
| 25 |
- h.add_sq(1, 1, [0.5, 0, 0], 1) |
|
| 26 |
- h.add_sq(2, 1, [0, 0.5, 0], 1) |
|
| 27 |
- h.add_sq(3, 1, [0, 0, 0.5], 1) |
|
| 28 |
- h.pos = [0.8, -0.8, 0.5] |
|
| 29 |
- h.rotate(2, [0, 1, 0]) |
|
| 30 |
- h.rotate(-0.1, [1, 0, 0]) |
|
| 31 |
- h.orient[:3, :3] *= 0.2 |
|
| 32 |
- self.hud["shl"] = mesh.SquareMesh() |
|
| 33 |
- self.hud["shl"].draw_mode = GL_LINE_STRIP |
|
| 34 |
- self.hud["shl"].add_sq(-2, 0.96, [0, 0, 0], 2) |
|
| 35 |
- self.hud["shl"].verts[:] = self.hud["shl"].verts[[0, 1, 3, 2, 0, 3]] |
|
| 8 |
+ self.hud["fps"] = huds.fps() |
|
| 9 |
+ self.hud["skybox"] = huds.skybox() |
|
| 10 |
+ self.hud["sun"] = huds.sun() |
|
| 11 |
+ self.hud["hand"] = huds.hand() |
|
| 12 |
+ self.hud["shl"] = huds.selection() |
|
| 36 | 13 |
self.hand = 1 |
| 37 | 14 |
self.collides = True |
| 38 | 15 |
self.falls = True |
| 39 | 16 |
|
| 40 |
- def update_motion(self, pk, mg): |
|
| 41 |
- netv = [pk[a] - pk[b] for (a, b) in zip(ARROWS[::2], ARROWS[1::2])] |
|
| 42 |
- if self.collides: |
|
| 43 |
- for i in range(len(netv)): |
|
| 17 |
+ def update_motion(self, pressed, mg): |
|
| 18 |
+ netv = [0, 0, 0] |
|
| 19 |
+ for i in range(3): |
|
| 44 | 20 |
for s in range(2): |
| 45 |
- o = (-1) ** s * self.vdof()[i] |
|
| 46 |
- va = mg.voxel_at(self.pos + o) |
|
| 47 |
- if va: |
|
| 48 |
- netv[i] = (max if s else min)(netv[i], 0) |
|
| 21 |
+ sf = (-1) ** s |
|
| 22 |
+ cp = self.pos + sf * self.vaa_mat()[i] |
|
| 23 |
+ if self.collides and mg.voxel_at(cp): |
|
| 24 |
+ pass |
|
| 49 | 25 |
elif self.falls and i == 1 and s == 1: |
| 50 |
- netv[i] = self.rv[i] - GRAVITY / FPS |
|
| 51 |
- self.rv = vec(netv) |
|
| 26 |
+ netv[i] = self.vaa_vel[i] - GRAVITY / FPS |
|
| 27 |
+ else: |
|
| 28 |
+ netv[i] += sf * any(pressed[j] for j in ARROWS[2 * i + s]) |
|
| 29 |
+ self.vaa_vel = vec(netv) |
|
| 52 | 30 |
|
| 53 |
- def set_hand(self, nh): |
|
| 54 |
- nh = int(nh) |
|
| 55 |
- self.hand = nh |
|
| 56 |
- self.hud["hand"].faces = [(a, self.hand) for (a, _) in self.hud["hand"].faces] |
|
| 31 |
+ def set_hand(self, new_hand): |
|
| 32 |
+ self.hand = int(new_hand) |
|
| 33 |
+ hg = self.hud["hand"] |
|
| 34 |
+ hg.faces = [(a, self.hand) for (a, _) in hg.faces] |
|
| 57 | 35 |
for i in range(1, 4, 2): |
| 58 |
- update_attr(self.hud["hand"], i) |
|
| 36 |
+ update_attr(hg, i) |
|
| 59 | 37 |
|
| 60 | 38 |
def handle_event(self, ev, mg, scene): |
| 61 | 39 |
super().handle_event(ev, mg) |
| ... | ... |
@@ -83,10 +61,18 @@ class Player(interactive.InteractiveCamera): |
| 83 | 61 |
c, ap = self.target(wrs) |
| 84 | 62 |
if c: |
| 85 | 63 |
self.hud["shl"].pos = c.pos + ap[1] + axis2offset(ap[0], 0.52) |
| 86 |
- self.hud["shl"].orient[:3, :3] = np.sign(ap[0]) * np.roll(np.identity(3), abs(ap[0]) + 1, 0) |
|
| 64 |
+ self.hud["shl"].orient[:3, :3] = np.sign(ap[0]) * np.roll( |
|
| 65 |
+ np.identity(3), abs(ap[0]) + 1, 0 |
|
| 66 |
+ ) |
|
| 87 | 67 |
else: |
| 88 | 68 |
self.hud["shl"].pos = vec([0, 0, 0]) |
| 89 |
- self.hud["fps"].col = GREEN if 0.9 < perf < 1.1 else YELLOW if perf > 0.5 else RED |
|
| 69 |
+ if 0.9 < perf < 1.1: |
|
| 70 |
+ perf_col = GREEN |
|
| 71 |
+ elif perf > 0.5: |
|
| 72 |
+ perf_col = YELLOW |
|
| 73 |
+ else: |
|
| 74 |
+ perf_col = RED |
|
| 75 |
+ self.hud["fps"].col = perf_col |
|
| 90 | 76 |
update_attr(self.hud["fps"], "col") |
| 91 | 77 |
for ent in self.hud.values(): |
| 92 | 78 |
self.draw(ent) |
| ... | ... |
@@ -0,0 +1,26 @@ |
| 1 |
+from libs import * |
|
| 2 |
+ |
|
| 3 |
+# https://iquilezles.org/articles/intersectors/ |
|
| 4 |
+def intersect_tri(ro, rd, verts): |
|
| 5 |
+ e1 = verts[1] - verts[0] |
|
| 6 |
+ e2 = verts[2] - verts[0] |
|
| 7 |
+ to = ro - verts[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 |
+def intersect_box(ro, rd, c, s): |
|
| 18 |
+ m = 1 / rd |
|
| 19 |
+ n = m * (ro - c) |
|
| 20 |
+ k = np.abs(m) * s |
|
| 21 |
+ tn = np.max(-n - k) |
|
| 22 |
+ tf = np.min(-n + k) |
|
| 23 |
+ if tf > tn and tf > 0: |
|
| 24 |
+ return tn, tf |
|
| 25 |
+ else: |
|
| 26 |
+ return None, None |
| ... | ... |
@@ -1,36 +1,28 @@ |
| 1 | 1 |
from libs import * |
| 2 |
+from raycast import intersect_box |
|
| 2 | 3 |
import heapq |
| 3 | 4 |
import mesh |
| 4 | 5 |
|
| 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 |
|
| 15 |
- |
|
| 16 | 6 |
class WorldRegion(mesh.SquareMesh): |
| 17 |
- def __init__(self, size=8): |
|
| 7 |
+ def __init__(self, size=REGION_SIZE): |
|
| 18 | 8 |
super().__init__(refls=[0.5, 0.5, 0.5, 32.0]) |
| 19 | 9 |
self.grid = np.zeros((size, size, size), dtype=VOXEL_TYPE) |
| 20 | 10 |
self.r = size |
| 21 | 11 |
self.dirty = False |
| 22 | 12 |
|
| 23 |
- def set_voxel(self, pos, vt=0): |
|
| 13 |
+ def set_voxel(self, pos, voxel_type=0): |
|
| 24 | 14 |
if any(pos < 0) or any(pos >= self.r): |
| 25 | 15 |
return pos |
| 26 |
- self.grid[*pos] = vt |
|
| 27 |
- if vt == 0: |
|
| 16 |
+ self.grid[*pos] = voxel_type |
|
| 17 |
+ if voxel_type == 0: |
|
| 28 | 18 |
self.remesh() |
| 29 | 19 |
else: |
| 30 |
- bp = bool(vt) |
|
| 20 |
+ bp = bool(voxel_type) |
|
| 31 | 21 |
for d in range(3): |
| 32 | 22 |
for s in range(2): |
| 33 |
- if pos[d] == self.r - 1 and s == 0 or pos[d] == 0 and s == 1: |
|
| 23 |
+ oob = pos[d] == self.r - 1 and s == 0 |
|
| 24 |
+ oob |= s == 0 or pos[d] == 0 and s == 1 |
|
| 25 |
+ if oob: |
|
| 34 | 26 |
continue |
| 35 | 27 |
a = (d + 1) * (-1) ** s |
| 36 | 28 |
o = np.array(axis2offset(a), dtype=np.int64) |
| ... | ... |
@@ -76,7 +68,9 @@ class WorldRegion(mesh.SquareMesh): |
| 76 | 68 |
self.dirty = False |
| 77 | 69 |
|
| 78 | 70 |
def norm_list(self): |
| 79 |
- return np.astype(np.repeat([axis2offset(a) for (a, _) in self.faces], 3, 0), FLOAT) |
|
| 71 |
+ return np.astype(np.repeat([ |
|
| 72 |
+ axis2offset(a) for (a, _) in self.faces |
|
| 73 |
+ ], 3, 0), FLOAT) |
|
| 80 | 74 |
|
| 81 | 75 |
def raycast(self, ro, rd): |
| 82 | 76 |
vr = np.repeat(self.r, 3) |
| ... | ... |
@@ -84,12 +78,17 @@ class WorldRegion(mesh.SquareMesh): |
| 84 | 78 |
if tn is not None: |
| 85 | 79 |
ids = [] |
| 86 | 80 |
for i in range(3): |
| 87 |
- k = np.arange(self.r + 1) if rd[i] >= 0 else np.arange(self.r + 1, 0, -1) |
|
| 88 |
- ids.append([(d, i) for d in (self.pos[i] + k - 0.5 - ro[i]) / rd[i]]) |
|
| 81 |
+ if rd[i] >= 0: |
|
| 82 |
+ k = np.arange(self.r + 1) |
|
| 83 |
+ else: |
|
| 84 |
+ k = np.arange(self.r + 1, 0, -1) |
|
| 85 |
+ dists = (self.pos[i] + k - 0.5 - ro[i]) / rd[i] |
|
| 86 |
+ ids.append([(d, i) for d in dists]) |
|
| 89 | 87 |
for (te, i) in heapq.merge(*ids): |
| 90 | 88 |
if te < 0 or te < tn or te > tf - 1e-3: |
| 91 | 89 |
continue |
| 92 |
- p = np.astype(np.round(ro + (te + 1e-4) * rd - self.pos), np.int16) |
|
| 90 |
+ p = ro + (te + 1e-4) * rd - self.pos |
|
| 91 |
+ p = np.astype(np.round(p), np.int16) |
|
| 93 | 92 |
if self.grid[*p]: |
| 94 | 93 |
sa = np.astype((i + 1) * -np.sign(rd[i]), np.int8) |
| 95 | 94 |
return te, (sa, p) |
| ... | ... |
@@ -18,16 +18,19 @@ class WorldHolder(): |
| 18 | 18 |
wr.onclick = (lambda s, b, c, h: self.click_region(s, b, c, h)) |
| 19 | 19 |
return wr |
| 20 | 20 |
|
| 21 |
- def voxel_at(self, pos, vt=None): |
|
| 21 |
+ def voxel_at(self, pos, voxel_type=None): |
|
| 22 |
+ try: |
|
| 22 | 23 |
r = next(iter(self.regions.values())).r |
| 24 |
+ except StopIteration: |
|
| 25 |
+ r = REGION_SIZE |
|
| 23 | 26 |
pr, pf = np.divmod(pos + 0.5, r) |
| 24 | 27 |
wr = self.fetch(tuple(int(r * c) for c in pr)) |
| 25 | 28 |
it = tuple(int(c) for c in pf) |
| 26 |
- if vt is None: |
|
| 29 |
+ if voxel_type is None: |
|
| 27 | 30 |
return wr.grid[*it] |
| 28 | 31 |
else: |
| 29 | 32 |
old = wr.grid[*it] |
| 30 |
- wr.grid[*it] = vt |
|
| 33 |
+ wr.grid[*it] = voxel_type |
|
| 31 | 34 |
wr.dirty = True |
| 32 | 35 |
return old |
| 33 | 36 |
|
| 34 | 37 |