Node tree text demo¶
3D cubes with text textures + 2D text overlay + Japanese text.
▶ Run in browserTags: 3d
Validates:
Node tree → SceneAdapter → Renderer pipeline
Camera3D view/projection matrices (row-major, Y-flipped)
MeshInstance3D model_matrix with quaternion rotation
Text2D overlay bridged to MSDF text renderer
Lazy glyph loading for Japanese characters
Text-as-texture on 3D geometry via Engine.create_text_texture()
Controls: ESC: Quit
Run with: uv run python examples/features/3d/text.py
Source¶
1#!/usr/bin/env python3
2"""Node tree text demo: 3D cubes with text textures + 2D text overlay + Japanese text.
3
4Validates:
5 - Node tree → SceneAdapter → Renderer pipeline
6 - Camera3D view/projection matrices (row-major, Y-flipped)
7 - MeshInstance3D model_matrix with quaternion rotation
8 - Text2D overlay bridged to MSDF text renderer
9 - Lazy glyph loading for Japanese characters
10 - Text-as-texture on 3D geometry via Engine.create_text_texture()
11
12Controls:
13 ESC: Quit
14
15Run with:
16 uv run python examples/features/3d/text.py
17"""
18
19import math
20
21from simvx.core import (
22 Camera3D,
23 DirectionalLight3D,
24 Input,
25 InputMap,
26 Key,
27 Material,
28 Mesh,
29 MeshInstance3D,
30 Node,
31 Text2D,
32 Vec3,
33)
34from simvx.graphics import App
35
36
37class RotatingCube(MeshInstance3D):
38 """A cube that rotates around its Y axis."""
39
40 def __init__(self, speed: float = 45.0, **kwargs):
41 super().__init__(**kwargs)
42 self.speed = speed
43
44 def on_process(self, dt: float):
45 self.rotate_y(math.radians(self.speed) * dt)
46
47
48class TextDemoScene(Node):
49 """Main demo scene with labeled cubes and text overlays."""
50
51 def on_ready(self):
52 InputMap.add_action("escape", [Key.ESCAPE])
53
54 engine = self.app.engine
55
56 # Camera: positioned to see cubes at origin
57 camera = self.add_child(
58 Camera3D(
59 name="Camera",
60 position=Vec3(0, 3, 8),
61 )
62 )
63 camera.look_at(Vec3(0, 0, 0))
64 camera.fov = 50.0
65
66 light = self.add_child(DirectionalLight3D(name="Sun"))
67 light.look_at(Vec3(-1, -2, -1))
68
69 # Create text textures for each cube label
70 labels = [
71 ("RED", (1.0, 0.3, 0.3, 1.0)),
72 ("GREEN", (0.3, 1.0, 0.4, 1.0)),
73 ("BLUE", (0.4, 0.5, 1.0, 1.0)),
74 ]
75 text_textures = []
76 for label, colour in labels:
77 tt = engine.create_text_texture(size=48, width=256, height=64)
78 tt.colour = colour
79 tt.text = label
80 text_textures.append(tt)
81
82 # Red cube: left, rotating slowly
83 red = self.add_child(
84 RotatingCube(
85 name="RedCube",
86 position=Vec3(-2.5, 0, 0),
87 speed=30.0,
88 )
89 )
90 red.mesh = Mesh.cube(size=1.5)
91 red.material = Material(colour=(0.9, 0.2, 0.2, 1.0))
92 red.material.albedo_tex_index = text_textures[0].texture_index
93
94 # Green cube: center, rotating faster
95 green = self.add_child(
96 RotatingCube(
97 name="GreenCube",
98 position=Vec3(0, 0, 0),
99 speed=60.0,
100 )
101 )
102 green.mesh = Mesh.cube(size=1.5)
103 green.material = Material(colour=(0.2, 0.8, 0.3, 1.0))
104 green.material.albedo_tex_index = text_textures[1].texture_index
105
106 # Blue cube: right, counter-rotating
107 blue = self.add_child(
108 RotatingCube(
109 name="BlueCube",
110 position=Vec3(2.5, 0, 0),
111 speed=-45.0,
112 )
113 )
114 blue.mesh = Mesh.cube(size=1.5)
115 blue.material = Material(colour=(0.2, 0.4, 0.9, 1.0))
116 blue.material.albedo_tex_index = text_textures[2].texture_index
117
118 # --- 2D text overlays ---
119 self.add_child(
120 Text2D(
121 name="Title",
122 text="SimVX Node Tree Demo",
123 x=10.0,
124 y=10.0,
125 font_scale=2.0,
126 colour=(1.0, 1.0, 1.0, 1.0),
127 )
128 )
129
130 self.add_child(
131 Text2D(
132 name="Subtitle",
133 text="3 rotating cubes with text textures + MSDF overlay",
134 x=10.0,
135 y=50.0,
136 font_scale=1.2,
137 colour=(0.71, 0.71, 0.71, 1.0),
138 )
139 )
140
141 self.add_child(
142 Text2D(name="Hiragana", text="ひらがな: あいうえお かきくけこ",
143 x=10.0, y=90.0, font_scale=1.2, colour=(1.0, 0.59, 0.78, 1.0))
144 )
145 self.add_child(
146 Text2D(name="Katakana", text="カタカナ: アイウエオ カキクケコ",
147 x=10.0, y=120.0, font_scale=1.2, colour=(0.59, 0.78, 1.0, 1.0))
148 )
149 self.add_child(
150 Text2D(name="Kanji", text="漢字: 東京都 日本語テスト",
151 x=10.0, y=150.0, font_scale=1.2, colour=(1.0, 0.9, 0.39, 1.0))
152 )
153
154 # Dynamic frame counter
155 self.frame_text = self.add_child(
156 Text2D(
157 name="FrameCounter",
158 text="Frame: 0",
159 x=10.0,
160 y=190.0,
161 font_scale=1.0,
162 colour=(0.39, 1.0, 0.39, 1.0),
163 )
164 )
165
166 self.frame = 0
167
168 def on_process(self, dt: float):
169 self.frame += 1
170 self.frame_text.text = f"Frame: {self.frame} dt: {dt*1000:.1f}ms"
171
172 if Input.is_action_just_pressed("escape"):
173 self.app.quit()
174
175
176def main():
177 app = App(
178 title="SimVX Node Tree + Text Demo",
179 width=1280,
180 height=720,
181 )
182 app.run(TextDemoScene())
183
184
185if __name__ == "__main__":
186 main()