Textured cubes demo¶
generates checkerboard textures and renders them.
▶ Run in browserTags: 3d
Usage: uv run python examples/features/3d/textured_cubes.py
Source¶
1"""Textured cubes demo: generates checkerboard textures and renders them.
2
3Usage:
4 uv run python examples/features/3d/textured_cubes.py
5"""
6
7import numpy as np
8
9from simvx.core import (
10 Camera3D,
11 DirectionalLight3D,
12 Input,
13 InputMap,
14 Key,
15 Material,
16 Mesh,
17 MeshInstance3D,
18 Node,
19 Text2D,
20 WorldEnvironment,
21)
22from simvx.graphics import App
23
24
25def _make_checkerboard(size=64, block=8):
26 """Generate a checkerboard RGBA image."""
27 img = np.zeros((size, size, 4), dtype=np.uint8)
28 for y in range(size):
29 for x in range(size):
30 if ((x // block) + (y // block)) % 2 == 0:
31 img[y, x] = [255, 255, 255, 255]
32 else:
33 img[y, x] = [80, 80, 80, 255]
34 return img
35
36
37def _make_gradient(size=64):
38 """Generate a red-blue gradient RGBA image."""
39 img = np.zeros((size, size, 4), dtype=np.uint8)
40 for x in range(size):
41 t = x / (size - 1)
42 img[:, x] = [int(255 * (1 - t)), 0, int(255 * t), 255]
43 return img
44
45
46def _make_stripes(size=64, stripe_width=4):
47 """Generate horizontal stripes RGBA image."""
48 img = np.zeros((size, size, 4), dtype=np.uint8)
49 for y in range(size):
50 if (y // stripe_width) % 2 == 0:
51 img[y, :] = [50, 200, 50, 255]
52 else:
53 img[y, :] = [200, 200, 50, 255]
54 return img
55
56
57class TexturedCubesScene(Node):
58 def on_ready(self):
59 InputMap.add_action("quit", [Key.ESCAPE])
60
61 # Camera: close enough that the textures are clearly readable
62 self.add_child(Camera3D(position=(0, -7, 2), look_at=(0, 0, 0), up=(0, 0, 1)))
63
64 # Environment ambient + key/fill lights so the texture detail reads
65 # clearly instead of sitting dim against a black void.
66 env = self.add_child(WorldEnvironment())
67 env.ambient_light_energy = 0.5
68
69 key = DirectionalLight3D(name="KeyLight", intensity=1.5)
70 key.look_at((-1.0, -2.0, -1.0))
71 self.add_child(key)
72
73 fill = DirectionalLight3D(name="FillLight", intensity=0.4, colour=(0.6, 0.7, 1.0))
74 fill.look_at((1.0, -1.0, 2.0))
75 self.add_child(fill)
76
77 # Procedural RGBA textures passed directly as numpy arrays. Material
78 # accepts an ndarray albedo_map, so there is no PNG/PIL round-trip and
79 # the demo works unchanged in a web export (no pillow dependency).
80 textures = [_make_checkerboard(), _make_gradient(), _make_stripes()]
81
82 # Share one mesh across all cubes (required for single-batch rendering)
83 cube_mesh = Mesh.cube()
84 positions = [(-3, 0, 0), (0, 0, 0), (3, 0, 0)]
85
86 for pos, tex in zip(positions, textures, strict=True):
87 mat = Material(colour=(1, 1, 1, 1), albedo_map=tex)
88 cube = MeshInstance3D(
89 mesh=cube_mesh,
90 material=mat,
91 position=pos,
92 )
93 self.add_child(cube)
94
95 # HUD
96 self.add_child(Text2D(text="Textured Cubes Demo", position=(10, 10), font_scale=1.5))
97 self.add_child(Text2D(text="Esc: quit", position=(10, 44)))
98
99 def on_update(self, dt):
100 if Input.is_action_just_pressed("quit"):
101 self.app.quit()
102 return
103 for child in self.children:
104 if isinstance(child, MeshInstance3D):
105 child.rotate_y(0.5 * dt)
106
107if __name__ == "__main__":
108 app = App(title="Textured Cubes", width=1280, height=720)
109 app.run(TexturedCubesScene())