pub struct RenderableComponent {
pub renderable: Renderable,
pub authored_shape: Option<AuthoredRenderableShape>,
pub handle: Option<InstanceHandle>,
/* private fields */
}Expand description
Renderable component.
Fields§
§renderable: Renderable§handle: Option<InstanceHandle>VisualWorld instance handle created for this renderable.
Implementations§
Source§impl RenderableComponent
impl RenderableComponent
Sourcepub fn new(renderable: Renderable) -> Self
pub fn new(renderable: Renderable) -> Self
Examples found in repository?
6fn build_demo_scene_7_shapes(universe: &mut engine::Universe) {
7 use engine::ecs::component::{
8 Camera3DComponent, ColorComponent, EmissiveComponent, GLTFComponent, InputComponent,
9 InputTransformModeComponent, PointLightComponent, RenderableComponent, TextureComponent,
10 TransformComponent,
11 };
12 use engine::graphics::BuiltinMeshType;
13 use engine::graphics::primitives::MaterialHandle;
14
15 // Built-in CPU meshes are pre-registered; just fetch stable handles.
16 let tri_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Triangle2D);
17 let square_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Quad2D);
18 let tetra_mesh = universe
19 .render_assets
20 .get_mesh(BuiltinMeshType::Tetrahedron);
21
22 fn spawn(
23 universe: &mut engine::Universe,
24 mesh: engine::graphics::primitives::CpuMeshHandle,
25 x: f32,
26 y: f32,
27 s: f32,
28 r: f32,
29 color: [f32; 4],
30 input_driven: bool,
31 emissive: bool,
32 ) -> engine::ecs::ComponentId {
33 let transform = universe.world.add_component(
34 TransformComponent::new()
35 .with_position(x, y, 0.0)
36 .with_scale(s, s, 1.0)
37 .with_rotation_euler(0.0, 0.0, r),
38 );
39 let renderable = universe.world.add_component(RenderableComponent::new(
40 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
41 ));
42 let color_c = universe.world.add_component(ColorComponent { rgba: color });
43
44 if emissive {
45 let emissive_c = universe.world.add_component(EmissiveComponent::on());
46 let _ = universe.attach(renderable, emissive_c);
47 }
48
49 // Topology: (optional Input) -> Transform -> Renderable
50 let _ = universe.attach(transform, renderable);
51 let _ = universe.attach(renderable, color_c);
52
53 if input_driven {
54 let input = universe
55 .world
56 .add_component(InputComponent::new().with_speed(0.5));
57 let _ = universe.attach(input, transform);
58 universe.add(input);
59 } else {
60 universe.add(transform);
61 }
62
63 transform
64 }
65
66 fn spawn_3d(
67 universe: &mut engine::Universe,
68 mesh: engine::graphics::primitives::CpuMeshHandle,
69 x: f32,
70 y: f32,
71 z: f32,
72 s: f32,
73 rx: f32,
74 ry: f32,
75 rz: f32,
76 color: [f32; 4],
77 ) -> engine::ecs::ComponentId {
78 let transform = universe.world.add_component(
79 TransformComponent::new()
80 .with_position(x, y, z)
81 .with_scale(s, s, s)
82 .with_rotation_euler(rx, ry, rz),
83 );
84 let renderable = universe.world.add_component(RenderableComponent::new(
85 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
86 ));
87 let color_c = universe.world.add_component(ColorComponent { rgba: color });
88
89 let _ = universe.attach(transform, renderable);
90 let _ = universe.attach(renderable, color_c);
91 universe.add(transform);
92
93 transform
94 }
95
96 // Spawn shapes.
97 // One triangle is input-driven (WASD/QE). Build a small "rig" so both the triangle
98 // and the camera can be driven by the same InputComponent.
99
100 // Topology: Input -> (InputTransformMode) -> RigTransform -> (CameraTransform -> Camera3D), (TriRootTransform -> ...)
101 let tri_input = universe
102 .world
103 .add_component(InputComponent::new().with_speed(0.5));
104 let input_mode = universe
105 .world
106 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
107 let _ = universe.attach(tri_input, input_mode);
108
109 // Start pulled back so the demo meshes at z=0 are in view.
110 // The camera will be attached directly under this transform, so there is no local
111 // camera offset that would cause orbiting when yawing.
112 let rig_transform = universe
113 .world
114 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
115 let _ = universe.attach(tri_input, rig_transform);
116
117 // Camera: attached directly to the rig transform.
118 let camera3d = universe.world.add_component(Camera3DComponent::new());
119 let _ = universe.attach(rig_transform, camera3d);
120
121 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
122 example_util::spawn_desktop_camera_controls_hint(universe, rig_transform);
123
124 let tri_root_transform = universe
125 .world
126 .add_component(TransformComponent::new().with_position(0.5, 0.50, 0.0));
127
128 // Visual transform under the root; this is where we apply rotation/scale.
129 let tri_visual_transform = universe.world.add_component(
130 TransformComponent::new()
131 .with_scale(0.30, 0.30, 1.0)
132 .with_rotation_euler(0.0, 0.0, (2.0 * 3.14159 / 3.0) + 3.14159),
133 );
134 let tri_renderable = universe.world.add_component(RenderableComponent::new(
135 engine::graphics::primitives::Renderable::new(tri_mesh, MaterialHandle::TOON_MESH),
136 ));
137 let tri_color = universe
138 .world
139 .add_component(ColorComponent::rgba(0.2, 1.0, 0.2, 1.0));
140
141 let _ = universe.attach(rig_transform, tri_root_transform);
142 let _ = universe.attach(tri_root_transform, tri_visual_transform);
143 let _ = universe.attach(tri_visual_transform, tri_renderable);
144 let _ = universe.attach(tri_renderable, tri_color);
145
146 let tri_light = universe.world.add_component(
147 PointLightComponent::new()
148 .with_distance(10.0)
149 .with_color(1.0, 1.0, 1.0),
150 );
151
152 let light_transform = universe.world.add_component(
153 TransformComponent::new()
154 .with_position(0.5, 0.50, 1.0)
155 .with_scale(0.1, 0.1, 0.1),
156 );
157
158 let _ = universe.attach(light_transform, tri_light);
159
160 universe.add(tri_input);
161 universe.add(light_transform);
162
163 spawn(
164 universe,
165 square_mesh,
166 -0.80,
167 -0.30,
168 0.25,
169 0.0,
170 [1.0, 0.2, 0.2, 1.0],
171 false,
172 true,
173 );
174 spawn(
175 universe,
176 square_mesh,
177 -0.40,
178 -0.30,
179 0.25,
180 0.0,
181 [1.0, 0.6, 0.2, 1.0],
182 false,
183 true,
184 );
185
186 // 3D primitive: tetrahedron.
187 spawn_3d(
188 universe,
189 tetra_mesh,
190 0.55,
191 -0.15,
192 0.0,
193 0.35,
194 0.75,
195 0.55,
196 0.0,
197 [0.2, 0.7, 1.0, 1.0],
198 );
199 spawn(
200 universe,
201 square_mesh,
202 0.00,
203 -0.30,
204 0.25,
205 0.0,
206 [1.0, 1.0, 0.2, 1.0],
207 false,
208 true,
209 );
210 spawn(
211 universe,
212 square_mesh,
213 0.40,
214 -0.30,
215 0.25,
216 0.0,
217 [0.2, 0.6, 1.0, 1.0],
218 false,
219 true,
220 );
221 spawn(
222 universe,
223 square_mesh,
224 0.80,
225 -0.30,
226 0.25,
227 0.0,
228 [0.8, 0.2, 1.0, 1.0],
229 false,
230 true,
231 );
232 spawn(
233 universe,
234 tri_mesh,
235 0.30,
236 0.35,
237 0.30,
238 -3.14159,
239 [1.0, 1.0, 1.0, 1.0],
240 false,
241 false,
242 );
243
244 // Textured square.
245 let tex_transform = universe.world.add_component(
246 TransformComponent::new()
247 .with_position(0.0, 0.1, 0.0)
248 .with_scale(0.45, 0.45, 1.0),
249 );
250 let tex_renderable = universe.world.add_component(RenderableComponent::new(
251 engine::graphics::primitives::Renderable::new(square_mesh, MaterialHandle::TOON_MESH),
252 ));
253 let tex_color = universe
254 .world
255 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
256 let tex = universe.world.add_component(TextureComponent::from_dds(
257 "assets/textures/cat-face-amused.dds",
258 ));
259
260 let _ = universe.attach(tex_transform, tex_renderable);
261 let _ = universe.attach(tex_renderable, tex_color);
262 let _ = universe.attach(tex_renderable, tex);
263 universe.add(tex_transform);
264
265 // glTF: color-cat
266 // Attach GLTFComponent under a Transform so GLTFSystem can use it as an anchor.
267 let cat_anchor = universe.world.add_component(
268 TransformComponent::new()
269 .with_position(0.0, -0.10, -4.0)
270 .with_scale(0.50, 0.50, 0.50)
271 .with_rotation_euler(0.0, 0.0, 0.0),
272 );
273 let cat_gltf = universe
274 .world
275 .add_component(GLTFComponent::new("assets/models/color-cat.2.glb"));
276 let _ = universe.attach(cat_anchor, cat_gltf);
277 universe.add(cat_anchor);
278}More examples
62fn spawn_sun_background(universe: &mut engine::Universe) {
63 let bg_root = universe
64 .world
65 .add_component(engine::ecs::component::BackgroundComponent::new());
66 universe.add(bg_root);
67
68 let circle_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Circle2D);
69
70 // Big yellow disk.
71 let sun_t = universe.world.add_component(
72 TransformComponent::new()
73 .with_position(2.0, 1.5, -8.0)
74 .with_scale(3.5, 3.5, 3.5),
75 );
76 let sun_r = universe
77 .world
78 .add_component(RenderableComponent::new(Renderable::new(
79 circle_mesh,
80 MaterialHandle::TOON_MESH,
81 )));
82 let sun_color = universe
83 .world
84 .add_component(ColorComponent::rgba(1.0, 0.85, 0.15, 1.0));
85 let sun_emissive = universe.world.add_component(EmissiveComponent::on());
86
87 let _ = universe.attach(bg_root, sun_t);
88 let _ = universe.attach(sun_t, sun_r);
89 let _ = universe.attach(sun_r, sun_color);
90 let _ = universe.attach(sun_r, sun_emissive);
91
92 // Small white highlight disk.
93 let highlight_t = universe.world.add_component(
94 TransformComponent::new()
95 .with_position(-0.35, 0.35, -0.01)
96 .with_scale(0.45, 0.45, 0.45),
97 );
98 let highlight_r = universe
99 .world
100 .add_component(RenderableComponent::new(Renderable::new(
101 circle_mesh,
102 MaterialHandle::TOON_MESH,
103 )));
104 let highlight_color = universe
105 .world
106 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
107 let highlight_emissive = universe.world.add_component(EmissiveComponent::on());
108
109 let _ = universe.attach(sun_t, highlight_t);
110 let _ = universe.attach(highlight_t, highlight_r);
111 let _ = universe.attach(highlight_r, highlight_color);
112 let _ = universe.attach(highlight_r, highlight_emissive);
113}105pub fn spawn_cloud_ring(
106 universe: &mut engine::Universe,
107 bg_root: engine::ecs::ComponentId,
108 p: CloudRingParams,
109) {
110 if p.cloud_count == 0 || p.radius == 0.0 {
111 return;
112 }
113
114 let cube_mesh = universe
115 .render_assets
116 .get_mesh(engine::graphics::BuiltinMeshType::Cube);
117
118 let step = std::f32::consts::TAU / (p.cloud_count as f32);
119 let angle_jitter = p.angle_jitter.clamp(0.0, 1.0);
120 let high_y_probability = p.high_y_probability.clamp(0.0, 1.0);
121
122 for i in 0..p.cloud_count {
123 let seed_i = p.seed ^ i.wrapping_mul(0x9E37_79B9);
124 let jitter = (rand01(seed_i ^ 0xa53a_9d2d) - 0.5) * step * angle_jitter;
125 let a = (i as f32) * step + jitter;
126 let cx = p.radius * a.cos();
127 let cz = p.radius * a.sin();
128
129 let cy = if rand01(seed_i ^ 0x7f4a_7c15) < high_y_probability {
130 p.center_y * p.high_y_multiplier
131 } else {
132 p.center_y
133 };
134
135 let center_tx = universe.world.add_component(
136 engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
137 );
138 let _ = universe.attach(bg_root, center_tx);
139
140 let base_seed = seed_i ^ 0x243f_6a88;
141 for puff_i in 0..p.puffs_per_cloud {
142 let seed = base_seed ^ puff_i.wrapping_mul(1_103_515_245);
143
144 let ox = (rand01(seed ^ 0x68bc_21eb) - 0.5) * 9.0;
145 let oy = (rand01(seed ^ 0x02e5_be93) - 0.5) * 4.0;
146 let oz = (rand01(seed ^ 0xa1d3_4f2b) - 0.5) * 9.0;
147
148 let base = 0.7 + rand01(seed ^ 0x9e37_79b9) * 2.8;
149 let sx = base * (0.7 + rand01(seed ^ 0x243f_6a88) * 0.9);
150 let sy = base * (0.6 + rand01(seed ^ 0x85a3_08d3) * 1.0);
151 let sz = base * (0.7 + rand01(seed ^ 0x1319_8a2e) * 0.9);
152
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(ox, oy, oz)
156 .with_scale(sx, sy, sz),
157 );
158 let renderable =
159 universe
160 .world
161 .add_component(engine::ecs::component::RenderableComponent::new(
162 engine::graphics::primitives::Renderable::new(
163 cube_mesh,
164 engine::graphics::primitives::MaterialHandle::TOON_MESH,
165 ),
166 ));
167
168 let t = rand01(seed ^ 0x7f4a_7c15);
169 let r = 0.70 + 0.10 * t;
170 let g = 0.72 + 0.10 * t;
171 let b = 0.80 + 0.12 * t;
172 let color = universe
173 .world
174 .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
175
176 let _ = universe.attach(center_tx, tx);
177 let _ = universe.attach(tx, renderable);
178 let _ = universe.attach(renderable, color);
179 }
180 }
181}55 fn spawn_labeled_mesh(
56 universe: &mut engine::Universe,
57 x: f32,
58 y: f32,
59 label: &str,
60 mesh: engine::graphics::primitives::CpuMeshHandle,
61 scale: [f32; 3],
62 color: [f32; 4],
63 ) {
64 use engine::ecs::component::{
65 ActionComponent, AnimationComponent, AnimationState, ColorComponent, EmissiveComponent,
66 KeyframeComponent, RenderableComponent, TextComponent, TransformComponent,
67 };
68 use engine::graphics::primitives::{MaterialHandle, Renderable};
69
70 // Mesh.
71 let root = universe.world.add_component(
72 TransformComponent::new()
73 .with_position(x, y, 0.0)
74 .with_scale(scale[0], scale[1], scale[2]),
75 );
76
77 // Spin each shape around its own +Y axis using AnimationComponent + keyframes.
78 // We fill [0, 2) beats densely so it looks smooth.
79 let anim = universe
80 .world
81 .add_component(AnimationComponent::new().with_state(AnimationState::Looping));
82 let _ = universe.attach(root, anim);
83
84 let steps: usize = 64;
85 for i in 0..steps {
86 let beat = (i as f64) * (2.0 / (steps as f64));
87 let kf = universe.world.add_component(KeyframeComponent::new(beat));
88 let _ = universe.attach(anim, kf);
89
90 // Full turn over 2 beats.
91 let angle = (std::f64::consts::TAU * (beat / 2.0)) as f32;
92 let rotation = utils::math::quat_from_axis_angle([0.0, 1.0, 0.0], angle);
93
94 let action_cid = universe.world.add_component(ActionComponent::new(
95 engine::ecs::IntentValue::UpdateTransform {
96 component_ids: vec![root],
97 translation: [x, y, 0.0],
98 rotation_quat_xyzw: rotation,
99 scale,
100 },
101 ));
102 let _ = universe.attach(kf, action_cid);
103 }
104
105 let renderable = universe
106 .world
107 .add_component(RenderableComponent::new(Renderable::new(
108 mesh,
109 MaterialHandle::TOON_MESH,
110 )));
111 let color_c = universe
112 .world
113 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
114 let emissive = universe.world.add_component(EmissiveComponent::on());
115
116 let _ = universe.attach(root, renderable);
117 let _ = universe.attach(renderable, color_c);
118 let _ = universe.attach(renderable, emissive);
119
120 universe.add(root);
121
122 // Label (separate transform so we can scale text independently).
123 let text_root = universe.world.add_component(
124 TransformComponent::new()
125 .with_position(x, y + 0.75, 0.05)
126 .with_scale(0.09, 0.09, 1.0),
127 );
128 let text = universe
129 .world
130 .add_component(TextComponent::with_word_wrap_tokens(
131 label,
132 LABEL_WRAP_AT,
133 ["::", "(", ")", ",", "."],
134 ));
135 let text_color = universe
136 .world
137 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
138 let text_emissive = universe.world.add_component(EmissiveComponent::on());
139 let _ = universe.attach(text_root, text);
140 let _ = universe.attach(text, text_color);
141 let _ = universe.attach(text, text_emissive);
142 universe.add(text_root);
143 }6fn main() {
7 mittens_engine::example_support::ensure_model_assets();
8 utils::logger::init();
9
10 let world = engine::ecs::World::default();
11 let mut universe = engine::Universe::new(world);
12
13 let background = universe
14 .world
15 .add_component(engine::ecs::component::BackgroundColorComponent::new());
16 let background_c = universe
17 .world
18 .add_component(engine::ecs::component::ColorComponent::rgba(
19 0.3, 0.1, 1.0, 1.0,
20 ));
21 let _ = universe.world.add_child(background, background_c);
22 universe.add(background);
23
24 // --- Camera rig (WASD/QE) ---
25 // Keep this similar to the main demo so we can fly around the cube field.
26 let input = universe
27 .world
28 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
29 let input_mode = universe.world.add_component(
30 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
31 );
32 let _ = universe.attach(input, input_mode);
33
34 // Start pulled back so the grid is in view.
35 let rig_transform = universe.world.add_component(
36 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 4.0),
37 );
38 let _ = universe.attach(input, rig_transform);
39
40 let camera3d = universe
41 .world
42 .add_component(engine::ecs::component::Camera3DComponent::new());
43 let _ = universe.attach(rig_transform, camera3d);
44
45 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
46 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
47
48 // Simple point light so the toon shader reads well.
49 let light = universe.world.add_component(
50 engine::ecs::component::PointLightComponent::new()
51 .with_distance(50.0)
52 .with_color(1.0, 1.0, 1.0),
53 );
54 let light_transform = universe.world.add_component(
55 engine::ecs::component::TransformComponent::new().with_position(0.0, 5.0, 2.0),
56 );
57 let _ = universe.attach(light_transform, light);
58
59 universe.add(input);
60 universe.add(light_transform);
61
62 // --- 16x16x16 cube grid ---
63 let cube_mesh = universe
64 .render_assets
65 .get_mesh(engine::graphics::BuiltinMeshType::Cube);
66
67 let n: usize = 32;
68 let cube_scale: f32 = 0.10;
69 let gap: f32 = 0.50;
70 let step: f32 = cube_scale + gap;
71
72 // Center positions around 0 by subtracting half the extent (in steps).
73 let half_extent_x = (n as f32 - 1.0) * step * 0.5;
74 let half_extent_y = (n as f32 - 1.0) * step * 0.5;
75 let half_extent_z = (n as f32 - 1.0) * step * 0.5;
76
77 // Move the whole container up/back based on its content size, plus the requested offsets.
78 // - up by +0.5 and by half the content height
79 // - back by -(0.5 + 1.0) and by half the content depth
80 let container_offset_y = half_extent_y + 0.5;
81 let container_offset_z = -(half_extent_z + 1.0 + 0.5);
82
83 let container = universe.world.add_component(
84 engine::ecs::component::TransformComponent::new().with_position(
85 0.0,
86 container_offset_y,
87 container_offset_z,
88 ),
89 );
90
91 for z in 0..n {
92 for y in 0..n {
93 for x in 0..n {
94 let px = x as f32 * step - half_extent_x;
95 let py = y as f32 * step - half_extent_y;
96 let pz = z as f32 * step - half_extent_z;
97
98 let tx = universe.world.add_component(
99 engine::ecs::component::TransformComponent::new()
100 .with_position(px, py, pz)
101 .with_scale(cube_scale, cube_scale, cube_scale),
102 );
103 let renderable =
104 universe
105 .world
106 .add_component(engine::ecs::component::RenderableComponent::new(
107 engine::graphics::primitives::Renderable::new(
108 cube_mesh,
109 engine::graphics::primitives::MaterialHandle::TOON_MESH,
110 ),
111 ));
112
113 let denom = (n - 1) as f32;
114 let color = engine::ecs::component::ColorComponent::rgba(
115 f32::sin(x as f32 / 10.0),
116 y as f32 / denom,
117 z as f32 / denom,
118 1.0,
119 );
120 let color_c = universe.world.add_component(color);
121
122 let _ = universe.attach(container, tx);
123 let _ = universe.attach(tx, renderable);
124 let _ = universe.attach(renderable, color_c);
125 }
126 }
127 }
128
129 universe.add(container);
130 universe.systems.process_commands(
131 &mut universe.world,
132 &mut universe.visuals,
133 &mut universe.render_assets,
134 &mut universe.command_queue,
135 );
136
137 let xr_input = universe
138 .world
139 .add_component(engine::ecs::component::InputXRComponent::on());
140 let xr_gamepad = universe
141 .world
142 .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
143 let xr_head = universe
144 .world
145 .add_component(engine::ecs::component::TransformComponent::new());
146 let camera_xr = universe
147 .world
148 .add_component(engine::ecs::component::CameraXRComponent::on());
149 let _ = universe.attach(xr_input, xr_head);
150 let _ = universe.attach(xr_input, xr_gamepad);
151 let _ = universe.attach(xr_head, camera_xr);
152 universe.add(xr_input);
153
154 // Add an OpenXR component so OpenXRSystem initializes and starts polling events.
155 let xr_root = universe
156 .world
157 .add_component(engine::ecs::component::XrComponent::on());
158 universe.add(xr_root);
159 universe.systems.process_commands(
160 &mut universe.world,
161 &mut universe.visuals,
162 &mut universe.render_assets,
163 &mut universe.command_queue,
164 );
165
166 universe.enable_repl();
167 engine::Windowing::run_app(universe).expect("Windowing failed");
168}13fn build_gestures_and_gizmos_scene(universe: &mut engine::Universe) -> Scene {
14 use engine::ecs::component::{
15 BackgroundColorComponent, BackgroundComponent, Camera3DComponent, ColorComponent,
16 DirectionalLightComponent, InputComponent, InputTransformModeComponent, PointerComponent,
17 RaycastableComponent, RenderableComponent, TransformComponent, TransformGizmoComponent,
18 };
19 use engine::graphics::BuiltinMeshType;
20 use engine::graphics::primitives::{MaterialHandle, Renderable};
21
22 let tri_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Triangle2D);
23 let cube_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Cube);
24 let tetra_mesh = universe
25 .render_assets
26 .get_mesh(BuiltinMeshType::Tetrahedron);
27
28 // BackgroundColor { C.rgba }
29 let bg_color = universe
30 .world
31 .add_component(BackgroundColorComponent::new());
32 let bg_color_c = universe
33 .world
34 .add_component(ColorComponent::rgba(0.90, 0.90, 0.90, 1.0));
35 let _ = universe.world.add_child(bg_color, bg_color_c);
36 universe.add(bg_color);
37
38 // ambient light
39 let ambient = universe
40 .world
41 .add_component(AmbientLightComponent::rgb(0.25, 0.25, 0.25));
42 universe.add(ambient);
43
44 // ground plane
45 let ground_tx = universe.world.add_component(
46 TransformComponent::new()
47 .with_position(0.0, -2.5, 0.0)
48 .with_scale(20.0, 1.0, 20.0),
49 );
50 let ground_r = universe
51 .world
52 .add_component(RenderableComponent::new(Renderable::new(
53 universe.render_assets.get_mesh(BuiltinMeshType::Cube),
54 MaterialHandle::TOON_MESH,
55 )));
56 let ground_c = universe
57 .world
58 .add_component(ColorComponent::rgba(0.75, 0.75, 0.75, 1.0));
59 let _ = universe.attach(ground_tx, ground_r);
60 let _ = universe.attach(ground_r, ground_c);
61 let _ = universe.add(ground_tx);
62
63 // Background {
64 // with_occlusion_and_lighting()
65 // // using the example utils to add clouds to the background
66 // }
67 let bg_root = universe
68 .world
69 .add_component(BackgroundComponent::new().with_occlusion_and_lighting());
70 universe.add(bg_root);
71
72 // DirectionalLight {
73 // T { translate [1, 1, 1] }
74 // }
75 // Directional lights encode their direction in the node's world position.
76 let sun_t = universe
77 .world
78 .add_component(TransformComponent::new().with_position(1.0, 1.0, 1.0));
79 let sun = universe
80 .world
81 .add_component(DirectionalLightComponent::new());
82 let _ = universe.attach(sun_t, sun);
83 universe.add(sun_t);
84
85 // i = input
86 // t = transform
87 // c3d = camera3d
88 //
89 // I {
90 // T {
91 // C3D { with_fps_rotation().with_roll_axis_y() }
92 // }
93 // }
94 let input = universe
95 .world
96 .add_component(InputComponent::new().with_speed(2.5));
97 let input_mode = universe.world.add_component(
98 InputTransformModeComponent::forward_z()
99 .with_fps_rotation()
100 .with_roll_axis_y(),
101 );
102
103 let _ = universe.attach(input, input_mode);
104
105 // Forward is -Z, so put the camera at +Z looking toward the origin.
106 let rig_t = universe
107 .world
108 .add_component(TransformComponent::new().with_position(0.0, 0.0, 3.5));
109 let _ = universe.attach(input, rig_t);
110
111 let cam = universe
112 .world
113 .add_component(Camera3DComponent::new().with_far(600.0).with_fov(70.0));
114 let _ = universe.attach(rig_t, cam);
115
116 // Opt-in: treat this camera rig as a pointer source.
117 let pointer = universe.world.add_component(PointerComponent::new());
118 let _ = universe.attach(cam, pointer);
119
120 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
121 example_util::spawn_desktop_camera_controls_hint(universe, rig_t);
122
123 fn spawn_shape_with_gizmo(
124 universe: &mut engine::Universe,
125 mesh: engine::graphics::primitives::CpuMeshHandle,
126 pos: [f32; 3],
127 scale: [f32; 3],
128 rot_euler: [f32; 3],
129 color: [f32; 4],
130 ) {
131 let t = universe.world.add_component(
132 TransformComponent::new()
133 .with_position(pos[0], pos[1], pos[2])
134 .with_scale(scale[0], scale[1], scale[2])
135 .with_rotation_euler(rot_euler[0], rot_euler[1], rot_euler[2]),
136 );
137 let r = universe
138 .world
139 .add_component(RenderableComponent::new(Renderable::new(
140 mesh,
141 MaterialHandle::TOON_MESH,
142 )));
143 let c = universe
144 .world
145 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
146 let rc = universe
147 .world
148 .add_component(RaycastableComponent::enabled());
149 let g = universe.world.add_component(TransformGizmoComponent::new());
150
151 let _ = universe.attach(t, r);
152 let _ = universe.attach(r, c);
153 let _ = universe.attach(r, rc);
154 let _ = universe.attach(t, g);
155
156 universe.add(t);
157 }
158
159 fn spawn_shape_raycastable_no_gizmo(
160 universe: &mut engine::Universe,
161 mesh: engine::graphics::primitives::CpuMeshHandle,
162 pos: [f32; 3],
163 scale: [f32; 3],
164 rot_euler: [f32; 3],
165 color: [f32; 4],
166 ) {
167 let t = universe.world.add_component(
168 TransformComponent::new()
169 .with_position(pos[0], pos[1], pos[2])
170 .with_scale(scale[0], scale[1], scale[2])
171 .with_rotation_euler(rot_euler[0], rot_euler[1], rot_euler[2]),
172 );
173 let r = universe
174 .world
175 .add_component(RenderableComponent::new(Renderable::new(
176 mesh,
177 MaterialHandle::TOON_MESH,
178 )));
179 let c = universe
180 .world
181 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
182 let rc = universe
183 .world
184 .add_component(RaycastableComponent::enabled());
185
186 let _ = universe.attach(t, r);
187 let _ = universe.attach(r, c);
188 let _ = universe.attach(r, rc);
189
190 universe.add(t);
191 }
192
193 spawn_shape_with_gizmo(
194 universe,
195 tri_mesh,
196 [-1.2, 0.0, 0.0],
197 [0.65, 0.65, 0.65],
198 [0.0, 0.0, 0.0],
199 [0.2, 0.9, 0.25, 1.0],
200 );
201 spawn_shape_with_gizmo(
202 universe,
203 cube_mesh,
204 [0.0, 0.0, 0.0],
205 [0.55, 0.55, 0.55],
206 [0.0, 0.0, 0.0],
207 [0.95, 0.25, 0.2, 1.0],
208 );
209 spawn_shape_with_gizmo(
210 universe,
211 tetra_mesh,
212 [1.2, 0.0, 0.0],
213 [0.7, 0.7, 0.7],
214 [0.0, 0.0, 0.0],
215 [0.2, 0.55, 1.0, 1.0],
216 );
217
218 // Standalone tetrahedron: no gizmo, but explicitly raycastable.
219 // This helps isolate tetra picking vs gizmo-handle interception.
220 spawn_shape_raycastable_no_gizmo(
221 universe,
222 tetra_mesh,
223 [2.6, -0.6, 0.0],
224 [0.95, 0.95, 0.95],
225 [0.0, 0.0, 0.0],
226 [0.85, 0.85, 1.0, 1.0],
227 );
228
229 universe.add(input);
230
231 Scene { bg_root }
232}pub fn from_cpu_mesh_handle(h: CpuMeshHandle, material: MaterialHandle) -> Self
Sourcepub fn get_handle(&self) -> Option<InstanceHandle>
pub fn get_handle(&self) -> Option<InstanceHandle>
Examples found in repository?
422fn debug_print_selected_joint_influence(
423 universe: &engine::Universe,
424 mesh_key: &str,
425 model_root: engine::ecs::ComponentId,
426 selected: &[(usize, engine::ecs::ComponentId)],
427) {
428 let Some(renderable) = find_renderable_by_mesh_key(&universe.world, model_root, mesh_key)
429 else {
430 println!(
431 "[vtuber-joints-example] influence: mesh_key='{}' renderable not found",
432 mesh_key
433 );
434 return;
435 };
436
437 let renderable_handle = universe
438 .world
439 .get_component_by_id_as::<RenderableComponent>(renderable)
440 .and_then(|r| r.get_handle());
441 println!(
442 "[vtuber-joints-example] influence: mesh_key='{}' renderable={:?} instance_handle={:?}",
443 mesh_key, renderable, renderable_handle
444 );
445 let Some(skin_id) = find_skin_id_for_renderable(&universe.world, renderable) else {
446 println!(
447 "[vtuber-joints-example] influence: mesh_key='{}' has no skin_id",
448 mesh_key
449 );
450 return;
451 };
452 let Some(skin) = universe.visuals.skin(skin_id) else {
453 println!(
454 "[vtuber-joints-example] influence: skin_id={:?} missing in visuals",
455 skin_id
456 );
457 return;
458 };
459 let Some(cpu_h) = universe.render_assets.imported_mesh(mesh_key) else {
460 println!(
461 "[vtuber-joints-example] influence: mesh_key='{}' missing in RenderAssets (did meshes register?)",
462 mesh_key
463 );
464 return;
465 };
466 let Some(cpu) = universe.render_assets.cpu_mesh(cpu_h) else {
467 println!(
468 "[vtuber-joints-example] influence: mesh_key='{}' cpu mesh handle invalid",
469 mesh_key
470 );
471 return;
472 };
473 let (Some(joints0), Some(weights0)) = (cpu.joints0.as_ref(), cpu.weights0.as_ref()) else {
474 println!(
475 "[vtuber-joints-example] influence: mesh_key='{}' has no skin attributes",
476 mesh_key
477 );
478 return;
479 };
480
481 let joint_count = skin.joint_count();
482 let totals = compute_total_weight_per_joint(joints0, weights0, joint_count);
483
484 println!(
485 "[vtuber-joints-example] influence: mesh_key='{}' verts={} joints={}",
486 mesh_key,
487 cpu.vertices.len(),
488 joint_count
489 );
490
491 for &(node_index, joint_tx) in selected.iter() {
492 // Find the skin joint index that maps to this node.
493 let skin_joint_index = skin
494 .joint_node_indices
495 .iter()
496 .position(|&ni| ni == node_index);
497
498 let name = universe
499 .world
500 .get_component_record(joint_tx)
501 .map(|r| r.name.as_str())
502 .unwrap_or("<unknown>");
503
504 match skin_joint_index {
505 Some(j) => {
506 let total = totals.get(j).copied().unwrap_or(0.0);
507 println!(
508 " influence: name='{}' node_index={} skin_joint_index={} total_weight={:.3}",
509 name, node_index, j, total
510 );
511 }
512 None => {
513 println!(
514 " influence: name='{}' node_index={} skin_joint_index=<none>",
515 name, node_index
516 );
517 }
518 }
519 }
520}Sourcepub fn triangle_dynamic(render_assets: &mut RenderAssets) -> Self
pub fn triangle_dynamic(render_assets: &mut RenderAssets) -> Self
Predefined renderable: 2D triangle (unique CPU mesh registered into render_assets).
Sourcepub fn square() -> Self
pub fn square() -> Self
Predefined renderable: 2D square/quad (shared built-in mesh handle).
Examples found in repository?
16fn spawn_row(
17 universe: &mut engine::Universe,
18 scrolling: engine::ecs::ComponentId,
19 index: usize,
20) -> engine::ecs::ComponentId {
21 use engine::ecs::component::{
22 ColorComponent, RenderableComponent, TextComponent, TransformComponent,
23 };
24
25 let label = format!("row_{index:02}");
26 let row = universe.world.add_component_boxed_named(
27 label.clone(),
28 Box::new(TransformComponent::new().with_position(0.0, -(index as f32) * 1.15, 0.05)),
29 );
30 let panel = universe.world.add_component_boxed_named(
31 format!("{label}_panel"),
32 Box::new(
33 TransformComponent::new()
34 .with_position(2.3, -0.45, 0.0)
35 .with_scale(4.6, 0.9, 1.0),
36 ),
37 );
38 let panel_renderable = universe.world.add_component_boxed_named(
39 format!("{label}_rend"),
40 Box::new(RenderableComponent::square()),
41 );
42 let panel_color = universe
43 .world
44 .add_component(ColorComponent::rgba(0.98, 0.94, 0.78, 1.0));
45
46 let text_anchor = universe.world.add_component_boxed_named(
47 format!("{label}_text_anchor"),
48 Box::new(
49 TransformComponent::new()
50 .with_position(0.35, -0.38, 0.02)
51 .with_scale(0.11, 0.11, 0.11),
52 ),
53 );
54 let text = universe.world.add_component_boxed_named(
55 format!("{label}_text"),
56 Box::new(TextComponent::new(label.clone())),
57 );
58 let text_color = universe
59 .world
60 .add_component(ColorComponent::rgba(0.20, 0.16, 0.08, 1.0));
61
62 let _ = universe.world.add_child(row, panel);
63 let _ = universe.world.add_child(panel, panel_renderable);
64 let _ = universe.world.add_child(panel_renderable, panel_color);
65 let _ = universe.world.add_child(row, text_anchor);
66 let _ = universe.world.add_child(text_anchor, text);
67 let _ = universe.world.add_child(text, text_color);
68
69 universe.attach(scrolling, row).expect("attach scroll row");
70 row
71}More examples
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}30fn spawn_scroll_item(
31 universe: &mut engine::Universe,
32 scrolling: engine::ecs::ComponentId,
33 prefix: &str,
34 index: usize,
35 panel_rgba: [f32; 4],
36 text_rgba: [f32; 4],
37) -> engine::ecs::ComponentId {
38 use engine::ecs::component::{
39 ColorComponent, RenderableComponent, TextComponent, TransformComponent,
40 };
41
42 let label = format!("{prefix}_item_{index:02}");
43 let root = universe.world.add_component_boxed_named(
44 label.clone(),
45 Box::new(TransformComponent::new().with_position(0.0, -(index as f32) * 1.15, 0.05)),
46 );
47 let panel = universe.world.add_component_boxed_named(
48 format!("{label}_panel"),
49 Box::new(
50 TransformComponent::new()
51 .with_position(2.3, -0.45, 0.0)
52 .with_scale(4.6, 0.9, 1.0),
53 ),
54 );
55 let panel_renderable = universe.world.add_component_boxed_named(
56 format!("{label}_renderable"),
57 Box::new(RenderableComponent::square()),
58 );
59 let panel_color = universe.world.add_component(ColorComponent::rgba(
60 panel_rgba[0],
61 panel_rgba[1],
62 panel_rgba[2],
63 panel_rgba[3],
64 ));
65
66 let text_anchor = universe.world.add_component_boxed_named(
67 format!("{label}_text_anchor"),
68 Box::new(
69 TransformComponent::new()
70 .with_position(0.35, -0.38, 0.02)
71 .with_scale(0.11, 0.11, 0.11),
72 ),
73 );
74 let text = universe.world.add_component_boxed_named(
75 format!("{label}_text"),
76 Box::new(TextComponent::new(label.clone())),
77 );
78 let text_color = universe.world.add_component(ColorComponent::rgba(
79 text_rgba[0],
80 text_rgba[1],
81 text_rgba[2],
82 text_rgba[3],
83 ));
84
85 let _ = universe.world.add_child(root, panel);
86 let _ = universe.world.add_child(panel, panel_renderable);
87 let _ = universe.world.add_child(panel_renderable, panel_color);
88 let _ = universe.world.add_child(root, text_anchor);
89 let _ = universe.world.add_child(text_anchor, text);
90 let _ = universe.world.add_child(text, text_color);
91
92 universe
93 .attach(scrolling, root)
94 .expect("attach scroll item");
95 root
96}34fn main() {
35 mittens_engine::example_support::ensure_model_assets();
36 utils::logger::init();
37
38 let world = engine::ecs::World::default();
39 let mut universe = engine::Universe::new(world);
40
41 // Orange/yellow-ish clear color so cutout edges read.
42 let clear = universe
43 .world
44 .add_component(BackgroundColorComponent::new());
45 let clear_c = universe
46 .world
47 .add_component(ColorComponent::rgba(0.98, 0.72, 0.22, 1.0));
48 let _ = universe.world.add_child(clear, clear_c);
49 universe.add(clear);
50
51 // Warm-ish ambient so the gold cubes don’t go too dark.
52 let ambient = universe
53 .world
54 .add_component(AmbientLightComponent::rgb(0.22, 0.16, 0.08));
55 universe.add(ambient);
56
57 // --- Camera rig (WASD/QE) ---
58 let input = universe
59 .world
60 .add_component(InputComponent::new().with_speed(2.0));
61 let input_mode = universe
62 .world
63 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
64 let _ = universe.attach(input, input_mode);
65
66 // Start a bit pulled back, looking toward the origin.
67 let rig_transform = universe
68 .world
69 .add_component(TransformComponent::new().with_position(0.0, 0.0, 5.0));
70 let _ = universe.attach(input, rig_transform);
71
72 let camera3d = universe
73 .world
74 .add_component(Camera3DComponent::new().with_far(200.0).with_fov(55.0));
75 let _ = universe.attach(rig_transform, camera3d);
76
77 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
78 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
79 universe.add(input);
80
81 // Key light for toon shading.
82 let light = universe.world.add_component(
83 PointLightComponent::new()
84 .with_distance(50.0)
85 .with_intensity(2.2)
86 .with_color(1.0, 0.98, 0.92),
87 );
88 let light_transform = universe
89 .world
90 .add_component(TransformComponent::new().with_position(2.0, 3.0, 4.0));
91 let _ = universe.attach(light_transform, light);
92 universe.add(light_transform);
93
94 // --- Transparent cutout: 100 cat faces in a 10x10 grid ---
95 // Place them *behind* the starting camera position (camera starts at z=+5.0).
96 // Not attached to the camera rig.
97 let cat_grid_root = universe
98 .world
99 .add_component(TransformComponent::new().with_position(0.0, 0.0, 9.0));
100 universe.add(cat_grid_root);
101
102 let grid_w: i32 = 10;
103 let grid_h: i32 = 10;
104 let spacing: f32 = 0.7;
105 let half_w = (grid_w as f32 - 1.0) * spacing * 0.5;
106 let half_h = (grid_h as f32 - 1.0) * spacing * 0.5;
107
108 for y in 0..grid_h {
109 for x in 0..grid_w {
110 // Topology: cat_grid_root { T_quad { R_quad { Texture + Filtering + Cutout + Color } } }
111 let px = x as f32 * spacing - half_w;
112 let py = y as f32 * spacing - half_h;
113
114 let pz: f32 = (x as f32) % half_w;
115
116 let quad_t = universe.world.add_component(
117 TransformComponent::new()
118 .with_position(px, py, pz)
119 .with_scale(0.55, 0.55, 1.0),
120 );
121 let quad_r = universe.world.add_component(RenderableComponent::square());
122
123 let quad_tex = universe.world.add_component(TextureComponent::with_uri(
124 "assets/textures/cat-face-amused.dds",
125 ));
126 let quad_filtering = universe
127 .world
128 .add_component(TextureFilteringComponent::linear());
129 let quad_cutout = universe
130 .world
131 .add_component(TransparentCutoutComponent::new());
132 let quad_color = universe
133 .world
134 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
135
136 let _ = universe.attach(cat_grid_root, quad_t);
137 let _ = universe.attach(quad_t, quad_r);
138 let _ = universe.attach(quad_r, quad_tex);
139 let _ = universe.attach(quad_r, quad_filtering);
140 let _ = universe.attach(quad_r, quad_cutout);
141 let _ = universe.attach(quad_r, quad_color);
142 }
143 }
144
145 // --- Gold/yellow cubes behind the quad ---
146 // Parent them under a transform so it’s easy to tweak their depth.
147 let cubes_root = universe
148 .world
149 .add_component(TransformComponent::new().with_position(0.0, 0.0, 4.6));
150
151 // point light for cats
152 let cat_light_tx = universe
153 .world
154 .add_component(TransformComponent::new().with_position(0.0, 2.0, 7.0));
155
156 let cat_light = universe.world.add_component(
157 PointLightComponent::new()
158 .with_distance(150.0)
159 .with_intensity(1.5)
160 .with_color(1.0, 0.98, 0.92),
161 );
162 let _ = universe.attach(cat_light_tx, cat_light);
163 let _ = universe.attach(cubes_root, cat_light_tx);
164
165 universe.add(cubes_root);
166
167 let gold_a = (1.0, 0.86, 0.22);
168 let gold_b = (1.0, 0.74, 0.10);
169 let gold_c = (0.95, 0.92, 0.32);
170
171 // A loose cluster that’s visible through the cutout (transparent) area.
172 spawn_gold_cube(&mut universe, cubes_root, (-1.1, -0.3, -0.2), 0.45, gold_a);
173 spawn_gold_cube(&mut universe, cubes_root, (1.0, -0.4, -0.4), 0.40, gold_b);
174 spawn_gold_cube(&mut universe, cubes_root, (-0.2, 0.9, -0.6), 0.38, gold_c);
175 spawn_gold_cube(&mut universe, cubes_root, (0.7, 0.6, -0.9), 0.32, gold_a);
176 spawn_gold_cube(&mut universe, cubes_root, (-0.8, 0.4, -1.1), 0.36, gold_b);
177
178 // Bigger orange/yellow cubes behind the cluster (to make the cutout depth obvious).
179 let orange_gold_a = (1.0, 0.62, 0.10);
180 let orange_gold_b = (1.0, 0.78, 0.18);
181 spawn_gold_cube(
182 &mut universe,
183 cubes_root,
184 (0.0, -0.1, -2.1),
185 1.15,
186 orange_gold_b,
187 );
188 spawn_gold_cube(
189 &mut universe,
190 cubes_root,
191 (-1.8, 0.6, -2.6),
192 0.95,
193 orange_gold_a,
194 );
195 spawn_gold_cube(
196 &mut universe,
197 cubes_root,
198 (1.9, 0.7, -2.9),
199 1.05,
200 orange_gold_b,
201 );
202 spawn_gold_cube(
203 &mut universe,
204 cubes_root,
205 (0.9, 1.8, -3.2),
206 0.90,
207 orange_gold_a,
208 );
209 spawn_gold_cube(
210 &mut universe,
211 cubes_root,
212 (-0.9, 1.7, -3.5),
213 1.10,
214 orange_gold_b,
215 );
216
217 // Process init-time registrations (loads textures, registers renderables, etc.).
218 universe.systems.process_commands(
219 &mut universe.world,
220 &mut universe.visuals,
221 &mut universe.render_assets,
222 &mut universe.command_queue,
223 );
224
225 universe.enable_repl();
226 engine::Windowing::run_app(universe).expect("Windowing failed");
227}6fn main() {
7 mittens_engine::example_support::ensure_model_assets();
8 utils::logger::init();
9
10 let world = engine::ecs::World::default();
11 let mut universe = engine::Universe::new(world);
12
13 // Input-driven camera rig.
14 // Topology: I { T { C3D } }
15 let input = universe
16 .world
17 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18 let camera3d = universe
19 .world
20 .add_component(engine::ecs::component::Camera3DComponent::new());
21 let rig_transform = universe.world.add_component(
22 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23 );
24 let input_mode = universe.world.add_component(
25 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26 );
27 let _ = universe.attach(input, input_mode);
28 let _ = universe.attach(input, rig_transform);
29 let _ = universe.attach(rig_transform, camera3d);
30
31 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
32 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33 universe.add(input);
34
35 // Debug square: show the full font texture.
36 let debug_root = universe.world.add_component(
37 engine::ecs::component::TransformComponent::new()
38 .with_position(0.6, -0.2, 0.0)
39 .with_scale(0.8, 0.8, 1.0),
40 );
41 let debug_renderable = universe
42 .world
43 .add_component(engine::ecs::component::RenderableComponent::square());
44 let debug_tex =
45 universe
46 .world
47 .add_component(engine::ecs::component::TextureComponent::with_uri(
48 "assets/textures/font_system.dds",
49 ));
50 let debug_filtering = universe
51 .world
52 .add_component(engine::ecs::component::TextureFilteringComponent::nearest_magnification());
53 let _ = universe.attach(debug_root, debug_renderable);
54 let _ = universe.attach(debug_renderable, debug_tex);
55 let _ = universe.attach(debug_renderable, debug_filtering);
56 universe.add(debug_root);
57
58 // Light so we can actually see non-emissive materials.
59 let light_transform = universe.world.add_component(
60 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
61 );
62 let light = universe.world.add_component(
63 engine::ecs::component::PointLightComponent::new()
64 .with_distance(25.0)
65 .with_color(1.0, 1.0, 1.0),
66 );
67 let _ = universe.attach(light_transform, light);
68 universe.add(light_transform);
69
70 // 4 red cubes around the perimeter of the world (easy visual anchors).
71 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
72 let t = universe.world.add_component(
73 engine::ecs::component::TransformComponent::new()
74 .with_position(x, y, z)
75 .with_scale(s, s, s),
76 );
77 let r = universe
78 .world
79 .add_component(engine::ecs::component::RenderableComponent::cube());
80 let c = universe
81 .world
82 .add_component(engine::ecs::component::ColorComponent::rgba(
83 1.0, 0.0, 0.0, 1.0,
84 ));
85 let e = universe
86 .world
87 .add_component(engine::ecs::component::EmissiveComponent::on());
88
89 let _ = universe.attach(t, r);
90 let _ = universe.attach(r, c);
91 let _ = universe.attach(r, e);
92
93 universe.add(t);
94 }
95
96 let p = 1.5;
97 let s = 0.25;
98 spawn_red_cube(&mut universe, -p, -p, 0.0, s);
99 spawn_red_cube(&mut universe, p, -p, 0.0, s);
100 spawn_red_cube(&mut universe, -p, p, 0.0, s);
101 spawn_red_cube(&mut universe, p, p, 0.0, s);
102
103 use engine::ecs::component::{
104 ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
105 TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
106 };
107
108 fn spawn_text_style(
109 universe: &mut engine::Universe,
110 pos: [f32; 3],
111 scale: f32,
112 text: &str,
113 color: [f32; 4],
114 shadow: TextShadowComponent,
115 filtering: TextureFilteringComponent,
116 ) {
117 let root = universe.world.add_component(
118 TransformComponent::new()
119 .with_position(pos[0], pos[1], pos[2])
120 .with_scale(scale, scale, 1.0),
121 );
122
123 // Color must be an ancestor of the glyph renderables.
124 let color_id = universe
125 .world
126 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
127 let _ = universe.attach(root, color_id);
128
129 let text_id = universe.world.add_component(TextComponent::new(text));
130 let _ = universe.attach(color_id, text_id);
131
132 // Route into cutout pass for cleaner edges.
133 let cutout = universe
134 .world
135 .add_component(TransparentCutoutComponent::new());
136 let _ = universe.attach(text_id, cutout);
137
138 // Use the same atlas as the debug quad.
139 let tex = universe
140 .world
141 .add_component(TextureComponent::with_uri("assets/textures/font.dds"));
142 let _ = universe.attach(text_id, tex);
143
144 let shadow_id = universe.world.add_component(shadow);
145 let _ = universe.attach(text_id, shadow_id);
146
147 let filtering_id = universe.world.add_component(filtering);
148 let _ = universe.attach(text_id, filtering_id);
149
150 universe.add(root);
151 }
152
153 // Multiple text samples to show:
154 // - different inherited colors
155 // - different shadow settings
156 // - different texture filtering
157 spawn_text_style(
158 &mut universe,
159 [-0.95, 0.45, 0.0],
160 0.12,
161 "NEAREST_MAG\n(crisp)\nAaBbCc 123",
162 [1.0, 1.0, 1.0, 1.0],
163 TextShadowComponent::new()
164 .with_scale(1.35)
165 .with_offset([0.06, -0.06, 0.0015]),
166 TextureFilteringComponent::nearest_magnification(),
167 );
168
169 spawn_text_style(
170 &mut universe,
171 [-0.95, 0.05, 0.0],
172 0.12,
173 "LINEAR\n(softer)\nAaBbCc 123",
174 [0.55, 0.90, 1.0, 1.0],
175 TextShadowComponent::new()
176 .with_rgba([0.0, 0.0, 0.15, 1.0])
177 .with_scale(1.20)
178 .with_offset([0.05, -0.04, 0.0015]),
179 TextureFilteringComponent::linear(),
180 );
181
182 spawn_text_style(
183 &mut universe,
184 [-0.95, -0.35, 0.0],
185 0.12,
186 "NEAREST\n(pixelly)\nAaBbCc 123",
187 [1.0, 0.85, 0.35, 1.0],
188 TextShadowComponent::new()
189 .with_rgba([0.15, 0.0, 0.0, 1.0])
190 .with_scale(1.55)
191 .with_offset([0.08, -0.08, 0.0015]),
192 TextureFilteringComponent::nearest(),
193 );
194
195 // Process init-time registrations (Text expands into glyph subtrees here).
196 universe.systems.process_commands(
197 &mut universe.world,
198 &mut universe.visuals,
199 &mut universe.render_assets,
200 &mut universe.command_queue,
201 );
202
203 engine::Windowing::run_app(universe).expect("Windowing failed");
204}60fn main() {
61 mittens_engine::example_support::ensure_model_assets();
62 utils::logger::init();
63
64 // Usage:
65 // cargo run --example folder-text -- [folder] [spacing]
66 // Defaults:
67 // folder=src spacing=0.3
68 let mut args = std::env::args().skip(1);
69 let folder = args.next().unwrap_or_else(|| "src".to_string());
70 let spacing: f32 = args
71 .next()
72 .and_then(|s| s.parse::<f32>().ok())
73 .unwrap_or(1.0);
74
75 // Safety caps: this demo can explode the ECS if we load huge projects.
76 const MAX_FILES: usize = 42;
77 const MAX_CHARS_PER_FILE: usize = 10_000;
78 const WRAP_AT: usize = 90;
79 const TEXT_SCALE: f32 = 0.01;
80
81 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
82 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83 let scan_root = {
84 let p = PathBuf::from(&folder);
85 if p.is_absolute() {
86 p
87 } else {
88 // Resolve relative paths from the crate root, not the process CWD.
89 manifest_dir.join(p)
90 }
91 };
92 eprintln!("[folder-text] cwd={:?}", cwd);
93 eprintln!("[folder-text] manifest_dir={:?}", manifest_dir);
94 eprintln!("[folder-text] scan_root={:?}", scan_root);
95
96 let mut files = Vec::new();
97 collect_rs_files(&scan_root, &mut files);
98 files.sort();
99
100 if files.is_empty() {
101 eprintln!(
102 "[folder-text] No .rs files found under folder={:?} (resolved={:?})",
103 folder, scan_root
104 );
105 }
106
107 let files: Vec<PathBuf> = files.into_iter().take(MAX_FILES).collect();
108 eprintln!(
109 "[folder-text] Loaded {} file(s) from {:?} (spacing={})",
110 files.len(),
111 folder,
112 spacing
113 );
114
115 #[derive(Debug, Clone)]
116 struct FileEntry {
117 path: PathBuf,
118 display_text: String,
119 max_cols: usize,
120 rows: usize,
121 }
122
123 // Group files by folder.
124 // Layout:
125 // - each folder becomes a column along +X
126 // - within a folder, files stack along +Y (8 high)
127 // - after 8, additional files start a new stack further along +Z
128 let mut files_by_folder: BTreeMap<PathBuf, Vec<FileEntry>> = BTreeMap::new();
129 for path in files {
130 let Ok(content) = std::fs::read_to_string(&path) else {
131 eprintln!("[folder-text] Failed to read {:?}", path);
132 continue;
133 };
134
135 let mut display_text = format!(
136 "// {}\n\n{}",
137 path.strip_prefix(&manifest_dir).unwrap_or(&path).display(),
138 content
139 );
140
141 if display_text.chars().count() > MAX_CHARS_PER_FILE {
142 display_text = display_text
143 .chars()
144 .take(MAX_CHARS_PER_FILE)
145 .collect::<String>();
146 display_text.push_str("\n\n// ... truncated ...\n");
147 }
148
149 let (max_cols, rows) = measure_text_bounds(&display_text, WRAP_AT);
150
151 let folder_key = path.parent().unwrap_or(&scan_root).to_path_buf();
152 files_by_folder
153 .entry(folder_key)
154 .or_default()
155 .push(FileEntry {
156 path,
157 display_text,
158 max_cols,
159 rows,
160 });
161 }
162
163 let column_count = files_by_folder.len().max(1);
164
165 let world = engine::ecs::World::default();
166 let mut universe = engine::Universe::new(world);
167
168 let bg_r = 0.25;
169 let bg_g = 0.25;
170 let bg_b = 0.25;
171 let background = universe
172 .world
173 .add_component(engine::ecs::component::BackgroundColorComponent::new());
174 let background_c = universe
175 .world
176 .add_component(engine::ecs::component::ColorComponent::rgba(
177 bg_r, bg_g, bg_b, 1.00,
178 ));
179 let _ = universe.world.add_child(background, background_c);
180 universe.add(background);
181
182 let ambient = universe
183 .world
184 .add_component(engine::ecs::component::AmbientLightComponent::rgb(
185 bg_r, bg_g, bg_b,
186 ));
187 universe.add(ambient);
188
189 // Input-driven camera rig.
190 // Topology: I { T { C3D } }
191 let input = universe
192 .world
193 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
194
195 // Center the camera horizontally over the spawned columns.
196 let center_x = if column_count <= 1 {
197 0.0
198 } else {
199 ((column_count - 1) as f32) * spacing * 0.5
200 };
201
202 // Bring camera closer: these text blocks are tiny.
203 let rig_transform = universe.world.add_component(
204 engine::ecs::component::TransformComponent::new().with_position(center_x, 0.0, 1.2),
205 );
206 let camera3d = universe
207 .world
208 .add_component(engine::ecs::component::Camera3DComponent::new());
209 let input_mode = universe.world.add_component(
210 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
211 );
212 let _ = universe.attach(input, input_mode);
213 let _ = universe.attach(input, rig_transform);
214 let _ = universe.attach(rig_transform, camera3d);
215
216 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
217 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
218 universe.add(input);
219
220 // Big floor plane under everything.
221 // RenderableComponent::square() is an XY quad facing +Z; rotate it to XZ facing +Y.
222 let max_stacks = files_by_folder
223 .values()
224 .map(|v| (v.len() + 7) / 8)
225 .max()
226 .unwrap_or(1)
227 .max(1);
228 let total_width = (column_count as f32) * spacing;
229 let total_depth = (max_stacks as f32) * spacing;
230 let floor_w = total_width.max(10.0);
231 let floor_h = total_depth.max(10.0);
232 let floor_transform = universe.world.add_component(
233 engine::ecs::component::TransformComponent::new()
234 .with_position(0.0, -2.0, 0.0)
235 .with_rotation_euler(-std::f32::consts::FRAC_PI_2, 0.0, 0.0)
236 .with_scale(floor_w, floor_h, 1.0),
237 );
238 let floor_renderable = universe
239 .world
240 .add_component(engine::ecs::component::RenderableComponent::square());
241 let floor_color = universe
242 .world
243 .add_component(engine::ecs::component::ColorComponent::rgba(
244 0.88, 0.88, 0.88, 1.0,
245 ));
246 let _ = universe.attach(floor_transform, floor_renderable);
247 let _ = universe.attach(floor_renderable, floor_color);
248 universe.add(floor_transform);
249
250 // 4 red cubes around the perimeter of the world (easy visual anchors).
251 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
252 let t = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new()
254 .with_position(x, y, z)
255 .with_scale(s, s, s),
256 );
257 let r = universe
258 .world
259 .add_component(engine::ecs::component::RenderableComponent::cube());
260 let c = universe
261 .world
262 .add_component(engine::ecs::component::ColorComponent::rgba(
263 1.0, 0.0, 0.0, 1.0,
264 ));
265
266 let light_transform = universe.world.add_component(
267 engine::ecs::component::TransformComponent::new().with_position(0.0, s * 0.75, 0.0),
268 );
269 let light = universe.world.add_component(
270 engine::ecs::component::PointLightComponent::new()
271 .with_intensity(1.5)
272 .with_distance(20.0)
273 .with_color(1.0, 0.0, 0.0),
274 );
275
276 let _ = universe.attach(t, r);
277 let _ = universe.attach(r, c);
278 let _ = universe.attach(t, light_transform);
279 let _ = universe.attach(light_transform, light);
280
281 universe.add(t);
282 }
283
284 let p = 1.5;
285 let s = 0.25;
286 spawn_red_cube(&mut universe, -p, -p, 0.0, s);
287 spawn_red_cube(&mut universe, p, -p, 0.0, s);
288 spawn_red_cube(&mut universe, -p, p, 0.0, s);
289 spawn_red_cube(&mut universe, p, p, 0.0, s);
290
291 let start_x = -center_x;
292 let stack_depth = spacing;
293 let row_gap_world = 0.35;
294
295 // One global point light at the top of the overall text "tower".
296 let pad_y = 4.0;
297 let global_max_panel_h_world = files_by_folder
298 .values()
299 .flat_map(|v| v.iter())
300 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
301 .fold(0.0_f32, f32::max)
302 .max(0.6);
303 let global_slot_h_world = global_max_panel_h_world + row_gap_world;
304 let tower_top_y = (7.0 * global_slot_h_world) + 4.0;
305 let tower_center_z = total_depth * 0.5;
306
307 let tower_light_transform = universe.world.add_component(
308 engine::ecs::component::TransformComponent::new().with_position(
309 0.0,
310 tower_top_y,
311 tower_center_z,
312 ),
313 );
314 let tower_light = universe.world.add_component(
315 engine::ecs::component::PointLightComponent::new()
316 .with_intensity(2.0)
317 .with_distance(120.0)
318 .with_color(1.0, 1.0, 1.0),
319 );
320 let _ = universe.attach(tower_light_transform, tower_light);
321 universe.add(tower_light_transform);
322
323 for (col_idx, (_folder, mut entries)) in files_by_folder.into_iter().enumerate() {
324 entries.sort_by(|a, b| a.path.cmp(&b.path));
325
326 // Slot height (world units) based on the tallest panel in this folder.
327 let pad_y = 4.0;
328 let max_panel_h_world = entries
329 .iter()
330 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
331 .fold(0.0_f32, f32::max)
332 .max(0.6);
333 let slot_h_world = max_panel_h_world + row_gap_world;
334
335 let col_x = start_x + (col_idx as f32) * spacing;
336
337 for (i, entry) in entries.into_iter().enumerate() {
338 let row = (i % 8) as f32;
339 let stack = (i / 8) as f32;
340 let file_y = row * slot_h_world;
341 let file_z = stack * stack_depth;
342
343 let file_group = universe.world.add_component(
344 engine::ecs::component::TransformComponent::new()
345 .with_position(col_x, file_y, file_z),
346 );
347
348 // Text subtree (tiny scale).
349 let file_root = universe.world.add_component(
350 engine::ecs::component::TransformComponent::new()
351 .with_position(0.0, 1.0, 0.0)
352 .with_scale(TEXT_SCALE, TEXT_SCALE, 1.0)
353 .with_rotation_euler(0.0, std::f32::consts::PI / 6.0, 0.0),
354 );
355 let _ = universe.attach(file_group, file_root);
356
357 // Background panel behind the text.
358 // Size is based on wrapped line count (matches TextSystem's strict wrapping behavior).
359 let (max_cols, rows) = (entry.max_cols, entry.rows);
360 let pad_x = 4.0;
361 let pad_y = 4.0;
362 let w = (max_cols as f32) + pad_x;
363 let h = (rows as f32) + pad_y;
364
365 // Text glyph quads are centered at integer (col,row) positions and are 1x1, so the
366 // text's AABB (in text-space) is roughly:
367 // x: [-0.5, max_cols-0.5]
368 // y: [-(rows-1)-0.5, 0.5]
369 // Centering the background quad at those midpoints keeps it aligned as text grows.
370 let bg_x = (max_cols as f32 - 1.0) * 0.5;
371 let bg_y = -((rows as f32 - 1.0) * 0.5);
372
373 let bg_transform = universe.world.add_component(
374 engine::ecs::component::TransformComponent::new()
375 .with_position(bg_x, bg_y, -0.05)
376 .with_scale(w, h, 1.0),
377 );
378 let bg_renderable = universe
379 .world
380 .add_component(engine::ecs::component::RenderableComponent::square());
381 let bg_quant = universe.world.add_component(
382 engine::ecs::component::LightQuantizationComponent::steps(5.0),
383 );
384 let bg_color =
385 universe
386 .world
387 .add_component(engine::ecs::component::ColorComponent::rgba(
388 0.2, 0.2, 0.2, 1.0,
389 ));
390 let _ = universe.attach(file_root, bg_transform);
391 let _ = universe.attach(bg_transform, bg_renderable);
392 let _ = universe.attach(bg_renderable, bg_quant);
393 let _ = universe.attach(bg_renderable, bg_color);
394
395 let text =
396 universe
397 .world
398 .add_component(engine::ecs::component::TextComponent::with_wrap(
399 entry.display_text,
400 WRAP_AT,
401 ));
402 let cutout = universe
403 .world
404 .add_component(engine::ecs::component::TransparentCutoutComponent::new());
405 let filtering = universe.world.add_component(
406 engine::ecs::component::TextureFilteringComponent::nearest_magnification(),
407 );
408 // let color = universe
409 // .world
410 // .add_component(engine::ecs::component::ColorComponent::rgba(0.7, 0.7, 1.0, 1.0));
411 let emissive = universe
412 .world
413 .add_component(engine::ecs::component::EmissiveComponent::on());
414 let _ = universe.attach(file_root, text);
415 let _ = universe.attach(text, cutout);
416 let _ = universe.attach(text, filtering);
417 //let _ = universe.world.add_child(text, color);
418 let _ = universe.attach(text, emissive);
419
420 universe.add(file_group);
421 }
422 }
423
424 // Process init-time registrations (Text expands into glyph subtrees here).
425 universe.systems.process_commands(
426 &mut universe.world,
427 &mut universe.visuals,
428 &mut universe.render_assets,
429 &mut universe.command_queue,
430 );
431
432 engine::Windowing::run_app(universe).expect("Windowing failed");
433}Sourcepub fn square_dynamic(render_assets: &mut RenderAssets) -> Self
pub fn square_dynamic(render_assets: &mut RenderAssets) -> Self
Predefined renderable: 2D square/quad (unique CPU mesh registered into render_assets).
Sourcepub fn cube() -> Self
pub fn cube() -> Self
Predefined renderable: cube primitive (shared built-in mesh handle).
Examples found in repository?
12fn spawn_gold_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: f32,
17 color: (f32, f32, f32),
18) {
19 let t = universe.world.add_component(
20 TransformComponent::new()
21 .with_position(position.0, position.1, position.2)
22 .with_scale(scale, scale, scale),
23 );
24 let r = universe.world.add_component(RenderableComponent::cube());
25 let c = universe
26 .world
27 .add_component(ColorComponent::rgba(color.0, color.1, color.2, 1.0));
28
29 let _ = universe.attach(parent, t);
30 let _ = universe.attach(t, r);
31 let _ = universe.attach(r, c);
32}More examples
176fn spawn_raycastable_cube(
177 universe: &mut engine::Universe,
178 parent: engine::ecs::ComponentId,
179 pos: [f32; 3],
180 scale: [f32; 3],
181) -> engine::ecs::ComponentId {
182 use engine::ecs::component::{RaycastableComponent, RenderableComponent, TransformComponent};
183
184 let t = universe.world.add_component(
185 TransformComponent::new()
186 .with_position(pos[0], pos[1], pos[2])
187 .with_scale(scale[0], scale[1], scale[2]),
188 );
189 let r = universe.world.add_component(RenderableComponent::cube());
190 let rc = universe
191 .world
192 .add_component(RaycastableComponent::enabled());
193
194 let _ = universe.attach(parent, t);
195 let _ = universe.attach(t, r);
196 let _ = universe.attach(r, rc);
197
198 r
199}71 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
72 let t = universe.world.add_component(
73 engine::ecs::component::TransformComponent::new()
74 .with_position(x, y, z)
75 .with_scale(s, s, s),
76 );
77 let r = universe
78 .world
79 .add_component(engine::ecs::component::RenderableComponent::cube());
80 let c = universe
81 .world
82 .add_component(engine::ecs::component::ColorComponent::rgba(
83 1.0, 0.0, 0.0, 1.0,
84 ));
85 let e = universe
86 .world
87 .add_component(engine::ecs::component::EmissiveComponent::on());
88
89 let _ = universe.attach(t, r);
90 let _ = universe.attach(r, c);
91 let _ = universe.attach(r, e);
92
93 universe.add(t);
94 }12fn spawn_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: (f32, f32, f32),
17 color: Option<(f32, f32, f32, f32)>,
18 opacity: Option<OpacityComponent>,
19) {
20 let t = universe.world.add_component(
21 TransformComponent::new()
22 .with_position(position.0, position.1, position.2)
23 .with_scale(scale.0, scale.1, scale.2),
24 );
25 let r = universe.world.add_component(RenderableComponent::cube());
26
27 let _ = universe.attach(parent, t);
28 let _ = universe.attach(t, r);
29
30 if let Some((cr, cg, cb, ca)) = color {
31 let c = universe
32 .world
33 .add_component(ColorComponent::rgba(cr, cg, cb, ca));
34 let _ = universe.attach(r, c);
35 }
36
37 if let Some(o) = opacity {
38 let o = universe.world.add_component(o);
39 let _ = universe.attach(r, o);
40 }
41}
42
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44 // T_root { T_scale { TXT { filtering } } }
45 let text_root = universe
46 .world
47 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49 let text_scale = universe
50 .world
51 .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52 let _ = universe.attach(text_root, text_scale);
53
54 let text_c = universe.world.add_component(TextComponent::new(text));
55 let _ = universe.attach(text_scale, text_c);
56
57 // Keep it crisp.
58 let filtering = universe
59 .world
60 .add_component(TextureFilteringComponent::nearest());
61 let _ = universe.attach(text_c, filtering);
62
63 // White label.
64 let color = universe
65 .world
66 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67 let _ = universe.attach(text_c, color);
68
69 universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73 let mut max_cols = 0usize;
74 let mut rows = 1usize;
75 let mut cur = 0usize;
76
77 for ch in text.chars() {
78 if ch == '\n' {
79 max_cols = max_cols.max(cur);
80 cur = 0;
81 rows += 1;
82 } else {
83 cur += 1;
84 }
85 }
86 max_cols = max_cols.max(cur);
87
88 (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}
156
157fn spawn_demo_spot(
158 universe: &mut engine::Universe,
159 spot_pos: (f32, f32, f32),
160 opacity: f32,
161 multiple_layers: bool,
162 grid_n: i32,
163) {
164 let spot = universe
165 .world
166 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
167
168 // All cubes inherit this color.
169 let white = universe
170 .world
171 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
172 let _ = universe.attach(spot, white);
173
174 // All cubes inherit this opacity (no per-cube OpacityComponent needed).
175 let mut oc = OpacityComponent::new().with_opacity(opacity);
176 if multiple_layers {
177 oc = oc.with_multiple_layers();
178 }
179 let oc = universe.world.add_component(oc);
180 let _ = universe.attach(spot, oc);
181
182 universe.add(spot);
183
184 // Cube grid.
185 let n = grid_n.max(1);
186 let spacing = if n <= 2 { 1.0 } else { 0.6 };
187 let cube_scale = if n <= 2 { 0.8 } else { 0.45 };
188 let half = (n as f32 - 1.0) * spacing * 0.5;
189
190 for z in 0..n {
191 for y in 0..n {
192 for x in 0..n {
193 let px = x as f32 * spacing - half;
194 let py = y as f32 * spacing;
195 let pz = z as f32 * spacing - half;
196
197 spawn_cube(
198 universe,
199 spot,
200 (px, py, pz),
201 (cube_scale, cube_scale, cube_scale),
202 None,
203 None,
204 );
205 }
206 }
207 }
208
209 // Label above.
210 let label = format!(
211 "opacity: {:.2}\nmulti-layer: {}",
212 opacity,
213 if multiple_layers { "true" } else { "false" }
214 );
215 // Keep labels away from the cube volume so they don't intersect.
216 let label_y = spot_pos.1 + (n as f32 * spacing) + 1.8;
217 let label_x = spot_pos.0 - (half + 0.6);
218 let label_z = spot_pos.2 - (half + 1.0);
219 spawn_text_label(universe, (label_x, label_y, label_z), &label);
220}
221
222fn spawn_demo_strip_pair(
223 universe: &mut engine::Universe,
224 spot_pos: (f32, f32, f32),
225 transparent_multiple_layers: bool,
226) {
227 let n_z: i32 = 4;
228 let spacing = 1.0;
229 let cube_scale = 0.8;
230 let half_z = (n_z as f32 - 1.0) * spacing * 0.5;
231
232 // Transparent strip (1x4 along Z).
233 let transparent_root = universe
234 .world
235 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
236
237 let white = universe
238 .world
239 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
240 let _ = universe.attach(transparent_root, white);
241
242 let mut oc = OpacityComponent::new().with_opacity(0.50);
243 if transparent_multiple_layers {
244 oc = oc.with_multiple_layers();
245 }
246 let oc = universe.world.add_component(oc);
247 let _ = universe.attach(transparent_root, oc);
248
249 universe.add(transparent_root);
250
251 for z in 0..n_z {
252 let pz = z as f32 * spacing - half_z;
253 spawn_cube(
254 universe,
255 transparent_root,
256 (0.0, 0.0, pz),
257 (cube_scale, cube_scale, cube_scale),
258 None,
259 None,
260 );
261 }
262
263 // Opaque strip to the left, same spacing/scale.
264 let opaque_root = universe
265 .world
266 .add_component(TransformComponent::new().with_position(
267 spot_pos.0 - spacing,
268 spot_pos.1,
269 spot_pos.2,
270 ));
271 let white = universe
272 .world
273 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
274 let _ = universe.attach(opaque_root, white);
275 universe.add(opaque_root);
276
277 for z in 0..n_z {
278 let pz = z as f32 * spacing - half_z;
279 spawn_cube(
280 universe,
281 opaque_root,
282 (0.0, 0.0, pz),
283 (cube_scale, cube_scale, cube_scale),
284 None,
285 None,
286 );
287 }
288
289 let label = format!(
290 "mini: opaque + transparent strip\ntransparent opacity: 0.50\nmulti-layer: {}",
291 if transparent_multiple_layers {
292 "true"
293 } else {
294 "false"
295 }
296 );
297 let label_y = spot_pos.1 + spacing + 1.8;
298 let label_x = spot_pos.0 - (spacing * 2.6);
299 let label_z = spot_pos.2 - (half_z + 1.0);
300 spawn_text_label(universe, (label_x, label_y, label_z), &label);
301}
302
303fn spawn_demo_xy_plane(
304 universe: &mut engine::Universe,
305 spot_pos: (f32, f32, f32),
306 opacity: f32,
307 n_x: i32,
308 n_y: i32,
309 z: f32,
310) {
311 let spot = universe
312 .world
313 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
314
315 let white = universe
316 .world
317 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
318 let _ = universe.attach(spot, white);
319
320 let oc = universe
321 .world
322 .add_component(OpacityComponent::new().with_opacity(opacity));
323 let _ = universe.attach(spot, oc);
324
325 universe.add(spot);
326
327 let nx = n_x.max(1);
328 let ny = n_y.max(1);
329 let spacing = 0.45;
330 let cube_scale = 0.35;
331 let half_x = (nx as f32 - 1.0) * spacing * 0.5;
332
333 for y in 0..ny {
334 for x in 0..nx {
335 let px = x as f32 * spacing - half_x;
336 let py = y as f32 * spacing;
337 spawn_cube(
338 universe,
339 spot,
340 (px, py, z),
341 (cube_scale, cube_scale, cube_scale),
342 None,
343 None,
344 );
345 }
346 }
347
348 // Label in front of the plane.
349 let label = format!(
350 "XY plane: {}x{}\nopacity: {:.2}\nmulti-layer: false",
351 nx, ny, opacity
352 );
353 let label_x = spot_pos.0 - (half_x + 0.6);
354 let label_y = spot_pos.1 + (ny as f32 * spacing) + 1.2;
355 let label_z = spot_pos.2 - 2.5;
356 spawn_text_label_with_bg(universe, (label_x, label_y, label_z), &label, 0.50);
357}
358
359fn main() {
360 mittens_engine::example_support::ensure_model_assets();
361 utils::logger::init();
362
363 let world = engine::ecs::World::default();
364 let mut universe = engine::Universe::new(world);
365
366 // Dark brown / pink background.
367 let bg = universe
368 .world
369 .add_component(BackgroundColorComponent::new());
370 let bg_c = universe
371 .world
372 .add_component(ColorComponent::rgba(0.22, 0.08, 0.10, 1.0));
373 let _ = universe.world.add_child(bg, bg_c);
374 universe.add(bg);
375
376 // Ambient so unlit areas aren't black.
377 let ambient = universe
378 .world
379 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.40));
380 universe.add(ambient);
381
382 // A bright overhead light.
383 let light_t = universe.world.add_component(
384 TransformComponent::new()
385 .with_position(0.0, 18.0, 10.0)
386 .with_scale(0.2, 0.2, 0.2),
387 );
388 let light = universe.world.add_component(
389 PointLightComponent::new()
390 .with_color(1.0, 1.0, 1.0)
391 .with_intensity(1.6)
392 .with_distance(80.0),
393 );
394 let _ = universe.attach(light_t, light);
395 universe.add(light_t);
396
397 // --- Camera rig ---
398 // I { T { C3D { with_fps_rotation with_roll_axis_y } } }
399 let input = universe
400 .world
401 .add_component(InputComponent::new().with_speed(3.0));
402 let input_mode = universe.world.add_component(
403 InputTransformModeComponent::forward_z()
404 .with_roll_axis_y()
405 .with_fps_rotation(),
406 );
407 let _ = universe.attach(input, input_mode);
408
409 let rig_transform = universe
410 .world
411 .add_component(TransformComponent::new().with_position(0.0, 6.0, 20.0));
412 let _ = universe.attach(input, rig_transform);
413
414 let camera = universe.world.add_component(Camera3DComponent::new());
415 let _ = universe.attach(rig_transform, camera);
416
417 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
418 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
419
420 universe.add(input);
421
422 // --- Ground ---
423 let ground = universe.world.add_component(
424 TransformComponent::new()
425 .with_position(0.0, -0.6, 0.0)
426 .with_scale(60.0, 1.0, 60.0),
427 );
428 let ground_r = universe.world.add_component(RenderableComponent::cube());
429 let ground_c = universe
430 .world
431 .add_component(ColorComponent::rgba(0.95, 0.95, 0.95, 1.0));
432 let _ = universe.attach(ground, ground_r);
433 let _ = universe.attach(ground_r, ground_c);
434 universe.add(ground);
435
436 // --- Opaque yellow cubes behind (contrast reference) ---
437 for i in 0..6 {
438 let x = -10.0 + i as f32 * 4.0;
439 spawn_cube(
440 &mut universe,
441 ground,
442 (x, 1.2, -18.0),
443 (2.0, 2.0, 2.0),
444 Some((1.0, 0.92, 0.15, 1.0)),
445 None,
446 );
447 }
448
449 // --- Demo spots ---
450 // Left of the left big spot: a single-layer transparent XY plane (16x16).
451 spawn_demo_xy_plane(&mut universe, (-14.0, 0.0, 0.0), 0.50, 16, 16, 0.0);
452
453 // Big spots: 8x8x8
454 // Left: single-layer transparent (instanced)
455 spawn_demo_spot(&mut universe, (-4.0, 0.0, 0.0), 0.50, false, 8);
456
457 // Right: multi-layer transparent (sorted)
458 spawn_demo_spot(&mut universe, (4.0, 0.0, 0.0), 0.50, true, 8);
459
460 // Small spots: opaque 1x4 strip + transparent 1x4 strip (along Z).
461 spawn_demo_strip_pair(&mut universe, (-4.0, 0.0, 10.0), false);
462 spawn_demo_strip_pair(&mut universe, (4.0, 0.0, 10.0), true);
463
464 // Process init-time registrations.
465 universe.systems.process_commands(
466 &mut universe.world,
467 &mut universe.visuals,
468 &mut universe.render_assets,
469 &mut universe.command_queue,
470 );
471
472 universe.enable_repl();
473
474 engine::Windowing::run_app(universe).expect("Windowing failed");
475}20fn spawn_emissive_marker_cube(
21 universe: &mut engine::Universe,
22 parent: engine::ecs::ComponentId,
23 local_pos: (f32, f32, f32),
24 scale: f32,
25 rgba: [f32; 4],
26) {
27 let tx = universe.world.add_component(
28 engine::ecs::component::TransformComponent::new()
29 .with_position(local_pos.0, local_pos.1, local_pos.2)
30 .with_scale(scale, scale, scale),
31 );
32 let r = universe
33 .world
34 .add_component(engine::ecs::component::RenderableComponent::cube());
35 let c = universe
36 .world
37 .add_component(engine::ecs::component::ColorComponent::rgba(
38 rgba[0], rgba[1], rgba[2], rgba[3],
39 ));
40 let e = universe
41 .world
42 .add_component(engine::ecs::component::EmissiveComponent::on());
43
44 let _ = universe.attach(parent, tx);
45 let _ = universe.attach(tx, r);
46 let _ = universe.attach(r, c);
47 let _ = universe.attach(r, e);
48}
49
50/// Create a cube subtree ahead-of-time (not attached to the scene).
51///
52/// Returns the root `TransformComponent` id.
53fn spawn_detached_cube_prefab(
54 universe: &mut engine::Universe,
55 scale: f32,
56 rgba: [f32; 4],
57) -> engine::ecs::ComponentId {
58 let tx = universe.world.add_component(
59 engine::ecs::component::TransformComponent::new().with_scale(scale, scale, scale),
60 );
61 let r = universe
62 .world
63 .add_component(engine::ecs::component::RenderableComponent::cube());
64 let c = universe
65 .world
66 .add_component(engine::ecs::component::ColorComponent::rgba(
67 rgba[0], rgba[1], rgba[2], rgba[3],
68 ));
69 let e = universe
70 .world
71 .add_component(engine::ecs::component::EmissiveComponent::on());
72
73 let _ = universe.attach(tx, r);
74 let _ = universe.attach(r, c);
75 let _ = universe.attach(r, e);
76
77 tx
78}117 fn spawn_emissive_cube(
118 universe: &mut engine::Universe,
119 parent: engine::ecs::ComponentId,
120 pos: (f32, f32, f32),
121 scale: f32,
122 rgba: [f32; 4],
123 ) {
124 let tx = universe.world.add_component(
125 engine::ecs::component::TransformComponent::new()
126 .with_position(pos.0, pos.1, pos.2)
127 .with_scale(scale, scale, scale),
128 );
129 let r = universe
130 .world
131 .add_component(engine::ecs::component::RenderableComponent::cube());
132 let c = universe
133 .world
134 .add_component(engine::ecs::component::ColorComponent::rgba(
135 rgba[0], rgba[1], rgba[2], rgba[3],
136 ));
137 let e = universe
138 .world
139 .add_component(engine::ecs::component::EmissiveComponent::on());
140 let _ = universe.attach(parent, tx);
141 let _ = universe.attach(tx, r);
142 let _ = universe.attach(r, c);
143 let _ = universe.attach(r, e);
144 }
145
146 fn spawn_op_cube(
147 universe: &mut engine::Universe,
148 parent: engine::ecs::ComponentId,
149 pos: (f32, f32, f32),
150 scale: f32,
151 base_rgba: [f32; 4],
152 ) -> engine::ecs::ComponentId {
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(pos.0, pos.1, pos.2)
156 .with_scale(scale, scale, scale),
157 );
158 let r = universe
159 .world
160 .add_component(engine::ecs::component::RenderableComponent::cube());
161 let c = universe
162 .world
163 .add_component(engine::ecs::component::ColorComponent::rgba(
164 base_rgba[0],
165 base_rgba[1],
166 base_rgba[2],
167 base_rgba[3],
168 ));
169 let e = universe
170 .world
171 .add_component(engine::ecs::component::EmissiveComponent::on());
172
173 let _ = universe.attach(parent, tx);
174 let _ = universe.attach(tx, r);
175 let _ = universe.attach(r, c);
176 let _ = universe.attach(r, e);
177
178 tx
179 }- examples/audio-graph-example.rs
- examples/raycast-topology-animation.rs
- examples/folder-text.rs
- examples/collision-perimeter.rs
- examples/pointer-events.rs
- examples/util/mod.rs
- examples/vr-input.rs
- examples/vtuber-example.rs
- examples/bisket-vr-demo.rs
- examples/vtuber-joints-example.rs
- examples/gravity-fields.rs
Sourcepub fn cube_dynamic(render_assets: &mut RenderAssets) -> Self
pub fn cube_dynamic(render_assets: &mut RenderAssets) -> Self
Predefined renderable: cube primitive (unique CPU mesh registered into render_assets).
Sourcepub fn wireframe_box(render_assets: &mut RenderAssets, thickness: f32) -> Self
pub fn wireframe_box(render_assets: &mut RenderAssets, thickness: f32) -> Self
Unit wireframe box with twelve solid edges of configurable relative thickness.
Sourcepub fn sphere_dynamic(render_assets: &mut RenderAssets) -> Self
pub fn sphere_dynamic(render_assets: &mut RenderAssets) -> Self
Predefined renderable: sphere primitive (unique CPU mesh registered into render_assets).
Sourcepub fn cone_dynamic(render_assets: &mut RenderAssets, segments: u32) -> Self
pub fn cone_dynamic(render_assets: &mut RenderAssets, segments: u32) -> Self
Predefined renderable: cone primitive (unique CPU mesh registered into render_assets).
pub fn icosahedron( render_assets: &mut RenderAssets, tessellations: u32, sphericalness: f32, ) -> Self
Sourcepub fn tetrahedron() -> Self
pub fn tetrahedron() -> Self
Predefined renderable: tetrahedron primitive (shared built-in mesh handle).
Sourcepub fn tetrahedron_dynamic(render_assets: &mut RenderAssets) -> Self
pub fn tetrahedron_dynamic(render_assets: &mut RenderAssets) -> Self
Predefined renderable: tetrahedron primitive (unique CPU mesh registered into render_assets).
Sourcepub fn color_tetrahedron() -> Self
pub fn color_tetrahedron() -> Self
Predefined renderable: tetrahedron (alias of tetrahedron).
pub fn partial_annulus_2d( render_assets: &mut RenderAssets, inner_radius: f32, outer_radius: f32, start_angle_radians: f32, sweep_angle_radians: f32, segments: u32, ) -> Self
pub fn star( render_assets: &mut RenderAssets, points: u32, inner_radius_fraction: f32, outer_bevel_segments: u32, inner_bevel_segments: u32, ) -> Self
pub fn heart(render_assets: &mut RenderAssets, segments: u32) -> Self
Trait Implementations§
Source§impl Clone for RenderableComponent
impl Clone for RenderableComponent
Source§fn clone(&self) -> RenderableComponent
fn clone(&self) -> RenderableComponent
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Component for RenderableComponent
impl Component for RenderableComponent
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
fn set_id(&mut self, component: ComponentId)
fn as_any(&self) -> &dyn Any
fn as_any_mut(&mut self) -> &mut dyn Any
Source§fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Source§fn cleanup(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn cleanup(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Source§fn to_mms_ast(&self, _world: &World) -> ComponentExpression
fn to_mms_ast(&self, _world: &World) -> ComponentExpression
Auto Trait Implementations§
impl Freeze for RenderableComponent
impl RefUnwindSafe for RenderableComponent
impl Send for RenderableComponent
impl Sync for RenderableComponent
impl Unpin for RenderableComponent
impl UnsafeUnpin for RenderableComponent
impl UnwindSafe for RenderableComponent
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.