Skip to main content

pfx_play/
world.rs

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}