vtuber_mirror_example/
vtuber-mirror-example.rs1use mittens_engine::engine::ecs::component::{ControllerHand, ControllerXRComponent};
2use mittens_engine::engine::ecs::{
3 ComponentId, EventSignal, Signal, SignalEmitter, SignalKind, World,
4};
5use mittens_engine::{engine, scripting, utils};
6
7#[path = "example_util/mod.rs"]
8mod example_util;
9
10fn component_label(world: &World, id: ComponentId) -> String {
11 world.component_name(id).unwrap_or("?").to_string()
12}
13
14fn controller_hand_for_component(world: &World, start: ComponentId) -> Option<ControllerHand> {
15 let mut cur = start;
16 loop {
17 if let Some(ctrl) = world.get_component_by_id_as::<ControllerXRComponent>(cur) {
18 return Some(ctrl.hand);
19 }
20 cur = world.parent_of(cur)?;
21 }
22}
23
24fn on_xr_pointer_event(world: &mut World, _emit: &mut dyn SignalEmitter, env: &Signal) {
25 let Some(event) = env.event.as_ref() else {
26 return;
27 };
28 let (kind, raycaster, renderable, hit_point) = match event {
29 EventSignal::DragStart {
30 raycaster,
31 renderable,
32 hit_point,
33 ..
34 } => ("DragStart", *raycaster, *renderable, Some(*hit_point)),
35 EventSignal::DragMove {
36 raycaster,
37 renderable,
38 hit_point,
39 ..
40 } => ("DragMove", *raycaster, *renderable, Some(*hit_point)),
41 EventSignal::DragEnd {
42 raycaster,
43 renderable,
44 hit_point,
45 } => ("DragEnd", *raycaster, *renderable, *hit_point),
46 EventSignal::Click {
47 raycaster,
48 renderable,
49 hit_point,
50 ..
51 } => ("Click", *raycaster, *renderable, Some(*hit_point)),
52 _ => return,
53 };
54
55 let Some(hand) = controller_hand_for_component(world, raycaster) else {
56 return;
57 };
58 let hand_label = match hand {
59 ControllerHand::Left => "Left",
60 ControllerHand::Right => "Right",
61 };
62 let renderable_name = component_label(world, renderable);
63 let ray_name = component_label(world, raycaster);
64 if let Some(p) = hit_point {
65 println!(
66 "[xr-pointer] hand={} kind={} raycaster={} renderable={} hit=[{:+.3},{:+.3},{:+.3}]",
67 hand_label, kind, ray_name, renderable_name, p[0], p[1], p[2]
68 );
69 } else {
70 println!(
71 "[xr-pointer] hand={} kind={} raycaster={} renderable={}",
72 hand_label, kind, ray_name, renderable_name
73 );
74 }
75}
76
77fn main() {
78 mittens_engine::example_support::ensure_model_assets();
79 utils::logger::init();
80
81 let output = scripting::MeowMeowRunner::eval(include_str!("vtuber-mirror-example.mms"));
82
83 for error in &output.errors {
84 eprintln!("[mms] {error}");
85 }
86 println!(
87 "[mms] {} intent(s) from vtuber-mirror-example.mms",
88 output.intents.len()
89 );
90 println!(
91 "[vtuber-mirror-example] expected XR-only views: 2 XR eye views plus mirror-derived views; no desktop scene view unless a Camera3D/Camera2D is added"
92 );
93
94 let world = engine::ecs::World::default();
95 let mut universe = engine::Universe::new(world);
96
97 let scope = engine::ecs::ComponentId::default();
98 for intent in output.intents {
99 universe.command_queue.push_intent_now(scope, intent);
100 }
101
102 universe.systems.process_commands(
103 &mut universe.world,
104 &mut universe.visuals,
105 &mut universe.render_assets,
106 &mut universe.command_queue,
107 );
108
109 let bg_root = universe.world.add_component(
110 engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
111 );
112 universe.add(bg_root);
113
114 let cloud_params = example_util::CloudRingParams {
115 cloud_count: 10,
116 radius: 34.0,
117 center_y: 8.5,
118 puffs_per_cloud: 28,
119 angle_jitter: 0.30,
120 high_y_probability: 0.45,
121 high_y_multiplier: 1.28,
122 seed: 0x57_55_B0_0Au32,
123 };
124 example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
125
126 for kind in [
127 SignalKind::DragStart,
128 SignalKind::DragMove,
129 SignalKind::DragEnd,
130 SignalKind::Click,
131 ] {
132 universe
133 .systems
134 .rx
135 .add_global_handler(kind, on_xr_pointer_event);
136 }
137
138 universe.enable_repl();
139 engine::Windowing::run_app(universe).expect("Windowing failed");
140}