Skip to main content

pfx_play/world/
rig.rs

1use std::collections::BTreeMap;
2
3use pfx_core::rig::{Blend, Input as RigInput, Probe, Vec3, View};
4use pfx_input::Input;
5use pfx_load::scene::Projection;
6use pfx_physics::rigid::{self, Filter, Pose, Shape};
7
8use crate::rigs::{Kind, RigDesc, Rigs};
9use crate::world::{ObjectId, World};
10
11const ASPECT: f32 = 16.0 / 9.0;
12
13pub(super) struct Driver {
14    rigs: BTreeMap<String, RigDesc>,
15    active: Option<String>,
16    blend: Option<Blend>,
17    aspect: f32,
18    mouse: [f32; 2],
19    pointer: Option<[f32; 2]>,
20    grounded: bool,
21}
22
23impl Driver {
24    pub(super) fn new(rigs: Rigs) -> Self {
25        Self {
26            rigs: rigs.into_map(),
27            active: None,
28            blend: None,
29            aspect: ASPECT,
30            mouse: [0.0; 2],
31            pointer: None,
32            grounded: true,
33        }
34    }
35}
36
37struct Cast<'a> {
38    physics: &'a rigid::World,
39    filter: Filter,
40}
41
42impl Probe for Cast<'_> {
43    fn clear(&self, from: Vec3, direction: Vec3, radius: f32, max: f32) -> f32 {
44        let shape = Shape::Sphere {
45            radius: radius.max(1e-3),
46        };
47        match self
48            .physics
49            .shape_cast(&shape, Pose::at(from), direction, max, self.filter)
50        {
51            Some(hit) => hit.distance.min(max),
52            None => max,
53        }
54    }
55}
56
57impl World {
58    pub fn rig_names(&self) -> impl Iterator<Item = &str> {
59        self.driver.rigs.keys().map(String::as_str)
60    }
61
62    pub fn rig_desc(&self, name: &str) -> Option<&RigDesc> {
63        self.driver.rigs.get(name)
64    }
65
66    pub fn rig_state(&self, name: &str) -> Option<&pfx_core::rig::Rig> {
67        self.driver.rigs.get(name).map(|desc| &desc.rig)
68    }
69
70    pub fn rig_state_mut(&mut self, name: &str) -> Option<&mut pfx_core::rig::Rig> {
71        self.driver.rigs.get_mut(name).map(|desc| &mut desc.rig)
72    }
73
74    pub fn active_rig(&self) -> Option<&str> {
75        self.driver.active.as_deref()
76    }
77
78    pub fn rig(&mut self, name: &str) -> bool {
79        if self.driver.active.as_deref() == Some(name) {
80            return true;
81        }
82        let Some(desc) = self.driver.rigs.get_mut(name) else {
83            return false;
84        };
85        desc.rig.restart();
86        let blend = desc.blend;
87        let plane = desc.kind == Kind::Follow2d;
88        let height = desc.height;
89        let from = View {
90            at: self.camera.at,
91            look_at: self.camera.look_at,
92            up: self.camera.up,
93        };
94        self.driver.blend = (blend > 0.0).then(|| Blend::new(from, blend));
95        self.driver.active = Some(name.to_string());
96        if plane && let Some(height) = height {
97            self.camera.projection = Projection::Orthographic { height };
98        }
99        true
100    }
101
102    pub fn rig_off(&mut self) {
103        self.driver.active = None;
104        self.driver.blend = None;
105    }
106
107    pub fn rig_target(&mut self, name: &str, target: Option<&str>) -> bool {
108        if let Some(object) = target
109            && self.object(object).is_none()
110        {
111            return false;
112        }
113        let Some(desc) = self.driver.rigs.get_mut(name) else {
114            return false;
115        };
116        desc.target = target.map(str::to_string);
117        desc.rig.hand_over();
118        true
119    }
120
121    pub fn rig_look(&mut self, mouse: [f32; 2]) {
122        self.driver.mouse[0] += mouse[0];
123        self.driver.mouse[1] += mouse[1];
124    }
125
126    pub fn set_view_aspect(&mut self, aspect: f32) {
127        if aspect.is_finite() && aspect > 0.0 {
128            self.driver.aspect = aspect;
129        }
130    }
131
132    fn rig_target_id(&self, target: &Option<String>) -> Option<ObjectId> {
133        target.as_deref().and_then(|name| self.object(name))
134    }
135
136    pub(crate) fn step_rigs(&mut self, input: &Input) {
137        let mouse = std::mem::take(&mut self.driver.mouse);
138        let pointer = input.pointer().layout;
139        let moved = match (self.driver.pointer, pointer) {
140            (Some(before), Some(now)) => [now[0] - before[0], now[1] - before[1]],
141            _ => [0.0; 2],
142        };
143        self.driver.pointer = pointer;
144        let Some(name) = &self.driver.active else {
145            return;
146        };
147        let Some(desc) = self.driver.rigs.get(name) else {
148            return;
149        };
150        let id = self.rig_target_id(&desc.target);
151        let target = id.map_or([0.0; 3], |id| self.at(id));
152        let aspect = self.driver.aspect;
153        let state = input.action(&desc.look).vector;
154        let mouse = if desc.mouse {
155            [mouse[0] + moved[0], mouse[1] + moved[1]]
156        } else {
157            mouse
158        };
159        let mut rig_input = RigInput::at(target).looking(state).mouse(mouse);
160        rig_input.grounded = self.driver.grounded;
161        let mut filter = Filter::layers(desc.collide);
162        if let Some(body) = id.and_then(|id| self.body(id)) {
163            filter = filter.excluding(body);
164        }
165        let comfort = self.fx.comfort;
166        let dt = self.clock.tick_dt() as f32;
167        let probe = Cast {
168            physics: &self.physics,
169            filter,
170        };
171        let height = desc.height;
172        let fit = desc.fit_view;
173        let desc = self.driver.rigs.get_mut(name).expect("the rig is there");
174        if fit && let (Some(follow), Some(height)) = (desc.rig.follow_mut(), height) {
175            follow.set_view_half([height * aspect * 0.5, height * 0.5]);
176        }
177        desc.rig.tick(dt, &rig_input, comfort, &probe);
178        if let Some(blend) = &mut self.driver.blend {
179            blend.step(dt);
180        }
181        if self.driver.blend.is_some_and(|blend| blend.done()) {
182            self.driver.blend = None;
183        }
184    }
185
186    pub fn rig_grounded(&mut self, grounded: bool) {
187        self.driver.grounded = grounded;
188    }
189
190    pub(crate) fn apply_rig(&mut self, alpha: f32) {
191        let Some(name) = &self.driver.active else {
192            return;
193        };
194        let Some(desc) = self.driver.rigs.get(name) else {
195            return;
196        };
197        if !desc.rig.started() {
198            return;
199        }
200        let mut view = desc.rig.pose(alpha);
201        if let Some(blend) = &self.driver.blend {
202            view = blend.apply(view);
203        }
204        self.camera.at = view.at;
205        self.camera.look_at = view.look_at;
206        self.camera.up = view.up;
207    }
208}