Skip to main content

vtuber_mirror_example/
vtuber-mirror-example.rs

1use 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}