pub struct EditorComponent {
pub active: bool,
pub transform_gizmo: Option<ComponentId>,
pub selected: Option<ComponentId>,
pub interaction_mode: EditorInteractionMode,
pub transform_gizmo_translation_space: TransformGizmoCoordSpace,
pub transform_gizmo_rotation_space: TransformGizmoCoordSpace,
pub spawn_panels: bool,
pub serialize_editor_panels: bool,
pub world_panel_pos: (f32, f32, f32),
pub inspector_panel_pos: (f32, f32, f32),
/* private fields */
}Expand description
Marks an “editor root” subtree.
When a renderable under this subtree is clicked, the editor selection system can reattach the editor’s gizmos (e.g. TransformGizmo) to the clicked target.
Fields§
§active: boolDeclaratively prefer this editor as the active workspace editor.
transform_gizmo: Option<ComponentId>Runtime cache: resolved TransformGizmoComponent id within this editor subtree.
Not serialized.
selected: Option<ComponentId>Runtime: currently selected TransformComponent.
Set by EditorSystem on DragStart. Read by InspectorSystem to drive panel content. Not serialized.
interaction_mode: EditorInteractionModeRuntime editor interaction mode.
transform_gizmo_translation_space: TransformGizmoCoordSpaceCoordinate space used for translation handles (arrows).
transform_gizmo_rotation_space: TransformGizmoCoordSpaceCoordinate space used for rotation handles (rings).
spawn_panels: boolSpawn world-tree and inspector panels automatically on init. Default: true.
serialize_editor_panels: boolInclude editor-owned runtime UI and editor wrappers when serializing a scene. Default: false.
world_panel_pos: (f32, f32, f32)World-space position of the world-tree panel. Default: (-0.7, 1.6, -1.2).
inspector_panel_pos: (f32, f32, f32)World-space position of the inspector panel.
If set to the same x as world_panel_pos (the default), the inspector is
auto-placed to the right of the world panel using estimate_panel_width.
Implementations§
Source§impl EditorComponent
impl EditorComponent
Sourcepub fn new() -> Self
pub fn new() -> Self
Examples found in repository?
190pub fn spawn_desktop_camera_controls_hint(
191 universe: &mut engine::Universe,
192 camera_transform: engine::ecs::ComponentId,
193) -> engine::ecs::ComponentId {
194 use engine::ecs::component::{
195 ColorComponent, EditorComponent, EmissiveComponent, RaycastableComponent, TextComponent,
196 TextureFilteringComponent, TransformComponent,
197 };
198
199 // We intentionally do NOT parent the hint under the camera rig.
200 // This keeps it out of the camera subtree (and avoids inheriting camera motion/rotation).
201 //
202 // Instead, we place it in world space based on the camera rig's current pose.
203 // That makes it start out “in front-right of the camera” without being attached.
204 let (cam_pos, cam_rot) = universe
205 .world
206 .get_component_by_id_as::<TransformComponent>(camera_transform)
207 .map(|t| (t.transform.translation, t.transform.rotation))
208 .unwrap_or(([0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]));
209
210 // Camera convention in examples: forward is -Z.
211 // Local-space offset from the camera rig at spawn time.
212 let local_offset = [0.65, 0.25, -1.7];
213
214 // Rotate the offset by the camera rig's rotation so it appears in front-right of the view.
215 let world_offset = mittens_engine::utils::math::quat_rotate_vec3(cam_rot, local_offset);
216 let world_pos = [
217 cam_pos[0] + world_offset[0],
218 cam_pos[1] + world_offset[1],
219 cam_pos[2] + world_offset[2],
220 ];
221
222 let hint_root = universe.world.add_component(
223 TransformComponent::new()
224 .with_position(world_pos[0], world_pos[1], world_pos[2])
225 .with_scale(0.055, 0.055, 1.0),
226 );
227
228 // Put the hint into an editor subtree so clicking it attaches gizmos.
229 let editor_root = universe.world.add_component(EditorComponent::new());
230 let _ = universe.attach(hint_root, editor_root);
231
232 let text = universe.world.add_component(TextComponent::new(
233 "use wasd/rf/qe\nand right-mouse\nclick and drag\nto move/look",
234 ));
235
236 // Make glyph renderables raycastable so the hint can be clicked without adding a
237 // large invisible pick plane (clicking is per-glyph / per-text-quad).
238 let raycastable = universe
239 .world
240 .add_component(RaycastableComponent::enabled());
241 let _ = universe.attach(text, raycastable);
242
243 // Text color should be an immediate child of the TextComponent root.
244 let color = universe
245 .world
246 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
247 let _ = universe.attach(text, color);
248
249 // TextSystem looks for these as immediate children of the TextComponent root.
250 let emissive = universe.world.add_component(EmissiveComponent::on());
251 let filtering = universe
252 .world
253 .add_component(TextureFilteringComponent::nearest_magnification());
254
255 let cutout = universe
256 .world
257 .add_component(TransparentCutoutComponent::new());
258 let _ = universe.attach(text, cutout);
259
260 let _ = universe.attach(editor_root, text);
261 let _ = universe.attach(text, emissive);
262 let _ = universe.attach(text, filtering);
263
264 // Ensure the text is initialized even if the caller only adds the camera rig.
265 universe.add(hint_root);
266
267 hint_root
268}More examples
439fn main() {
440 mittens_engine::example_support::ensure_model_assets();
441 utils::logger::init();
442
443 let world = engine::ecs::World::default();
444 let mut universe = engine::Universe::new(world);
445
446 // Minimal lit scene + pointer raycaster.
447 use engine::ecs::component::{
448 AmbientLightComponent, BackgroundColorComponent, Camera3DComponent,
449 DirectionalLightComponent, InputComponent, InputTransformModeComponent, PointerComponent,
450 TransformComponent,
451 };
452
453 let bg = universe
454 .world
455 .add_component(BackgroundColorComponent::new());
456 let bg_c = universe
457 .world
458 .add_component(engine::ecs::component::ColorComponent::rgba(
459 0.92, 0.92, 0.96, 1.0,
460 ));
461 let _ = universe.world.add_child(bg, bg_c);
462 universe.add(bg);
463
464 let ambient = universe
465 .world
466 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.35));
467 universe.add(ambient);
468
469 let sun_t = universe
470 .world
471 .add_component(TransformComponent::new().with_position(1.0, 1.0, 1.0));
472 let sun = universe
473 .world
474 .add_component(DirectionalLightComponent::new());
475 let _ = universe.attach(sun_t, sun);
476 universe.add(sun_t);
477
478 let input = universe
479 .world
480 .add_component(InputComponent::new().with_speed(2.5));
481 let input_mode = universe.world.add_component(
482 InputTransformModeComponent::forward_z()
483 .with_fps_rotation()
484 .with_roll_axis_y(),
485 );
486 let _ = universe.attach(input, input_mode);
487
488 let rig_t = universe
489 .world
490 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
491 let _ = universe.attach(input, rig_t);
492
493 let cam = universe
494 .world
495 .add_component(Camera3DComponent::new().with_far(600.0).with_fov(70.0));
496 let _ = universe.attach(rig_t, cam);
497
498 let pointer = universe.world.add_component(PointerComponent::new());
499 let _ = universe.attach(cam, pointer);
500
501 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_t);
502 universe.add(input);
503
504 // The button.
505 let button_root = spawn_button(
506 &mut universe,
507 [0.0, 0.0, 0.0],
508 [1.20, 0.45],
509 ButtonBorder::new(0.025, 0.025, 0.025, 0.025),
510 "click me",
511 0.18,
512 [0.15, 0.15, 0.20, 1.0],
513 [0.85, 0.85, 0.92, 1.0],
514 );
515
516 // A small 3-cube stack to the left of the button for gizmo testing:
517 // [cyan]
518 // [yellow][light brown]
519 //
520 // These live under an EditorComponent subtree so clicking attaches the editor gizmo.
521 {
522 use engine::ecs::component::{EditorComponent, TransformComponent};
523
524 let editor_root = universe.world.add_component(EditorComponent::new());
525
526 // Position the stack in world space (left of the button).
527 let stack_root = universe
528 .world
529 .add_component(TransformComponent::new().with_position(-1.55, 0.0, 0.0));
530 let _ = universe.attach(editor_root, stack_root);
531
532 let cube = 0.22_f32;
533 let gap = 0.04_f32;
534 let step = cube + gap;
535 let y_bottom = -0.5 * step;
536 let y_top = 0.5 * step;
537 let x_left = -0.5 * step;
538 let x_right = 0.5 * step;
539
540 let yellow = [1.0, 0.92, 0.22, 1.0];
541 let light_brown = [0.80, 0.66, 0.46, 1.0];
542 let cyan = [0.20, 0.95, 1.0, 1.0];
543
544 let _bottom_left = spawn_raycastable_colored_cube(
545 &mut universe,
546 stack_root,
547 [x_left, y_bottom, 0.0],
548 [cube, cube, 0.06],
549 yellow,
550 );
551 let _bottom_right = spawn_raycastable_colored_cube(
552 &mut universe,
553 stack_root,
554 [x_right, y_bottom, 0.0],
555 [cube, cube, 0.06],
556 light_brown,
557 );
558 let _top = spawn_raycastable_colored_cube(
559 &mut universe,
560 stack_root,
561 [0.0, y_top, 0.0],
562 [cube, cube, 0.06],
563 cyan,
564 );
565
566 universe.add(editor_root);
567 }
568
569 universe.add_signal_handler(
570 engine::ecs::SignalKind::DragStart,
571 button_root,
572 button_press_handler,
573 );
574 universe.add_signal_handler(
575 engine::ecs::SignalKind::DragEnd,
576 button_root,
577 button_press_handler,
578 );
579
580 universe.systems.process_commands(
581 &mut universe.world,
582 &mut universe.visuals,
583 &mut universe.render_assets,
584 &mut universe.command_queue,
585 );
586
587 universe.enable_repl();
588 engine::Windowing::run_app(universe).expect("Windowing failed");
589}186fn main() {
187 mittens_engine::example_support::ensure_model_assets();
188 utils::logger::init();
189
190 let options = match parse_options() {
191 Ok(options) => options,
192 Err(message) => {
193 eprintln!("{message}");
194 std::process::exit(2);
195 }
196 };
197
198 println!(
199 "[vr-input] xr controller rotation filter pipeline: {}",
200 if options.xr_controller_rotation_filter {
201 "enabled"
202 } else {
203 "disabled"
204 }
205 );
206
207 let world = engine::ecs::World::default();
208 let mut universe = engine::Universe::new(world);
209
210 let renderer_settings = universe
211 .world
212 .add_component(RendererSettingsComponent::msaa_off().with_window_size(320, 240));
213 universe.add(renderer_settings);
214
215 let render_graph = universe.world.add_component(RenderGraphComponent::new());
216 let emissive_pass = universe.world.add_component(EmissivePassComponent::new());
217 let blur_pass = universe.world.add_component(
218 BlurPassComponent::new()
219 .with_radius_ndc(0.06)
220 .with_half_res(true),
221 );
222 let bloom = universe.world.add_component(
223 BloomComponent::new()
224 .with_intensity(0.95)
225 .with_emissive_scale(1.2),
226 );
227 let _ = universe.attach(emissive_pass, blur_pass);
228 let _ = universe.attach(render_graph, emissive_pass);
229 let _ = universe.attach(render_graph, bloom);
230 universe.add(render_graph);
231
232 // Sky base.
233 let background = universe
234 .world
235 .add_component(BackgroundColorComponent::new());
236 let background_c = universe
237 .world
238 .add_component(ColorComponent::rgba(0.62, 0.80, 1.00, 1.0));
239 let _ = universe.world.add_child(background, background_c);
240 universe.add(background);
241
242 // Lighting for the model.
243 let ambient = universe
244 .world
245 .add_component(AmbientLightComponent::rgb(0.18, 0.18, 0.22));
246 universe.add(ambient);
247
248 let sun = universe.world.add_component(
249 DirectionalLightComponent::new()
250 .with_intensity(1.1)
251 .with_color(1.0, 0.98, 0.95),
252 );
253 let sun_dir = universe
254 .world
255 .add_component(TransformComponent::new().with_position(0.15, -0.45, 1.0));
256 let _ = universe.attach(sun_dir, sun);
257 universe.add(sun_dir);
258
259 // --- Desktop camera rig (for debugging while in VR) ---
260 let input = universe
261 .world
262 .add_component(InputComponent::new().with_speed(1.5));
263 let input_mode = universe.world.add_component(
264 InputTransformModeComponent::forward_z()
265 .with_fps_rotation()
266 .with_roll_axis_y(),
267 );
268 let _ = universe.attach(input, input_mode);
269
270 let desktop_rig = universe
271 .world
272 .add_component(TransformComponent::new().with_position(0.0, 1.2, 3.5));
273 let _ = universe.attach(input, desktop_rig);
274
275 let camera3d = universe.world.add_component(Camera3DComponent::new());
276 let _ = universe.attach(desktop_rig, camera3d);
277
278 let pointer = universe.world.add_component(PointerComponent::new());
279 let _ = universe.attach(camera3d, pointer);
280
281 example_util::spawn_desktop_camera_controls_hint(&mut universe, desktop_rig);
282 universe.add(input);
283
284 // --- XR rig (Aim controller debug cubes only; camera has moved to AVC) ---
285 let xr_input = universe.world.add_component(InputXRComponent::on());
286 let xr_gamepad = universe.world.add_component(
287 mittens_engine::engine::ecs::component::InputXRGamepadComponent::new().speed(1.5),
288 );
289 let xr_rig = universe.world.add_component(TransformComponent::new());
290 let _ = universe.attach(xr_input, xr_rig);
291 let _ = universe.attach(xr_input, xr_gamepad);
292
293 // renderer stats
294 let renderer_stats = universe
295 .world
296 .add_component(RendererStatsComponent::new().with_camera_target(CameraTarget::Xr));
297 let render_stats_rig = universe
298 .world
299 .add_component(TransformComponent::new().with_position(0.0, 1.85, 0.6));
300 let _ = universe.attach(render_stats_rig, renderer_stats);
301 let _ = universe.attach(xr_rig, render_stats_rig);
302
303 universe.add(xr_input);
304
305 // Background "skybox" content.
306 spawn_sun_background(&mut universe);
307
308 // --- VTuber model — single-input topology ---
309 //
310 // InputXRComponent drives body translation and head rotation through AvatarControlSystem.
311 // AvatarControlSystem:
312 // - Splices a TransformComponent under J_Bip_C_Head's parent (the neck) to drive
313 // head rotation directly. Rotating the head — not the neck — isolates the spine
314 // so the torso doesn't twist with HMD yaw.
315 // - Strips rotation from model_root (body faces body_yaw, not raw HMD yaw).
316 // - Bakes the π Y handedness correction into the head rotation math.
317 // - Smoothly rotates body to follow head when yaw delta exceeds threshold.
318 // - Measures J_Bip_C_Head local Y in the rest pose and sets model_root.y = -bone_local_y,
319 // so the head bone sits at driven_t world Y (= HMD height) with no hardcoded constant.
320 // - Re-parents CameraXRComponent under J_Bip_C_Head for first-person alignment.
321 //
322 // Topology (after AVC init):
323 // editor_root
324 // └── avatar_input_xr (InputXRComponent)
325 // └── avatar_driven_t (TransformComponent)
326 // └── AvatarControlComponent
327 // ├── body_pipeline → pipeline_output
328 // │ └── model_root (y auto-calibrated from J_Bip_C_Head)
329 // │ └── GLTFComponent → ... → J_Bip_C_Head
330 // │ └── CameraXRComponent
331 // ├── CTLXR(Left, Grip) → re-parented to lower_arm
332 // └── CTLXR(Right, Grip) → re-parented to lower_arm
333
334 let editor_root = universe.world.add_component(EditorComponent::new());
335
336 let avatar_input_xr = universe.world.add_component(InputXRComponent::on());
337 let avatar_xr_gamepad = universe.world.add_component(
338 mittens_engine::engine::ecs::component::InputXRGamepadComponent::new().speed(1.5),
339 );
340 let avatar_driven_t = universe.world.add_component(TransformComponent::new());
341 let _ = universe.attach(avatar_input_xr, avatar_driven_t);
342 let _ = universe.attach(avatar_input_xr, avatar_xr_gamepad);
343
344 // AvatarControlComponent: -Z forward (OpenXR default), body starts facing -Z (π yaw).
345 // camera_bone triggers auto-calibration of model_root.y from J_Bip_C_Head rest pose height,
346 // and causes any CameraXR/Camera3D direct children of AVC to be re-parented to that bone.
347 let avatar_control = universe.world.add_component(
348 AvatarControlComponent::new()
349 .with_head_bone("J_Bip_C_Head")
350 .with_camera_bone("J_Bip_C_Head")
351 .with_left_hand_bone("J_Bip_L_Hand")
352 .with_right_hand_bone("J_Bip_R_Hand")
353 .with_initial_yaw(std::f32::consts::PI)
354 .with_hand_rotation_smoothing(220.0),
355 );
356 let _ = universe.attach(avatar_driven_t, avatar_control);
357
358 // CameraXR as a direct child of AVC — discovered and re-parented to J_Bip_C_Head at init.
359 let camera_xr = universe.world.add_component(CameraXRComponent::on());
360 let _ = universe.attach(avatar_control, camera_xr);
361 let head_pointer = universe.world.add_component(PointerComponent::new());
362 let _ = universe.attach(camera_xr, head_pointer);
363
364 // Grip controllers for hand bone splicing — children of AvatarControlComponent so
365 // AvatarControlSystem discovers them by topology. Each needs a TransformComponent
366 // child (driven_t) that OpenXRSystem writes each tick.
367 let left_grip = universe.world.add_component(ControllerXRComponent::new(
368 true,
369 ControllerHand::Left,
370 ControllerPoseKind::Grip,
371 ));
372 let left_grip_t = universe.world.add_component(TransformComponent::new());
373 let _ = universe.attach(left_grip, left_grip_t);
374 let left_pointer = universe.world.add_component(PointerComponent::new());
375 let _ = universe.attach(left_grip_t, left_pointer);
376 let _ = universe.attach(avatar_control, left_grip);
377
378 let right_grip = universe.world.add_component(ControllerXRComponent::new(
379 true,
380 ControllerHand::Right,
381 ControllerPoseKind::Grip,
382 ));
383 let right_grip_t = universe.world.add_component(TransformComponent::new());
384 let _ = universe.attach(right_grip, right_grip_t);
385 let right_pointer = universe.world.add_component(PointerComponent::new());
386 let _ = universe.attach(right_grip_t, right_pointer);
387 let _ = universe.attach(avatar_control, right_grip);
388
389 // model_root: no explicit Y offset — AvatarControlSystem calibrates it from J_Bip_C_Head.
390 let model_root = universe.world.add_component(TransformComponent::new());
391 let model = universe
392 .world
393 .add_component(GLTFComponent::new("assets/models/pc-rei.hoodie.glb"));
394 let emissive = universe.world.add_component(EmissiveComponent::on());
395 let _ = universe.attach(model, emissive);
396
397 let _ = universe.attach(editor_root, avatar_input_xr);
398 let _ = universe.attach(avatar_control, model_root);
399 let _ = universe.attach(model_root, model);
400 universe.add(editor_root);
401
402 // --- Controller debug cubes (tracked poses) ---
403 let _left = spawn_controller_cube(
404 &mut universe,
405 xr_rig,
406 ControllerHand::Left,
407 (0.10, 0.90, 1.00, 1.0),
408 220.0,
409 options.xr_controller_rotation_filter,
410 );
411 let _right = spawn_controller_cube(
412 &mut universe,
413 xr_rig,
414 ControllerHand::Right,
415 (1.00, 0.35, 0.35, 1.0),
416 220.0,
417 options.xr_controller_rotation_filter,
418 );
419
420 // Enable OpenXR runtime.
421 let xr_root = universe.world.add_component(XrComponent::on());
422 universe.add(xr_root);
423
424 universe.systems.process_commands(
425 &mut universe.world,
426 &mut universe.visuals,
427 &mut universe.render_assets,
428 &mut universe.command_queue,
429 );
430
431 // Force the glTF subtree to spawn so we can query the armature for bone markers.
432 {
433 let systems = &mut universe.systems;
434 systems.gltf.tick_with_queue(
435 &mut universe.world,
436 &mut universe.visuals,
437 &mut systems.skinned_mesh,
438 &mut universe.command_queue,
439 0.0,
440 );
441 }
442 universe.systems.process_commands(
443 &mut universe.world,
444 &mut universe.visuals,
445 &mut universe.render_assets,
446 &mut universe.command_queue,
447 );
448
449 // Bone markers for editor inspection.
450 let marker_joints: &[(&str, (f32, f32, f32, f32))] = &[
451 ("[name='J_Bip_C_Head']", (0.85, 0.20, 0.85, 0.9)),
452 ("[name='J_Bip_C_Neck']", (0.20, 0.85, 0.85, 0.9)),
453 ("[name='J_Bip_C_UpperChest']", (0.20, 0.20, 0.85, 0.9)),
454 ("[name='J_Bip_L_UpperArm']", (0.85, 0.85, 0.20, 0.9)),
455 ("[name='J_Bip_R_UpperArm']", (0.85, 0.60, 0.20, 0.9)),
456 ];
457
458 for &(selector, color) in marker_joints {
459 let Some(bone) = universe.find_component(model_root, selector) else {
460 continue;
461 };
462 let marker_t = universe
463 .world
464 .add_component(TransformComponent::new().with_scale(0.025, 0.025, 0.025));
465 let marker_r = universe.world.add_component(RenderableComponent::cube());
466 let marker_c = universe
467 .world
468 .add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
469 let marker_rcast = universe
470 .world
471 .add_component(RaycastableComponent::enabled());
472 let _ = universe.world.add_child(marker_r, marker_c);
473 let _ = universe.world.add_child(marker_r, marker_rcast);
474 let _ = universe.world.add_child(marker_t, marker_r);
475 let _ = universe.attach(bone, marker_t);
476 }
477
478 universe.enable_repl();
479 engine::Windowing::run_app(universe).expect("Windowing failed");
480}14fn main() {
15 mittens_engine::example_support::ensure_model_assets();
16 utils::logger::init();
17
18 let world = engine::ecs::World::default();
19 let mut universe = engine::Universe::new(world);
20
21 // Slow the global beat clock so beat-based animations run half as fast.
22 let clock = universe
23 .world
24 .add_component(ClockComponent::new().with_bpm(60.0));
25 universe.add(clock);
26
27 // Light pink background.
28 let background = universe
29 .world
30 .add_component(BackgroundColorComponent::new());
31 let background_c = universe
32 .world
33 .add_component(ColorComponent::rgba(1.0, 0.82, 0.90, 1.0));
34 let _ = universe.world.add_child(background, background_c);
35 universe.add(background);
36
37 // Small ambient so shadowed areas aren't pure black.
38 let ambient = universe
39 .world
40 .add_component(AmbientLightComponent::rgb(0.10, 0.10, 0.12));
41 universe.add(ambient);
42
43 // --- Camera rig (WASD + mouse) ---
44 let input = universe
45 .world
46 .add_component(InputComponent::new().with_speed(1.5));
47 let input_mode = universe.world.add_component(
48 InputTransformModeComponent::forward_z()
49 .with_fps_rotation()
50 .with_roll_axis_y(),
51 );
52 let _ = universe.attach(input, input_mode);
53
54 // Start slightly pulled back looking towards the origin.
55 let rig_transform = universe
56 .world
57 .add_component(TransformComponent::new().with_position(0.0, 0.0, 6.0));
58 let _ = universe.attach(input, rig_transform);
59
60 let camera3d = universe.world.add_component(Camera3DComponent::new());
61 let _ = universe.attach(rig_transform, camera3d);
62
63 // Pointer so gizmos can be interacted with.
64 let pointer = universe.world.add_component(PointerComponent::new());
65 let _ = universe.attach(camera3d, pointer);
66
67 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
68 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
69
70 universe.add(input);
71
72 // --- lighting ---
73 let sun = universe.world.add_component(
74 DirectionalLightComponent::new()
75 .with_intensity(1.2)
76 .with_color(1.0, 0.98, 0.94),
77 );
78 let sun_dir = universe
79 .world
80 .add_component(TransformComponent::new().with_position(0.0, -0.35, 1.0));
81 let _ = universe.attach(sun_dir, sun);
82 universe.add(sun_dir);
83
84 let light_transform = universe.world.add_component(
85 TransformComponent::new()
86 .with_position(1.0, 6.0, 3.0)
87 .with_scale(0.1, 0.1, 0.1),
88 );
89 let light = universe.world.add_component(
90 engine::ecs::component::PointLightComponent::new()
91 .with_distance(120.0)
92 .with_color(1.0, 1.0, 1.0),
93 );
94 let _ = universe.attach(light_transform, light);
95 universe.add(light_transform);
96
97 // --- VTuber model ---
98 let model_uri = "assets/models/pc-rei.hoodie.glb";
99
100 // Wrap the model subtree in an editor root so transform-only glTF nodes can be visualized
101 // (and thus raycasted/selected) without affecting non-editor scenes.
102 let editor_root = universe.world.add_component(EditorComponent::new());
103
104 let model_root = universe.world.add_component(TransformComponent::new());
105 let model = universe.world.add_component(GLTFComponent::new(model_uri));
106
107 // emissive for pc-rei
108 let emissive = universe.world.add_component(EmissiveComponent::on());
109 let _ = universe.attach(model, emissive);
110
111 let xr_input = universe.world.add_component(InputXRComponent::on());
112 let xr_gamepad = universe
113 .world
114 .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
115 let xr_head = universe.world.add_component(TransformComponent::new());
116 let xr_camera = universe.world.add_component(CameraXRComponent::on());
117 let _ = universe.attach(xr_input, xr_head);
118 let _ = universe.attach(xr_input, xr_gamepad);
119 let _ = universe.attach(xr_head, xr_camera);
120 let xr_pointer = universe.world.add_component(PointerComponent::new());
121 let _ = universe.attach(xr_camera, xr_pointer);
122 let _ = universe.attach(xr_head, editor_root);
123
124 let _ = universe.attach(model_root, model);
125
126 let _ = universe.attach(editor_root, model_root);
127
128 // Initialize the editor root so GLTFComponent gets registered.
129 universe.add(xr_input);
130 universe.add(editor_root);
131
132 // --- Background clouds (occluded + lit) ---
133 let bg_root = universe.world.add_component(
134 engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
135 );
136 universe.add(bg_root);
137 let mut cloud_params = example_util::CloudRingParams::default();
138 cloud_params.cloud_count = 8; // +3 clusters
139 cloud_params.angle_jitter = 0.35;
140 cloud_params.high_y_probability = 0.5;
141 cloud_params.high_y_multiplier = 1.5;
142 cloud_params.seed = 0x57_55_B0_01u32;
143 example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
144
145 // --- Simple environment ---
146 let spawn_cube = |universe: &mut engine::Universe,
147 position: (f32, f32, f32),
148 scale: (f32, f32, f32),
149 color: (f32, f32, f32, f32)| {
150 let transform = universe.world.add_component(
151 TransformComponent::new()
152 .with_position(position.0, position.1, position.2)
153 .with_scale(scale.0, scale.1, scale.2),
154 );
155 let renderable = universe.world.add_component(RenderableComponent::cube());
156 let color = universe
157 .world
158 .add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
159
160 let _ = universe.attach(transform, renderable);
161 let _ = universe.attach(renderable, color);
162
163 universe.add(transform);
164 };
165
166 // floor
167 spawn_cube(
168 &mut universe,
169 (0.0, -0.05, 0.0),
170 (10.0, 0.1, 10.0),
171 (0.92, 0.92, 0.92, 1.0),
172 );
173
174 // back wall
175 spawn_cube(
176 &mut universe,
177 (-3.0, 1.5, -5.0),
178 (3.0, 3.0, 1.0),
179 (0.95, 0.94, 0.96, 1.0),
180 );
181
182 // desk
183 spawn_cube(
184 &mut universe,
185 (0.0, 0.35, 1.0),
186 (1.0, 0.75, 0.5),
187 (0.75, 0.70, 0.65, 1.0),
188 );
189
190 // --- Editor-side stacked cubes (inside the editor subtree for picking/gizmos) ---
191 {
192 let spawn_editor_cube = |universe: &mut engine::Universe,
193 editor_root: engine::ecs::ComponentId,
194 name: &str,
195 position: (f32, f32, f32),
196 scale: (f32, f32, f32),
197 color: (f32, f32, f32, f32)| {
198 let transform = universe.world.add_component_boxed_named(
199 format!("{name}_t"),
200 Box::new(
201 TransformComponent::new()
202 .with_position(position.0, position.1, position.2)
203 .with_scale(scale.0, scale.1, scale.2),
204 ),
205 );
206 let renderable = universe.world.add_component_boxed_named(
207 format!("{name}_r"),
208 Box::new(RenderableComponent::cube()),
209 );
210 let color_comp = universe.world.add_component_boxed_named(
211 format!("{name}_color"),
212 Box::new(ColorComponent::rgba(color.0, color.1, color.2, color.3)),
213 );
214 let raycastable = universe.world.add_component_boxed_named(
215 format!("{name}_raycastable"),
216 Box::new(RaycastableComponent::enabled()),
217 );
218
219 let _ = universe.world.add_child(transform, renderable);
220 let _ = universe.world.add_child(renderable, color_comp);
221 let _ = universe.world.add_child(renderable, raycastable);
222
223 // One attach into the initialized editor subtree triggers init for the new subtree.
224 let _ = universe.attach(editor_root, transform);
225 };
226
227 // Place the stack beside the desk (a bit to the right).
228 let stack_x = 1.35;
229 let stack_z = 1.0;
230 let s = 0.25;
231 let half = 0.5 * s;
232 let light_brown = (0.80, 0.72, 0.55, 1.0);
233 let cyan = (0.20, 1.00, 1.00, 1.0);
234
235 spawn_editor_cube(
236 &mut universe,
237 editor_root,
238 "editor_stack_0",
239 (stack_x, half, stack_z),
240 (s, s, s),
241 light_brown,
242 );
243 spawn_editor_cube(
244 &mut universe,
245 editor_root,
246 "editor_stack_1",
247 (stack_x, half + 1.0 * s, stack_z),
248 (s, s, s),
249 light_brown,
250 );
251 spawn_editor_cube(
252 &mut universe,
253 editor_root,
254 "editor_stack_2",
255 (stack_x, half + 2.0 * s, stack_z),
256 (s, s, s),
257 light_brown,
258 );
259 spawn_editor_cube(
260 &mut universe,
261 editor_root,
262 "editor_stack_top",
263 (stack_x, half + 3.0 * s, stack_z),
264 (s, s, s),
265 cyan,
266 );
267 }
268
269 let xr_root = universe
270 .world
271 .add_component(engine::ecs::component::XrComponent::on());
272 universe.add(xr_root);
273
274 universe.systems.process_commands(
275 &mut universe.world,
276 &mut universe.visuals,
277 &mut universe.render_assets,
278 &mut universe.command_queue,
279 );
280
281 // Spawn the glTF subtree once up-front so joint ComponentIds exist.
282 {
283 let systems = &mut universe.systems;
284 systems.gltf.tick_with_queue(
285 &mut universe.world,
286 &mut universe.visuals,
287 &mut systems.skinned_mesh,
288 &mut universe.command_queue,
289 0.0,
290 );
291 }
292 universe.systems.process_commands(
293 &mut universe.world,
294 &mut universe.visuals,
295 &mut universe.render_assets,
296 &mut universe.command_queue,
297 );
298
299 // Register imported meshes into RenderAssets early so we can inspect skin weights
300 // (textures will still be uploaded later during normal rendering).
301 universe
302 .systems
303 .gltf
304 .flush_mesh_imports_only(&mut universe.render_assets);
305
306 // --- Joint printout + animation binding (in the example) ---
307 let all_joints = collect_joint_transforms(
308 &universe.world,
309 &universe.systems.skinned_mesh,
310 &universe.visuals,
311 model,
312 model_root,
313 );
314 println!("[vtuber-joints-example] joints found: {}", all_joints.len());
315 for (i, (node_index, joint_tx)) in all_joints.iter().enumerate() {
316 println!(" joint[{i:03}] node_index={node_index} transform={joint_tx:?}");
317 }
318
319 let node_index_to_transform: HashMap<usize, engine::ecs::ComponentId> =
320 all_joints.iter().copied().collect();
321
322 // Example settings are hardcoded to keep this example simple.
323 let joint_offset: usize = 0;
324 let wiggle_count: usize = 16;
325
326 let target_mesh_key = "pc-rei.hoodie:Body_(merged).baked:prim0".to_string();
327 let target_joint_names: Vec<String> = vec![
328 "J_Bip_L_UpperArm".to_string(),
329 "J_Bip_R_UpperArm".to_string(),
330 ];
331
332 let print_transform_updates: bool = false;
333
334 let selected_joint_transforms: Vec<(usize, engine::ecs::ComponentId)> =
335 select_named_joints(&universe.world, &all_joints, &target_joint_names)
336 .or_else(|| {
337 select_body_prim0_influencers(
338 &universe,
339 model_root,
340 &target_mesh_key,
341 wiggle_count,
342 &node_index_to_transform,
343 )
344 })
345 .unwrap_or_else(|| select_joint_range(&all_joints, joint_offset, wiggle_count));
346 println!(
347 "[vtuber-joints-example] wiggle selection: target_mesh_key='{}' offset={} count={} selected={}",
348 target_mesh_key,
349 joint_offset,
350 wiggle_count,
351 selected_joint_transforms.len()
352 );
353
354 debug_print_selected_joint_influence(
355 &universe,
356 &target_mesh_key,
357 model_root,
358 &selected_joint_transforms,
359 );
360
361 println!("[vtuber-joints-example] selected joints:");
362 for (i, (node_index, joint_tx)) in selected_joint_transforms.iter().enumerate() {
363 let name = universe
364 .world
365 .get_component_record(*joint_tx)
366 .map(|n| n.name.as_str())
367 .unwrap_or("<unknown>");
368 println!(" sel[{i:02}] node_index={node_index} name={name} transform={joint_tx:?}");
369 }
370
371 if print_transform_updates {
372 println!("[vtuber-joints-example] note: joint animation disabled");
373 }
374
375 universe.systems.process_commands(
376 &mut universe.world,
377 &mut universe.visuals,
378 &mut universe.render_assets,
379 &mut universe.command_queue,
380 );
381
382 universe.enable_repl();
383 engine::Windowing::run_app(universe).expect("Windowing failed");
384}pub fn with_active(self, active: bool) -> Self
pub fn with_transform_gizmo_translation_space( self, space: TransformGizmoCoordSpace, ) -> Self
pub fn with_transform_gizmo_rotation_space( self, space: TransformGizmoCoordSpace, ) -> Self
pub fn with_interaction_mode(self, mode: EditorInteractionMode) -> Self
Sourcepub fn with_panels(self, enabled: bool) -> Self
pub fn with_panels(self, enabled: bool) -> Self
Suppress automatic panel spawning. Call as .with_panels(false).
pub fn with_serialize_editor_panels(self, enabled: bool) -> Self
Sourcepub fn with_panel_positions(
self,
world_panel: (f32, f32, f32),
inspector_panel: (f32, f32, f32),
) -> Self
pub fn with_panel_positions( self, world_panel: (f32, f32, f32), inspector_panel: (f32, f32, f32), ) -> Self
Override panel positions (world_panel, inspector_panel).
pub fn id(&self) -> Option<ComponentId>
Trait Implementations§
Source§impl Clone for EditorComponent
impl Clone for EditorComponent
Source§fn clone(&self) -> EditorComponent
fn clone(&self) -> EditorComponent
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 EditorComponent
impl Component for EditorComponent
fn set_id(&mut self, component: ComponentId)
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
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 to_mms_ast(&self, _world: &World) -> ComponentExpression
fn to_mms_ast(&self, _world: &World) -> ComponentExpression
Source§fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
impl Copy for EditorComponent
Source§impl Debug for EditorComponent
impl Debug for EditorComponent
Auto Trait Implementations§
impl Freeze for EditorComponent
impl RefUnwindSafe for EditorComponent
impl Send for EditorComponent
impl Sync for EditorComponent
impl Unpin for EditorComponent
impl UnsafeUnpin for EditorComponent
impl UnwindSafe for EditorComponent
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.