Command-line options and tweak textures
dkl9

dkl9 commited on 2025-184 01:10:44
Showing 9 changed files, with 61 additions and 70 deletions.

... ...
@@ -33,18 +33,13 @@ void main() {{
33 33
     vec3 c = colF.xyz;
34 34
 {"\n".join([
35 35
     f"if (abs(j - {i}.0) < 1e-3)" +
36
-        f"c = texture2D(tex[{i}], texcF.xy).xyz;" for i in range(TEX_COUNT)
36
+        f"c *= texture2D(tex[{i}], texcF.xy).xyz;" for i in range(TEX_COUNT)
37 37
 ])}
38 38
     gl_FragColor = vec4((ambient + diffuse + specular) * c, colF.w);
39 39
 }}
40 40
 """
41 41
 
42 42
 def load_texture(i, img_data):
43
-    if isinstance(img_data, str):
44
-        pli = pygame.image.load(img_data).convert()
45
-        (tw, th) = pli.get_size()
46
-        img_data = np.array(list(pygame.image.tobytes(pli, "RGB")), np.uint8)
47
-    elif isinstance(img_data, np.ndarray):
48 43
     (tw, th) = img_data.shape[:2]
49 44
     tex = glGenTextures(1)
50 45
     glActiveTexture(GL_TEXTURE0 + i)
... ...
@@ -57,10 +52,9 @@ def load_texture(i, img_data):
57 52
 
58 53
 def init_display():
59 54
     pygame.init()
60
-    full = os.environ.get("FULLSCREEN")
61 55
     pygame.display.set_mode((640, 480),
62
-        pygame.DOUBLEBUF | pygame.OPENGL |
63
-        (pygame.FULLSCREEN | pygame.SCALED if full else pygame.RESIZABLE),
56
+        pygame.DOUBLEBUF | pygame.OPENGL | (pygame.FULLSCREEN | pygame.SCALED
57
+            if conf["maximised"] else pygame.RESIZABLE),
64 58
     vsync=1)
65 59
     pygame.event.set_allowed([
66 60
         pygame.KEYDOWN, pygame.KEYUP,
... ...
@@ -68,8 +62,6 @@ def init_display():
68 62
         pygame.QUIT, pygame.WINDOWRESIZED, pygame.WINDOWSIZECHANGED
69 63
     ])
70 64
     pygame.display.set_caption("Voxels")
71
-    load_texture(0, "favicon.png")
72
-    load_texture(1, "container.jpg")
73 65
     sp = shaders.compileProgram(
74 66
         shaders.compileShader(VS, GL_VERTEX_SHADER),
75 67
         shaders.compileShader(FS, GL_FRAGMENT_SHADER)
... ...
@@ -34,12 +34,12 @@ def selection():
34 34
 
35 35
 def skybox():
36 36
     sb = mesh.SquareMesh()
37
-    offset = [0, 0, VIEW_DIST]
37
+    offset = [0, 0, conf["view-dist"]]
38 38
     for d in range(3):
39 39
         roll_vec3(offset, 1)
40 40
         for s in range(2):
41 41
             sf = (-1) ** s
42
-            sb.add_sq((d + 1) * sf, 2 * VIEW_DIST, sf * vec(offset), 2)
42
+            sb.add_sq((d + 1) * sf, 2 * conf["view-dist"], sf * vec(offset), 2)
43 43
             i = 6 * (2 * d + s) + 1
44 44
             for t in range(2):
45 45
                 vp = np.s_[i + 3 * t:i + 3 * t + 2]
... ...
@@ -48,7 +48,7 @@ def skybox():
48 48
 
49 49
 def sun():
50 50
     s = attributes.AttributedEntity(
51
-        [[0.0, 0.0, 0.0]], normalise(LIGHT_POS) * VIEW_DIST,
51
+        [[0.0, 0.0, 0.0]], normalise(LIGHT_POS) * conf["view-dist"],
52 52
         [1.0, 1.0, 0.9, 1.0], texi=-1, mode=GL_TRIANGLE_FAN
53 53
     )
54 54
     cv = []
... ...
@@ -4,12 +4,7 @@ import numpy as np
4 4
 import pygame
5 5
 
6 6
 TEX_COUNT = 7
7
-TEX_RES = 64
8 7
 LIGHT_POS = [5, 30, 10]
9
-REGION_SIZE = 8
10
-GRAVITY = 1
11
-VIEW_DIST = 30
12
-FPS = 60
13 8
 ARROWS = [
14 9
     ["RIGHT", "e", "d"], ["LEFT", "a"],
15 10
     ["SPACE"], ["LSHIFT"],
... ...
@@ -17,8 +12,16 @@ ARROWS = [
17 12
 ]
18 13
 ARROWS = [[getattr(pygame, f"K_{n}") for n in opts] for opts in ARROWS]
19 14
 
15
+conf = {
16
+    "fps": 60,
17
+    "gravity": 1.0,
18
+    "maximised": False,
19
+    "region": 8,
20
+    "tex-res": 32,
21
+    "view-dist": 30.0,
22
+}
23
+
20 24
 FLOAT = np.float32
21
-VOXEL_TYPE = np.uint8
22 25
 VERTEX_ATTRS = [
23 26
     "vec3 position", "vec4 col", "vec3 normal", "vec4 refls", "vec3 texc"
24 27
 ]
... ...
@@ -60,7 +63,6 @@ def smerp(corners, weights):
60 63
     return corners[0]
61 64
 
62 65
 def norm_tri(verts):
63
-    assert verts.shape[0] == 3
64 66
     return normalise(np.cross(verts[2] - verts[0], verts[1] - verts[0]))
65 67
 
66 68
 def update_attr(ent, n, l=None):
... ...
@@ -1,35 +1,47 @@
1 1
 from libs import *
2
+import argparse
2 3
 import display
3 4
 from mapgen import MapGen
4 5
 from player import Player
5 6
 
7
+TEX_PARAMS = [
8
+    ("debug", 0.1, (0.5, 0.5)), ("sky", 0.15, (0.15, 0.1)),
9
+    ("stone", 0.1, (0.2, 0.3)), ("grass", 0.15, (0.15, 0.12)),
10
+    ("bark", 0.2, (0.04, 0.25)), ("leaves", 0.4, (0.2, 0.3)),
11
+]
12
+
6 13
 def debug_mode(mg, cam):
7 14
     pass
8 15
 
9
-def build_texture(i, mg, name, mid, offset, period):
10
-    display.load_texture(i, mg.make_texture(
11
-        name, (TEX_RES, TEX_RES), mid, offset, period
12
-    ))
13
-
14 16
 def in_view(wr, cam):
15 17
     rwrc = wr.pos + wr.r / 2 - cam.pos
16
-    close = np.linalg.norm(rwrc) < VIEW_DIST
18
+    close = np.linalg.norm(rwrc) < conf["view-dist"]
17 19
     ld = cam.looking()
18 20
     cv = rwrc + 8 * ld
19 21
     frust = np.dot(cv, ld) / np.linalg.norm(cv) > 0.5
20 22
     return close and frust
21 23
 
22 24
 def main():
25
+    parser = argparse.ArgumentParser()
26
+    for (k, v) in conf.items():
27
+        parser.add_argument(f"-{k[0]}", f"--{k}", type=type(v))
28
+    args = parser.parse_args()
29
+    for k in conf:
30
+        v = getattr(args, k.replace("-", "_"))
31
+        if v is not None:
32
+            conf[k] = v
23 33
     display.init_display()
24 34
     glUniform4fv(UNIFORMS["lightPos"][2], 1, vec([*LIGHT_POS, 100]))
25 35
     mg = MapGen()
26
-    build_texture(2, mg, "sky", [0.5, 0.5, 0.8], [0.4, 0.4, 0.1], (20, 8))
27
-    build_texture(3, mg, "stone", [0.7, 0.7, 0.8], [0.1, 0.1, 0.1], (12, 15))
28
-    build_texture(4, mg, "grass", [0.3, 0.6, 0.3], [0.2, 0.1, 0.2], (3, 5))
29
-    build_texture(5, mg, "bark", [0.2, 0.1, 0.0], [0.1, 0.1, 0.0], (2, 8))
30
-    build_texture(6, mg, "leaves", [0.2, 0.5, 0.2], [0.2, 0.4, 0.2], (10, 14))
36
+    tr = conf["tex-res"]
37
+    for (i, p) in enumerate(TEX_PARAMS):
38
+        v = [0.2, 0.2, 0.0] if i == 0 else [p[1]] * 3
39
+        m = [1 - 1.5 * p[1]] * 3
40
+        display.load_texture(i + 1, mg.make_texture(
41
+            p[0], (tr, tr), m, v, np.multiply(tr, p[2])
42
+        ))
31 43
     clock = pygame.time.Clock()
32
-    cam = Player(speed=6/FPS)
44
+    cam = Player(speed=6/conf["fps"])
33 45
     cam.pos = [4, 8, 4]
34 46
     cam.set_focus(True)
35 47
     while True:
... ...
@@ -57,9 +69,9 @@ def main():
57 69
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
58 70
         for ent in scene:
59 71
             cam.draw(ent)
60
-        perf = clock.get_fps() / FPS
72
+        perf = clock.get_fps() / conf["fps"]
61 73
         cam.draw_hud(scene, perf)
62 74
         pygame.display.flip()
63
-        clock.tick(FPS)
75
+        clock.tick(conf["fps"])
64 76
 
65 77
 main()
... ...
@@ -49,7 +49,8 @@ class MapGen(world.WorldHolder):
49 49
                     wr.grid[i, :min(h, wr.r), j] = 3
50 50
                 if h < wr.r and h >= 0:
51 51
                     wr.grid[i, h, j] = 4
52
-                    if 2 <= i < wr.r - 2 and 2 <= j < wr.r - 2 and self.poisson(
52
+                    inner = 2 <= i < wr.r - 2 and 2 <= j < wr.r - 2
53
+                    if inner and self.poisson(
53 54
                             "trees", wr.pos[[0, 2]], wr.r, (6, 6)
54 55
                         )[i, j] >= 1:
55 56
                         for u in range(1, 6):
... ...
@@ -58,32 +59,15 @@ class MapGen(world.WorldHolder):
58 59
                             for v in range(-7 + u, 8 - u):
59 60
                                 b = 7 - u - abs(v)
60 61
                                 for w in range(-b, b + 1):
61
-                                    self.voxel_at(wr.pos + [i + v, h + u, j + w], 6)
62
-        return n
63
-
64
-    def world_min(self, wr):
65
-        wr.grid[0, 0, 0] = 1
66
-        return 1
67
-
68
-    def world_grid(self, wr):
69
-        n = 0
70
-        for i in range(0, wr.r, 2):
71
-            for j in range(0, wr.r, 2):
72
-                for k in range(0, wr.r, 2):
73
-                    wr.grid[i, j, k] = 1
74
-                    n += 1
75
-        return n
76
-
77
-    def world_checker(self, wr):
78
-        n = 0
79
-        for i in range(wr.r):
80
-            for j in range(wr.r):
81
-                for k in range(wr.r):
82
-                    if not (i + j + k) % 2:
83
-                        wr.grid[i, j, k] = 1
84
-                        n += 1
62
+                                    self.voxel_at(
63
+                                        wr.pos + [i + v, h + u, j + w], 6
64
+                                    )
85 65
         return n
86 66
 
87 67
     def make_texture(self, name, dims, mid, offset, period):
88
-        mid, offset = vec(mid), vec(offset)
89
-        return np.multiply(256, [[mid + offset * self.perlin(name, vec((i, j)), period) for j in range(dims[1])] for i in range(dims[0])], dtype=FLOAT)
68
+        m, o = vec(mid), vec(offset)
69
+        ta = np.empty(dims + (3,))
70
+        for i in range(dims[1]):
71
+            for j in range(dims[0]):
72
+                ta[i, j] = m + o * self.perlin(name, vec((i, j)), period)
73
+        return np.multiply(255, ta, dtype=FLOAT)
... ...
@@ -38,11 +38,11 @@ class SquareMesh(attributes.AttributedEntity):
38 38
                 return (vec([0.0, 0.0, 0.0, 0.0]), vec([1.0, 0.0, 0.0, 1.0]))
39 39
             case 1:
40 40
                 return (
41
-                    vec([*np.add(0.5, axis2offset(axis, 0.5)), 1.0]),
41
+                    vec([*np.add(0.5, axis2offset(axis, 0.4)), 1.0]),
42 42
                     vec([1.0, 0.0, 0.1, 128.0])
43 43
                 )
44 44
             case 2:
45
-                top = (vec([0.2, 0.2, 1.0, 1.0]), vec([1.0, 0.0, 0.0, 1.0]))
45
+                top = (vec([0.8, 0.8, 1.5, 1.0]), vec([1.0, 0.0, 0.0, 1.0]))
46 46
                 bottom = (vec([0.0, 0.0, 0.2, 1.0]), vec([1.0, 0.0, 0.0, 1.0]))
47 47
                 match axis:
48 48
                     case 2:
... ...
@@ -52,7 +52,7 @@ class SquareMesh(attributes.AttributedEntity):
52 52
                     case a:
53 53
                         return bottom if corner & (2 >> face_orient) else top
54 54
             case 3:
55
-                return (vec([0.7, 0.7, 0.8, 1.0]), vec([0.5, 0.5, 0.01, 2.0]))
55
+                return (vec([0.5, 0.5, 0.6, 1.0]), vec([0.5, 0.5, 0.01, 16.0]))
56 56
             case 4:
57 57
                 top = (vec([0.2, 0.6, 0.2, 1.0]), vec([0.6, 0.4, 0.0, 1.0]))
58 58
                 bottom = (vec([0.5, 0.2, 0.0, 1.0]), vec([0.8, 0.2, 0.0, 1.0]))
... ...
@@ -73,7 +73,7 @@ class SquareMesh(attributes.AttributedEntity):
73 73
                     case _:
74 74
                         return bark
75 75
             case 6:
76
-                return (vec([0.0, 0.4, 0.0, 0.5]), vec([0.6, 0.4, 0.01, 4.0]))
76
+                return (vec([0.2, 0.6, 0.2, 0.5]), vec([0.6, 0.4, 0.01, 4.0]))
77 77
 
78 78
     def mat_list(self):
79 79
         cl = np.empty((2, 3 * len(self.faces), 4), dtype=FLOAT)
... ...
@@ -22,7 +22,7 @@ class Player(interactive.InteractiveCamera):
22 22
                 cd = 0.3 * sf * self.vaa_mat()[i]
23 23
                 hit = self.collides and mg.voxel_at(self.pos + cd)
24 24
                 if i == 1 and sf == -1 and self.falls and not hit:
25
-                    netv[i] = self.vaa_vel[i] - GRAVITY / FPS
25
+                    netv[i] = self.vaa_vel[i] - conf["gravity"] / conf["fps"]
26 26
                 if i != 1 or not self.falls or mg.voxel_at(self.pos - cd):
27 27
                     netv[i] += sf * any(pressed[j] for j in ARROWS[2 * i + s])
28 28
                 if hit:
... ...
@@ -4,9 +4,10 @@ import heapq
4 4
 import mesh
5 5
 
6 6
 class WorldRegion(mesh.SquareMesh):
7
-    def __init__(self, size=REGION_SIZE):
7
+    def __init__(self, size=0):
8 8
         super().__init__(refls=[0.5, 0.5, 0.5, 32.0])
9
-        self.grid = np.zeros((size, size, size), dtype=VOXEL_TYPE)
9
+        size = size or conf["region"]
10
+        self.grid = np.zeros((size, size, size), dtype=np.uint8)
10 11
         self.r = size
11 12
         self.dirty = False
12 13
 
... ...
@@ -22,7 +22,7 @@ class WorldHolder():
22 22
         try:
23 23
             r = next(iter(self.regions.values())).r
24 24
         except StopIteration:
25
-            r = REGION_SIZE
25
+            r = conf["region"]
26 26
         pr, pf = np.divmod(pos + 0.5, r)
27 27
         wr = self.fetch(tuple(int(r * c) for c in pr))
28 28
         it = tuple(int(c) for c in pf)
29 29