- Notifications
You must be signed in to change notification settings - Fork 112
Expand file tree
/
Copy pathtext_overlay.py
More file actions
Latest commit
171 lines (137 loc) · 5.34 KB
/
Copy pathtext_overlay.py
File metadata and controls
171 lines (137 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""threepp text & SVG overlay — world-anchored labels + a 2D HUD, no assets.
python text_overlay.py # interactive (drag to orbit)
python text_overlay.py --shot out.png
Three lit objects, each tagged with a billboard TextSprite that floats above it
and always faces the camera. On top, a pixel-space HUD (an OrthographicCamera
rendered with auto_clear=False) draws a Text2D title and a small SVG badge.
Shows, all from Python:
* FontLoader.default_font() -> text with no font file
* TextSprite -> world-anchored labels that billboard
* Text2D + OrthographicCamera overlay (auto_clear=False) -> a 2D HUD
* SVGLoader.parse(...) -> an SVG turned into scene meshes
"""
importargparse
importmath
importos
importsys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
importthreeppastp
FONT=None# the embedded default font (loaded in main)
# a tiny inline SVG "badge": a teal rounded chevron made of two filled paths
LOGO_SVG= (
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">'
'<path d="M6,30 L18,8 L30,30 L24,30 L18,18 L12,30 Z" fill="#36e0c8"/>'
'<path d="M20,40 L32,18 L44,40 L38,40 L32,28 L26,40 Z" fill="#ffb030"/>'
'</svg>'
)
# (label, hex color, factory)
ITEMS= [
("Torus Knot", 0xffb030, lambda: tp.TorusKnotGeometry(0.55, 0.18, 160, 24)),
("Gem", 0x36e0c8, lambda: tp.IcosahedronGeometry(0.7)),
("Cube", 0xff5d8b, lambda: tp.BoxGeometry(1.1, 1.1, 1.1)),
]
deflabeled(name, color, geom):
"""A lit object with a TextSprite label floating above it."""
group=tp.Group()
mat=tp.MeshStandardMaterial()
mat.color=color
mat.metalness=0.25
mat.roughness=0.4
mesh=tp.Mesh(geom, mat)
mesh.cast_shadow=True
group.add(mesh)
label=tp.TextSprite(FONT, world_scale=0.34)
label.set_text(name)
label.set_color(0xffffff)
label.set_vertical_alignment(tp.VerticalAlignment.Above)
label.position.set(0, 1.0, 0)
group.add(label)
returngroup
defbuild_hud(w, h):
"""A pixel-space HUD scene + ortho camera (origin bottom-left, y up)."""
hud=tp.Scene()
cam=tp.OrthographicCamera(0, w, h, 0, -10, 10)
title=tp.Text2D(FONT, "threepp - text & SVG overlay", size=26)
title.set_color(0xeaf2ff)
title.position.set(24, h-48, 0)
hud.add(title)
sub=tp.Text2D(FONT, "TextSprite labels - Text2D HUD - SVGLoader badge", size=15)
sub.set_color(0x8aa0c0)
sub.position.set(24, h-74, 0)
hud.add(sub)
badge=tp.SVGLoader().parse(LOGO_SVG) # Group of filled meshes (y already flipped)
badge.scale.set(1.6, -1.6, 1.6) # keep the loader's y-flip, scale up
badge.position.set(w-110, 92, 0)
hud.add(badge)
returnhud, cam
defmain():
globalFONT
ap=argparse.ArgumentParser()
ap.add_argument("--shot", metavar="PNG", help="render one frame headless and save")
args=ap.parse_args()
headless=bool(args.shot)
w, h=1100, 620
canvas=tp.Canvas("threepp - text & SVG overlay", width=w, height=h,
antialiasing=4, headless=headless)
renderer=tp.GLRenderer(canvas)
renderer.shadow_map_enabled=True
renderer.tone_mapping=tp.ToneMapping.ACESFilmic
FONT=tp.FontLoader().default_font()
scene=tp.Scene()
scene.background=tp.Background(0x0d0f15)
scene.add(tp.HemisphereLight(0xbcd0ff, 0x202028, 0.7))
key=tp.DirectionalLight(0xffffff, 2.4)
key.position.set(4, 8, 6)
key.cast_shadow=True
scene.add(key)
rim=tp.DirectionalLight(0x4060ff, 1.0)
rim.position.set(-6, 2, -4)
scene.add(rim)
floor=tp.Mesh(tp.PlaneGeometry(60, 60), tp.MeshStandardMaterial())
floor.material.color=0x10131b
floor.material.roughness=0.6
floor.rotate_x(-math.pi/2)
floor.position.y=-1.15
floor.receive_shadow=True
scene.add(floor)
objects=tp.Group()
fori, (name, color, factory) inenumerate(ITEMS):
node=labeled(name, color, factory())
node.position.x= (i-1) *3.4
objects.add(node)
scene.add(objects)
camera=tp.PerspectiveCamera(50, canvas.aspect(), 0.1, 100)
camera.position.set(0, 1.7, 9.0)
camera.look_at(0, 0.4, 0)
hud, hud_cam=build_hud(w, h)
defrender():
renderer.auto_clear=True
renderer.render(scene, camera) # main 3D pass (clears)
renderer.auto_clear=False# overlay on top, don't clear
renderer.render(hud, hud_cam) # 2D HUD pass
ifheadless:
for_inrange(2):
render()
renderer.save_frame(args.shot)
print(f"saved {args.shot}")
return
controls=tp.OrbitControls(camera, canvas)
controls.enable_damping=True
controls.target=tp.Vector3(0, 0.4, 0)
controls.auto_rotate=True
controls.auto_rotate_speed=0.5
defon_resize(width, height):
camera.aspect=width/max(height, 1)
camera.update_projection_matrix()
renderer.set_size(width, height)
canvas.on_window_resize(on_resize)
clock=tp.Clock()
defanimate():
dt=clock.get_delta()
fork, nodeinenumerate(objects.children):
node.children[0].rotation.y+=dt* (0.5+0.15*k)
controls.update()
render()
canvas.animate(animate)
if__name__=="__main__":
main()