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 os
8import shutil
9import tempfile
10
11import numpy as np
12from PIL import Image
13
14from simvx.core import (
15 Camera3D,
16 Input,
17 InputMap,
18 Key,
19 Material,
20 Mesh,
21 MeshInstance3D,
22 Node,
23 Text2D,
24)
25from simvx.graphics import App
26
27
28def _make_checkerboard(size=64, block=8):
29 """Generate a checkerboard RGBA image."""
30 img = np.zeros((size, size, 4), dtype=np.uint8)
31 for y in range(size):
32 for x in range(size):
33 if ((x // block) + (y // block)) % 2 == 0:
34 img[y, x] = [255, 255, 255, 255]
35 else:
36 img[y, x] = [80, 80, 80, 255]
37 return img
38
39
40def _make_gradient(size=64):
41 """Generate a red-blue gradient RGBA image."""
42 img = np.zeros((size, size, 4), dtype=np.uint8)
43 for x in range(size):
44 t = x / (size - 1)
45 img[:, x] = [int(255 * (1 - t)), 0, int(255 * t), 255]
46 return img
47
48
49def _make_stripes(size=64, stripe_width=4):
50 """Generate horizontal stripes RGBA image."""
51 img = np.zeros((size, size, 4), dtype=np.uint8)
52 for y in range(size):
53 if (y // stripe_width) % 2 == 0:
54 img[y, :] = [50, 200, 50, 255]
55 else:
56 img[y, :] = [200, 200, 50, 255]
57 return img
58
59
60def _save_texture(pixels, path):
61 """Save RGBA numpy array as PNG."""
62 Image.fromarray(pixels).save(path)
63
64
65class TexturedCubesScene(Node):
66 def on_ready(self):
67 InputMap.add_action("quit", [Key.ESCAPE])
68
69 # Camera: looking down Y axis (proven setup)
70 self.add_child(Camera3D(position=(0, -15, 0), look_at=(0, 0, 0), up=(0, 0, 1)))
71
72 # Generate textures to temp files
73 self._tmp_dir = tempfile.mkdtemp(prefix="simvx_tex_")
74 tex_paths = []
75 for name, gen_fn in [
76 ("checker.png", _make_checkerboard),
77 ("gradient.png", _make_gradient),
78 ("stripes.png", _make_stripes),
79 ]:
80 path = os.path.join(self._tmp_dir, name)
81 _save_texture(gen_fn(), path)
82 tex_paths.append(path)
83
84 # Share one mesh across all cubes (required for single-batch rendering)
85 cube_mesh = Mesh.cube()
86 positions = [(-3, 0, 0), (0, 0, 0), (3, 0, 0)]
87
88 for pos, tex_path in zip(positions, tex_paths, strict=True):
89 mat = Material(colour=(1, 1, 1, 1), albedo_map=tex_path)
90 cube = MeshInstance3D(
91 mesh=cube_mesh,
92 material=mat,
93 position=pos,
94 )
95 self.add_child(cube)
96
97 # HUD
98 self.add_child(Text2D(text="Textured Cubes Demo", x=10, y=10, font_scale=1.5))
99
100 def on_process(self, dt):
101 if Input.is_action_just_pressed("quit"):
102 self.app.quit()
103 return
104 for child in self.children:
105 if isinstance(child, MeshInstance3D):
106 child.rotate_y(0.5 * dt)
107
108 def on_exit_tree(self):
109 shutil.rmtree(self._tmp_dir, ignore_errors=True)
110
111
112if __name__ == "__main__":
113 app = App(title="Textured Cubes", width=1280, height=720)
114 app.run(TexturedCubesScene())