1use std::any::Any;
2use std::collections::{BTreeMap, BTreeSet};
3use std::sync::Arc;
4
5use pfx_core::clock::{Clock, HitStop};
6use pfx_core::fx::{Comfort, Shake, ShakeTuning};
7use pfx_core::sim::Rng;
8use pfx_gpu::screens::DisplaySettings;
9use pfx_input::{PadId, Rumble};
10use pfx_live::renderer::invert;
11use pfx_load::scene::{
12 Body, BodyKind, BodyShape, Camera, Cue, Light, Matrix, Scene, model, multiply,
13};
14use pfx_materials::Material;
15use pfx_physics::rigid::{
16 self, BodyDesc, BodyId, Character, CharacterDesc, ColliderDesc, ColliderId, Combine, Event,
17 Filter, Friction, Pose, Shape, WorldDesc,
18};
19use pfx_sound::{ChannelId, Clip, Level, Mixer, MusicTrack, PcmSender, VoiceId};
20
21use crate::PlayError;
22use crate::events::{Phase, TriggerEvent, WorldEvent};
23use crate::layers::Layers;
24use crate::math;
25use crate::rigs::Rigs;
26use crate::tunables::{FromTunable, TunableValue, Tunables};
27
28mod anim;
29mod characters;
30mod edits;
31mod query;
32mod rig;
33
34pub use anim::{Animate, AnimationEvent};
35pub use query::{Hit, Overlap, Query, QueryError};
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub struct SoundLevel {
39 pub volume: f32,
40 pub pan: f32,
41 pub looping: bool,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45struct Physical {
46 id: BodyId,
47 collider: ColliderId,
48 desc: Body,
49 groups: rigid::Layers,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq)]
53struct Sensor {
54 body: BodyId,
55 follows: bool,
56 pose: Pose,
57 shape: BodyShape,
58 offset: [f32; 3],
59 detects: u32,
60}
61
62pub(crate) fn shape(shape: BodyShape) -> Shape {
63 match shape {
64 BodyShape::Box { half } => Shape::Box { half },
65 BodyShape::Sphere { radius } => Shape::Sphere { radius },
66 BodyShape::Capsule {
67 half_height,
68 radius,
69 } => Shape::Capsule {
70 half_height,
71 radius,
72 },
73 BodyShape::Cylinder {
74 half_height,
75 radius,
76 } => Shape::Cylinder {
77 half_height,
78 radius,
79 },
80 }
81}
82
83pub fn body_volume(shape: BodyShape) -> f32 {
84 match shape {
85 BodyShape::Box { half } => 8.0 * half[0] * half[1] * half[2],
86 BodyShape::Sphere { radius } => 4.0 / 3.0 * std::f32::consts::PI * radius.powi(3),
87 BodyShape::Capsule {
88 half_height,
89 radius,
90 } => {
91 std::f32::consts::PI * radius * radius * 2.0 * half_height
92 + 4.0 / 3.0 * std::f32::consts::PI * radius.powi(3)
93 }
94 BodyShape::Cylinder {
95 half_height,
96 radius,
97 } => std::f32::consts::PI * radius * radius * 2.0 * half_height,
98 }
99}
100
101pub(crate) fn collider(body: &Body) -> ColliderDesc {
102 ColliderDesc::new(shape(body.shape))
103 .with_offset(Pose::at(body.offset))
104 .with_density(body.density)
105 .with_friction(Friction::uniform(body.friction))
106 .with_restitution(body.restitution, Combine::Average)
107}
108
109pub(crate) fn exposure_of(scene: &Scene) -> f32 {
110 scene
111 .finish
112 .as_ref()
113 .and_then(|finish| finish.chain.passes.first())
114 .and_then(|pass| match pass {
115 pfx_post::Pass::Exposure(value) => Some(*value),
116 _ => None,
117 })
118 .unwrap_or(1.0)
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
122pub struct ObjectId(usize);
123
124#[derive(Clone, Copy, Debug, PartialEq)]
125pub enum RumbleRequest {
126 Active(Rumble),
127 Pad(PadId, Rumble),
128}
129
130impl ObjectId {
131 pub(crate) fn from_index(place: usize) -> Self {
132 Self(place)
133 }
134
135 pub fn index(self) -> usize {
136 self.0
137 }
138}
139
140#[derive(Clone, Debug, Default, PartialEq)]
141pub struct Look {
142 pub material: Option<String>,
143 pub color: Option<[f32; 3]>,
144 pub emission: Option<[f32; 3]>,
145}
146
147impl Look {
148 pub fn is_plain(&self) -> bool {
149 *self == Self::default()
150 }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq)]
154pub struct Contact {
155 pub a: ObjectId,
156 pub b: ObjectId,
157 pub impulse: f32,
158}
159
160#[derive(Clone, Debug, PartialEq)]
161struct Entry {
162 name: String,
163 parent: Option<usize>,
164 local: Matrix,
165 scale: [f32; 3],
166 hidden: bool,
167 drawn: bool,
168 look: Look,
169}
170
171#[derive(Clone, Copy, Debug, PartialEq)]
172pub struct Fx {
173 pub shake: Shake,
174 pub comfort: Comfort,
175}
176
177impl Default for Fx {
178 fn default() -> Self {
179 Self {
180 shake: Shake::new(ShakeTuning::default()),
181 comfort: Comfort::FULL,
182 }
183 }
184}
185
186impl Fx {
187 pub fn camera(&self, camera: &Camera) -> Camera {
188 let offset = self.shake.offset(self.comfort.amount());
189 if offset.is_zero() {
190 return *camera;
191 }
192 let (_, right, up) = camera.axes();
193 let shift: [f32; 3] = std::array::from_fn(|axis| {
194 right[axis] * offset.translation[0] + up[axis] * offset.translation[1]
195 });
196 let (sin, cos) = offset.roll.sin_cos();
197 Camera {
198 at: std::array::from_fn(|axis| camera.at[axis] + shift[axis]),
199 look_at: std::array::from_fn(|axis| camera.look_at[axis] + shift[axis]),
200 up: std::array::from_fn(|axis| up[axis] * cos - right[axis] * sin),
201 ..*camera
202 }
203 }
204}
205
206#[derive(Clone, Debug, PartialEq, Eq)]
207pub enum Exit {
208 Quit(u8),
209 Failed(String),
210}
211
212impl Exit {
213 pub fn code(&self) -> u8 {
214 match self {
215 Exit::Quit(code) => *code,
216 Exit::Failed(_) => 1,
217 }
218 }
219}
220
221pub struct PcmStream {
222 voice: VoiceId,
223 sender: PcmSender,
224}
225
226impl PcmStream {
227 pub fn push(&self, samples: &[f32]) -> usize {
228 self.sender.push(samples)
229 }
230
231 pub fn room(&self) -> usize {
232 self.sender.room_frames()
233 }
234
235 pub fn queued(&self) -> usize {
236 self.sender.queued_frames()
237 }
238
239 pub fn capacity(&self) -> usize {
240 self.sender.capacity_frames()
241 }
242
243 pub fn channels(&self) -> u16 {
244 self.sender.channels()
245 }
246
247 pub fn voice(&self) -> VoiceId {
248 self.voice
249 }
250}
251
252pub struct Sounds {
253 mixer: Mixer,
254 clips: BTreeMap<String, Clip>,
255 voices: BTreeMap<String, VoiceId>,
256 log: Vec<(u64, String)>,
257 audio: Vec<f32>,
258 keep: usize,
259 rendered: u64,
260 now: u64,
261}
262
263impl Sounds {
264 fn new(scene: &Scene, rate: u32, channels: u16, seed: u64) -> Result<Self, PlayError> {
265 let mut mixer = Mixer::new(rate.max(1), channels.max(1));
266 mixer.set_seed(seed);
267 let mut clips = BTreeMap::new();
268 for (name, sound) in &scene.sounds {
269 let clip = if sound.bytes.starts_with(b"OggS") {
270 Clip::from_vorbis(&sound.bytes).map_err(|message| {
271 PlayError::Sound(format!(
272 "sound {name}: {} does not decode as Ogg Vorbis: {message}",
273 sound.file.display()
274 ))
275 })?
276 } else {
277 Clip::from_wav(&sound.bytes).ok_or_else(|| {
278 PlayError::Sound(format!(
279 "sound {name}: {} does not decode as a WAV",
280 sound.file.display()
281 ))
282 })?
283 };
284 clips.insert(name.clone(), clip);
285 }
286 Ok(Self {
287 keep: rate.max(1) as usize * channels.max(1) as usize * 2,
288 mixer,
289 clips,
290 voices: BTreeMap::new(),
291 log: Vec::new(),
292 audio: Vec::new(),
293 rendered: 0,
294 now: 0,
295 })
296 }
297
298 pub fn play_clip(&mut self, name: &str, level: Level, looping: bool) -> bool {
299 let Some(clip) = self.clips.get(name) else {
300 return false;
301 };
302 let voice = if looping {
303 self.mixer
304 .play_music(&MusicTrack::looping(clip.clone()), 0.0, level)
305 } else {
306 self.mixer.play(clip, level)
307 };
308 self.voices.insert(name.to_string(), voice);
309 self.log.push((self.now, name.to_string()));
310 true
311 }
312
313 pub fn stop(&mut self, name: &str, fade_seconds: f32) -> bool {
314 match self.voices.remove(name) {
315 Some(voice) => {
316 self.mixer.stop(voice, fade_seconds);
317 true
318 }
319 None => false,
320 }
321 }
322
323 pub fn names(&self) -> impl Iterator<Item = &str> {
324 self.clips.keys().map(String::as_str)
325 }
326
327 pub fn stream(
328 &mut self,
329 channel: ChannelId,
330 channels: u16,
331 buffer_seconds: f32,
332 level: Level,
333 ) -> PcmStream {
334 let (voice, sender) = self
335 .mixer
336 .pcm_stream(channel, channels, buffer_seconds, level);
337 PcmStream { voice, sender }
338 }
339
340 pub fn end_stream(&mut self, stream: PcmStream, fade_seconds: f32) {
341 stream.sender.finish();
342 self.mixer.stop(stream.voice, fade_seconds);
343 }
344
345 pub fn log(&self) -> &[(u64, String)] {
346 &self.log
347 }
348
349 pub fn mixer(&mut self) -> &mut Mixer {
350 &mut self.mixer
351 }
352
353 pub fn rate(&self) -> u32 {
354 self.mixer.rate()
355 }
356
357 pub fn channels(&self) -> u16 {
358 self.mixer.channels()
359 }
360
361 pub fn take_audio(&mut self) -> Vec<f32> {
362 std::mem::take(&mut self.audio)
363 }
364
365 fn render_until(&mut self, frame: u64) {
366 if frame <= self.rendered {
367 return;
368 }
369 let frames = (frame - self.rendered) as usize;
370 self.rendered = frame;
371 let start = self.audio.len();
372 self.audio
373 .resize(start + frames.saturating_mul(self.channels() as usize), 0.0);
374 self.mixer.render_into(&mut self.audio[start..]);
375 if self.audio.len() > self.keep {
376 let extra = self.audio.len() - self.keep;
377 let channels = self.channels().max(1) as usize;
378 self.audio.drain(..extra.div_ceil(channels) * channels);
379 }
380 }
381}
382
383pub struct World {
384 scene: Arc<Scene>,
385 objects: Vec<Entry>,
386 index: BTreeMap<String, usize>,
387 pub camera: Camera,
388 pub physics: rigid::World,
389 bodies: BTreeMap<usize, Physical>,
390 characters: BTreeMap<usize, Character>,
391 roster: BTreeMap<String, CharacterDesc>,
392 owners: BTreeMap<ColliderId, usize>,
393 sensors: BTreeMap<usize, Sensor>,
394 sensor_owners: BTreeMap<ColliderId, usize>,
395 inside: BTreeSet<(usize, usize)>,
396 movers: Vec<usize>,
397 layers: Layers,
398 events: Vec<WorldEvent>,
399 contacts: Vec<Contact>,
400 pub sounds: Sounds,
401 pub fx: Fx,
402 pub rng: Rng,
403 clock: Clock,
404 ticks: u64,
405 state: Option<Box<dyn Any>>,
406 rumble: Vec<RumbleRequest>,
407 poses: BTreeMap<usize, [[f32; 3]; 3]>,
408 materials: BTreeMap<String, Material>,
409 lights: BTreeMap<String, Light>,
410 lit: u64,
411 exposure: f32,
412 exposed: u64,
413 levels: BTreeMap<String, SoundLevel>,
414 tunables: Tunables,
415 tuned: BTreeMap<String, TunableValue>,
416 driver: rig::Driver,
417 animations: anim::Animations,
418 exit: Option<Exit>,
419 display: DisplaySettings,
420 cursor: Cursor,
421 warp: Option<[f32; 2]>,
422}
423
424#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
425pub enum Cursor {
426 #[default]
427 Shown,
428 Hidden,
429 Captured,
430}
431
432impl World {
433 pub(crate) fn new(
434 scene: Arc<Scene>,
435 seed: u64,
436 audio: (u32, u16),
437 tunables: Tunables,
438 rigs: Rigs,
439 ) -> Result<Self, PlayError> {
440 let layers = Layers::from_scene(&scene.layers).map_err(PlayError::Layers)?;
441 let settings = scene.physics_or_default();
442 let clock = Clock::new(f64::from(settings.rate))
443 .map_err(|error| PlayError::Clock(format!("{error:?}")))?;
444 let index: BTreeMap<String, usize> = scene
445 .objects
446 .iter()
447 .enumerate()
448 .map(|(place, object)| (object.name.clone(), place))
449 .collect();
450 let objects: Vec<Entry> = scene
451 .objects
452 .iter()
453 .map(|object| Entry {
454 name: object.name.clone(),
455 parent: object
456 .parent
457 .as_deref()
458 .and_then(|parent| index.get(parent).copied()),
459 local: model(object.at, object.rotate, object.scale),
460 scale: object.scale,
461 hidden: object.hidden,
462 drawn: !object.hidden,
463 look: Look::default(),
464 })
465 .collect();
466 let mut physics = rigid::World::new(WorldDesc {
467 rate: settings.rate,
468 substeps: settings.substeps,
469 iterations: settings.iterations,
470 gravity: settings.gravity,
471 ccd: true,
472 });
473 let mut bodies = BTreeMap::new();
474 let mut owners = BTreeMap::new();
475 for (name, body) in &scene.bodies {
476 let Some(&place) = index.get(name) else {
477 continue;
478 };
479 let pose = math::pose(scene.objects[place].model);
480 let desc = match body.kind {
481 BodyKind::Dynamic => BodyDesc::dynamic(pose),
482 BodyKind::Kinematic => BodyDesc::kinematic(pose),
483 BodyKind::Fixed => BodyDesc::fixed(pose),
484 };
485 let mut desc = desc
486 .with_velocity(body.velocity)
487 .with_spin(body.spin.map(f32::to_radians))
488 .with_damping(body.damping[0], body.damping[1])
489 .with_gravity_scale(body.gravity_scale);
490 if body.ccd {
491 desc = desc.with_ccd();
492 }
493 let groups =
494 match scene.body_layers.get(name) {
495 Some(layer) => layers.groups(layers.require(layer).map_err(|message| {
496 PlayError::Layers(format!("object {name}: {message}"))
497 })?),
498 None => rigid::Layers::new(u32::MAX, u32::MAX),
499 };
500 let id = physics.add_body(desc);
501 let collider = physics
502 .add_collider(id, collider(body).with_layers(groups))
503 .ok_or_else(|| PlayError::Body(format!("[body.{name}] has no usable shape")))?;
504 bodies.insert(
505 place,
506 Physical {
507 id,
508 collider,
509 desc: *body,
510 groups,
511 },
512 );
513 owners.insert(collider, place);
514 }
515 let mut roster = BTreeMap::new();
516 for (name, character) in &scene.characters {
517 let groups =
518 match &character.layer {
519 Some(layer) => layers.groups(layers.require(layer).map_err(|message| {
520 PlayError::Layers(format!("object {name}: {message}"))
521 })?),
522 None => rigid::Layers::ALL,
523 };
524 roster.insert(name.clone(), crate::characters::desc_of(character, groups));
525 }
526 let mut sensors = BTreeMap::new();
527 let mut sensor_owners = BTreeMap::new();
528 for (name, trigger) in &scene.triggers {
529 let Some(&place) = index.get(name) else {
530 continue;
531 };
532 let layer = match &trigger.layer {
533 Some(layer) => layers
534 .require(layer)
535 .map_err(|message| PlayError::Layers(format!("object {name}: {message}")))?,
536 None => u32::MAX,
537 };
538 let detects = match (&trigger.mask, &trigger.layer) {
539 (Some(mask), _) => layers
540 .mask(mask)
541 .map_err(|message| PlayError::Layers(format!("object {name}: {message}")))?,
542 (None, Some(_)) => layers.collision_mask(layer),
543 (None, None) => u32::MAX,
544 };
545 let pose = math::pose(scene.objects[place].model);
546 let (body, follows) = match bodies.get(&place) {
547 Some(physical) => (physical.id, false),
548 None => (physics.add_body(BodyDesc::kinematic(pose)), true),
549 };
550 let desc = ColliderDesc::new(shape(trigger.shape))
551 .with_offset(Pose::at(trigger.offset))
552 .with_density(0.0)
553 .with_layers(rigid::Layers::new(layer, 0))
554 .as_sensor();
555 let collider = physics
556 .add_collider(body, desc)
557 .ok_or_else(|| PlayError::Body(format!("[trigger.{name}] has no usable shape")))?;
558 sensors.insert(
559 place,
560 Sensor {
561 body,
562 follows,
563 pose,
564 shape: trigger.shape,
565 offset: trigger.offset,
566 detects,
567 },
568 );
569 sensor_owners.insert(collider, place);
570 }
571 let sounds = Sounds::new(&scene, audio.0, audio.1, seed)?;
572 let levels = scene
573 .sounds
574 .iter()
575 .map(|(name, sound)| {
576 (
577 name.clone(),
578 SoundLevel {
579 volume: sound.volume,
580 pan: sound.pan,
581 looping: sound.looping,
582 },
583 )
584 })
585 .collect();
586 let tuned = tunables
587 .iter()
588 .map(|tunable| (tunable.name.clone(), tunable.default))
589 .collect();
590 let animations =
591 anim::Animations::new(&scene, Self::rate_of(&clock)).map_err(PlayError::Animation)?;
592 let movers = mover_order(&scene, &objects);
593 let mut world = Self {
594 animations,
595 movers,
596 poses: BTreeMap::new(),
597 materials: BTreeMap::new(),
598 lights: scene.lights.clone(),
599 lit: 0,
600 exposure: exposure_of(&scene),
601 exposed: 0,
602 levels,
603 tunables,
604 tuned,
605 driver: rig::Driver::new(rigs),
606 camera: scene.camera_or_default(),
607 objects,
608 index,
609 physics,
610 bodies,
611 characters: BTreeMap::new(),
612 roster,
613 owners,
614 sensors,
615 sensor_owners,
616 inside: BTreeSet::new(),
617 layers,
618 events: Vec::new(),
619 contacts: Vec::new(),
620 sounds,
621 fx: Fx::default(),
622 rng: Rng::new(seed),
623 clock,
624 ticks: 0,
625 state: None,
626 rumble: Vec::new(),
627 exit: None,
628 display: DisplaySettings::default(),
629 cursor: Cursor::Shown,
630 warp: None,
631 scene,
632 };
633 world.physics.refresh_queries();
634 Ok(world)
635 }
636
637 pub fn scene(&self) -> &Scene {
638 &self.scene
639 }
640
641 pub fn len(&self) -> usize {
642 self.objects.len()
643 }
644
645 pub fn is_empty(&self) -> bool {
646 self.objects.is_empty()
647 }
648
649 pub fn object(&self, name: &str) -> Option<ObjectId> {
650 self.index.get(name).copied().map(ObjectId)
651 }
652
653 pub fn ids(&self) -> impl Iterator<Item = ObjectId> {
654 (0..self.objects.len()).map(ObjectId)
655 }
656
657 pub fn name(&self, id: ObjectId) -> &str {
658 &self.objects[id.0].name
659 }
660
661 pub fn parent(&self, id: ObjectId) -> Option<ObjectId> {
662 self.objects[id.0].parent.map(ObjectId)
663 }
664
665 pub fn rest(&self, id: ObjectId) -> Matrix {
666 self.scene.objects[id.0].model
667 }
668
669 pub fn local(&self, id: ObjectId) -> Matrix {
670 self.objects[id.0].local
671 }
672
673 pub fn set_local(&mut self, id: ObjectId, local: Matrix) {
674 self.objects[id.0].local = local;
675 }
676
677 pub fn model(&self, id: ObjectId) -> Matrix {
678 model_in(&self.objects, id.0)
679 }
680
681 pub fn models(&self) -> Vec<Matrix> {
682 self.ids().map(|id| self.model(id)).collect()
683 }
684
685 pub fn set_model(&mut self, id: ObjectId, world: Matrix) -> bool {
686 let local = match self.parent(id) {
687 None => world,
688 Some(parent) => match invert(self.model(parent)) {
689 Some(inverse) => multiply(inverse, world),
690 None => return false,
691 },
692 };
693 self.objects[id.0].local = local;
694 true
695 }
696
697 pub fn at(&self, id: ObjectId) -> [f32; 3] {
698 math::position(self.model(id))
699 }
700
701 pub fn place(&mut self, id: ObjectId, at: [f32; 3], rotation: [f32; 4]) -> bool {
702 let scale = self.objects[id.0].scale;
703 self.set_model(id, math::compose(at, rotation, scale))
704 }
705
706 pub fn move_to(&mut self, id: ObjectId, at: [f32; 3]) -> bool {
707 let mut world = self.model(id);
708 world[3] = [at[0], at[1], at[2], 1.0];
709 self.set_model(id, world)
710 }
711
712 pub fn hidden(&self, id: ObjectId) -> bool {
713 self.objects[id.0].hidden
714 }
715
716 pub fn set_hidden(&mut self, id: ObjectId, hidden: bool) -> bool {
717 let entry = &mut self.objects[id.0];
718 if !entry.drawn {
719 return false;
720 }
721 entry.hidden = hidden;
722 true
723 }
724
725 pub fn look(&self, id: ObjectId) -> &Look {
726 &self.objects[id.0].look
727 }
728
729 pub fn set_look(&mut self, id: ObjectId, look: Look) {
730 self.objects[id.0].look = look;
731 }
732
733 pub fn body(&self, id: ObjectId) -> Option<BodyId> {
734 self.bodies.get(&id.0).map(|physical| physical.id)
735 }
736
737 pub fn body_desc(&self, id: ObjectId) -> Option<&Body> {
738 self.bodies.get(&id.0).map(|physical| &physical.desc)
739 }
740
741 pub fn material(&self, name: &str) -> Option<Material> {
742 self.materials
743 .get(name)
744 .or_else(|| self.scene.library.get(name))
745 .copied()
746 }
747
748 pub fn edited_materials(&self) -> &BTreeMap<String, Material> {
749 &self.materials
750 }
751
752 pub fn light(&self, name: &str) -> Option<&Light> {
753 self.lights.get(name)
754 }
755
756 pub fn lights(&self) -> &BTreeMap<String, Light> {
757 &self.lights
758 }
759
760 pub fn set_light(&mut self, name: &str, light: Light) -> bool {
761 let Some(slot) = self.lights.get_mut(name) else {
762 return false;
763 };
764 if *slot != light {
765 *slot = light;
766 self.lit += 1;
767 }
768 true
769 }
770
771 pub(crate) fn lights_revision(&self) -> u64 {
772 self.lit
773 }
774
775 pub fn exposure(&self) -> f32 {
776 self.exposure
777 }
778
779 pub fn set_exposure(&mut self, exposure: f32) -> bool {
780 if !exposure.is_finite() || exposure <= 0.0 {
781 return false;
782 }
783 if exposure != self.exposure {
784 self.exposure = exposure;
785 self.exposed += 1;
786 }
787 true
788 }
789
790 pub(crate) fn exposure_revision(&self) -> u64 {
791 self.exposed
792 }
793
794 pub fn sound_level(&self, name: &str) -> Option<SoundLevel> {
795 self.levels.get(name).copied()
796 }
797
798 pub fn tunables(&self) -> &Tunables {
799 &self.tunables
800 }
801
802 pub fn tunable<T: FromTunable>(&self, name: &str) -> Option<T> {
803 T::from_tunable(*self.tuned.get(name)?)
804 }
805
806 pub fn set_tunable(&mut self, name: &str, value: TunableValue) -> Result<TunableValue, String> {
807 let value = self.tunables.check(name, value)?;
808 self.tuned.insert(name.to_string(), value);
809 Ok(value)
810 }
811
812 pub fn contacts(&self) -> &[Contact] {
813 &self.contacts
814 }
815
816 pub fn ticks(&self) -> u64 {
817 self.ticks
818 }
819
820 pub fn time(&self) -> f64 {
821 self.ticks as f64 / self.clock.tick_rate()
822 }
823
824 pub fn clock(&self) -> &Clock {
825 &self.clock
826 }
827
828 pub fn hit_stop(&mut self, stop: HitStop) {
829 self.clock.hit_stop(self.fx.comfort.hit_stop(stop));
830 }
831
832 pub fn set_speed(&mut self, speed: f32) {
833 self.clock.set_speed(speed);
834 }
835
836 pub fn state<T: Any>(&self) -> Option<&T> {
837 self.state.as_ref()?.downcast_ref()
838 }
839
840 pub fn state_mut<T: Any>(&mut self) -> Option<&mut T> {
841 self.state.as_mut()?.downcast_mut()
842 }
843
844 pub fn set_state<T: Any>(&mut self, state: T) {
845 self.state = Some(Box::new(state));
846 }
847
848 pub fn play_sound(&mut self, name: &str) -> bool {
849 let Some(sound) = self.levels.get(name).copied() else {
850 return false;
851 };
852 let level = Level {
853 volume: sound.volume,
854 pan: sound.pan,
855 ..Level::default()
856 };
857 self.sounds.play_clip(name, level, sound.looping)
858 }
859
860 pub fn play_cued(&mut self, cue: Cue) -> usize {
861 let scene = Arc::clone(&self.scene);
862 scene
863 .sounds
864 .iter()
865 .filter(|(name, sound)| sound.cue == cue && self.play_sound(name))
866 .count()
867 }
868
869 pub fn play_hits(&mut self) -> usize {
870 let scene = Arc::clone(&self.scene);
871 let mut names: Vec<&String> = Vec::new();
872 for contact in &self.contacts {
873 for (name, sound) in &scene.sounds {
874 let Some(object) = &sound.object else {
875 continue;
876 };
877 if sound.cue == Cue::Hit
878 && (self.objects[contact.a.0].name == *object
879 || self.objects[contact.b.0].name == *object)
880 && !names.contains(&name)
881 {
882 names.push(name);
883 }
884 }
885 }
886 names.iter().filter(|name| self.play_sound(name)).count()
887 }
888
889 pub fn pose_movers(&mut self) {
890 if self.movers.is_empty() {
891 return;
892 }
893 let posed = self
894 .scene
895 .posed(&self.scene.camera_or_default(), self.time() as f32);
896 for at in 0..self.movers.len() {
897 let place = self.movers[at];
898 self.set_model(ObjectId(place), posed[place]);
899 }
900 }
901
902 pub fn set_cursor(&mut self, cursor: Cursor) {
903 self.cursor = cursor;
904 }
905
906 pub fn cursor(&self) -> Cursor {
907 self.cursor
908 }
909
910 pub fn warp_pointer(&mut self, point: [f32; 2]) {
911 if point.iter().all(|value| value.is_finite()) {
912 self.warp = Some(point);
913 }
914 }
915
916 pub fn take_warp(&mut self) -> Option<[f32; 2]> {
917 self.warp.take()
918 }
919
920 pub fn rumble(&mut self, rumble: Rumble) {
921 self.rumble.push(RumbleRequest::Active(rumble));
922 }
923
924 pub fn rumble_pad(&mut self, pad: PadId, rumble: Rumble) {
925 self.rumble.push(RumbleRequest::Pad(pad, rumble));
926 }
927
928 pub fn rumble_queue(&self) -> &[RumbleRequest] {
929 &self.rumble
930 }
931
932 pub(crate) fn take_rumble(&mut self) -> Vec<RumbleRequest> {
933 std::mem::take(&mut self.rumble)
934 }
935
936 pub(crate) fn begin_tick(&mut self) {
937 self.begin_animations();
938 self.ticks += 1;
939 self.sounds.now = self.ticks;
940 }
941
942 pub(crate) fn refresh_physics(&mut self) {
943 self.sync_triggers();
944 self.physics.refresh_queries();
945 }
946
947 fn sync_triggers(&mut self) {
948 for (&place, sensor) in self.sensors.iter_mut().filter(|(_, sensor)| sensor.follows) {
949 let pose = math::pose(model_in(&self.objects, place));
950 if sensor.pose != pose {
951 sensor.pose = pose;
952 self.physics.set_pose(sensor.body, pose);
953 }
954 }
955 }
956
957 pub(crate) fn step_physics(&mut self) {
958 self.events.clear();
959 if self.bodies.is_empty() && self.sensors.is_empty() && self.characters.is_empty() {
960 return;
961 }
962 self.sync_triggers();
963 self.step_characters();
964 self.physics.step();
965 self.place_characters();
966 for (&place, physical) in &self.bodies {
967 if physical.desc.kind == BodyKind::Fixed {
968 continue;
969 }
970 if let Some(pose) = self.physics.pose(physical.id) {
971 let entry = &mut self.objects[place];
972 entry.local = math::compose(pose.position, pose.rotation, entry.scale);
973 }
974 }
975 self.contacts.clear();
976 for event in self.physics.events() {
977 if let Event::ContactStarted { a, b, impulse } = *event
978 && let (Some(&a), Some(&b)) = (self.owners.get(&a), self.owners.get(&b))
979 {
980 self.contacts.push(Contact {
981 a: ObjectId(a),
982 b: ObjectId(b),
983 impulse,
984 });
985 }
986 }
987 let mut now = BTreeSet::new();
988 for (&place, sensor) in &self.sensors {
989 let Some(pose) = self.physics.pose(sensor.body) else {
990 continue;
991 };
992 let shift = math::rotate(pose.rotation, sensor.offset);
993 let at = std::array::from_fn(|axis| pose.position[axis] + shift[axis]);
994 let filter = Filter::layers(sensor.detects)
995 .with_sensors()
996 .excluding(sensor.body);
997 for collider in self.physics.shape_overlaps(
998 &shape(sensor.shape),
999 Pose::new(at, pose.rotation),
1000 filter,
1001 ) {
1002 if let Some(&other) = self
1003 .owners
1004 .get(&collider)
1005 .or_else(|| self.sensor_owners.get(&collider))
1006 {
1007 now.insert((place, other));
1008 }
1009 }
1010 }
1011 self.events
1012 .extend(self.contacts.iter().copied().map(WorldEvent::Contact));
1013 let event = |phase| {
1014 move |&(trigger, other): &(usize, usize)| {
1015 WorldEvent::Trigger(TriggerEvent {
1016 phase,
1017 trigger: ObjectId(trigger),
1018 other: ObjectId(other),
1019 })
1020 }
1021 };
1022 self.events
1023 .extend(now.difference(&self.inside).map(event(Phase::Enter)));
1024 self.events
1025 .extend(now.intersection(&self.inside).map(event(Phase::Stay)));
1026 self.events
1027 .extend(self.inside.difference(&now).map(event(Phase::Exit)));
1028 self.inside = now;
1029 }
1030
1031 pub fn events(&self) -> &[WorldEvent] {
1032 &self.events
1033 }
1034
1035 pub fn layers(&self) -> &Layers {
1036 &self.layers
1037 }
1038
1039 pub fn is_trigger(&self, id: ObjectId) -> bool {
1040 self.sensors.contains_key(&id.0)
1041 }
1042
1043 pub fn inside(&self, trigger: ObjectId) -> Vec<ObjectId> {
1044 self.inside
1045 .iter()
1046 .filter(|(sensor, _)| *sensor == trigger.0)
1047 .map(|(_, other)| ObjectId(*other))
1048 .collect()
1049 }
1050
1051 pub fn quit(&mut self, code: u8) {
1052 if self.exit.is_none() {
1053 self.exit = Some(Exit::Quit(code));
1054 }
1055 }
1056
1057 pub fn fail(&mut self, error: impl std::fmt::Display) {
1058 if !matches!(self.exit, Some(Exit::Failed(_))) {
1059 self.exit = Some(Exit::Failed(error.to_string()));
1060 }
1061 }
1062
1063 pub fn exit(&self) -> Option<&Exit> {
1064 self.exit.as_ref()
1065 }
1066
1067 pub fn display(&self) -> &DisplaySettings {
1068 &self.display
1069 }
1070
1071 pub(crate) fn set_display(&mut self, display: &DisplaySettings) {
1072 if self.display != *display {
1073 self.display = display.clone();
1074 }
1075 }
1076
1077 fn audio_frame(&self) -> u64 {
1078 (self.ticks as f64 * f64::from(self.sounds.rate()) / self.clock.tick_rate()).floor() as u64
1079 }
1080
1081 pub fn audio_due(&self) -> usize {
1082 usize::try_from(self.audio_frame().saturating_sub(self.sounds.rendered))
1083 .unwrap_or(usize::MAX)
1084 }
1085
1086 pub(crate) fn render_audio(&mut self) {
1087 self.sounds.render_until(self.audio_frame());
1088 }
1089
1090 pub(crate) fn clock_mut(&mut self) -> &mut Clock {
1091 &mut self.clock
1092 }
1093}
1094
1095fn mover_order(scene: &Scene, objects: &[Entry]) -> Vec<usize> {
1096 if scene.movers.is_empty() {
1097 return Vec::new();
1098 }
1099 let owned: Vec<bool> = scene
1100 .objects
1101 .iter()
1102 .map(|object| {
1103 scene
1104 .movers
1105 .values()
1106 .any(|mover| mover.objects.contains(&object.name))
1107 })
1108 .collect();
1109 let mut order: Vec<(usize, usize)> = Vec::new();
1110 for place in 0..objects.len() {
1111 let mut depth = 0;
1112 let mut moved = owned[place];
1113 let mut at = objects[place].parent;
1114 while let Some(parent) = at {
1115 depth += 1;
1116 if depth > objects.len() {
1117 break;
1118 }
1119 moved |= owned[parent];
1120 at = objects[parent].parent;
1121 }
1122 if moved {
1123 order.push((depth, place));
1124 }
1125 }
1126 order.sort();
1127 order.into_iter().map(|(_, place)| place).collect()
1128}
1129
1130fn model_in(objects: &[Entry], place: usize) -> Matrix {
1131 let entry = &objects[place];
1132 let mut matrix = entry.local;
1133 let mut at = entry.parent;
1134 let mut steps = 0;
1135 while let Some(parent) = at {
1136 steps += 1;
1137 if steps > objects.len() {
1138 break;
1139 }
1140 matrix = multiply(objects[parent].local, matrix);
1141 at = objects[parent].parent;
1142 }
1143 matrix
1144}