Skip to main content

pfx_scene/types/
scene.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use super::either::either;
6use super::{is_false, is_zero3};
7use crate::Id;
8
9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct Mesh {
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub id: Option<Id>,
14    pub file: String,
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub node: Option<String>,
17    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
18    pub nodes: BTreeMap<String, Node>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub authoring: Option<toml::Value>,
21}
22
23#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct Node {
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub material: Option<String>,
28    #[serde(default, skip_serializing_if = "is_false")]
29    pub hidden: bool,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub shadow: Option<Shadow>,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub two_sided: Option<bool>,
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum Shadow {
39    #[default]
40    Cast,
41    Only,
42    None,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
46#[serde(untagged)]
47pub enum Scale {
48    Uniform(f32),
49    Axes([f32; 3]),
50}
51
52either!(Scale, "a number or three numbers", number => Uniform, seq => Axes([f32; 3]),);
53
54impl Scale {
55    pub fn axes(self) -> [f32; 3] {
56        match self {
57            Self::Uniform(value) => [value; 3],
58            Self::Axes(axes) => axes,
59        }
60    }
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
64#[serde(deny_unknown_fields)]
65pub struct Object {
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub id: Option<Id>,
68    pub name: String,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub mesh: Option<String>,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub prefab: Option<String>,
73    #[serde(default, skip_serializing_if = "is_zero3")]
74    pub at: [f32; 3],
75    #[serde(default, skip_serializing_if = "is_zero3")]
76    pub rotate: [f32; 3],
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub scale: Option<Scale>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub parent: Option<String>,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub material: Option<String>,
83    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
84    pub materials: BTreeMap<String, String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub shadow: Option<Shadow>,
87    #[serde(default, skip_serializing_if = "is_false")]
88    pub two_sided: bool,
89    #[serde(default, skip_serializing_if = "is_false")]
90    pub hidden: bool,
91    #[serde(default, skip_serializing_if = "Vec::is_empty")]
92    pub clip: Vec<[f32; 4]>,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub content: Option<String>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub pick: Option<u32>,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub alpha_cutoff: Option<f32>,
99    #[serde(default, skip_serializing_if = "is_false")]
100    pub face_camera: bool,
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub dynamic: Option<bool>,
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub body: Option<Body>,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub character: Option<Character>,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub trigger: Option<Trigger>,
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub animation: Option<Animation>,
111    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
112    pub set: BTreeMap<String, toml::Value>,
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub authoring: Option<toml::Value>,
115}
116
117impl Object {
118    pub const ALPHA_CUTOFF: f32 = 0.5;
119
120    pub fn is_placement(&self) -> bool {
121        self.prefab.is_some()
122    }
123
124    pub fn scale_axes(&self) -> [f32; 3] {
125        self.scale.map_or([1.0; 3], Scale::axes)
126    }
127}
128
129#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
130#[serde(deny_unknown_fields)]
131pub struct Light {
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub id: Option<Id>,
134    pub name: String,
135    pub position: [f32; 3],
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub color: Option<[f32; 3]>,
138    pub intensity: f32,
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub radius: Option<f32>,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub range: Option<f32>,
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub shadow: Option<bool>,
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub authoring: Option<toml::Value>,
147}
148
149impl Light {
150    pub const COLOR: [f32; 3] = [1.0; 3];
151    pub const RADIUS: f32 = 0.0;
152    pub const RANGE: f32 = 10.0;
153    pub const SHADOW: bool = true;
154}
155
156#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
157#[serde(deny_unknown_fields)]
158pub struct Emitter {
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub id: Option<Id>,
161    pub name: String,
162    pub position: [f32; 3],
163    pub radius: f32,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub color: Option<[f32; 3]>,
166    pub intensity: f32,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub authoring: Option<toml::Value>,
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum MoverKind {
174    Turn,
175    Slide,
176}
177
178#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum Motion {
181    #[default]
182    Swing,
183    Loop,
184    Once,
185}
186
187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
188#[serde(deny_unknown_fields)]
189pub struct Mover {
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub id: Option<Id>,
192    pub name: String,
193    pub objects: Vec<String>,
194    pub kind: MoverKind,
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub pivot: Option<[f32; 3]>,
197    pub axis: [f32; 3],
198    pub travel: [f32; 2],
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub period: Option<f32>,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub motion: Option<Motion>,
203    #[serde(default, skip_serializing_if = "Option::is_none")]
204    pub offset: Option<f32>,
205    #[serde(default, skip_serializing_if = "Vec::is_empty")]
206    pub clip: Vec<[f32; 4]>,
207    #[serde(default, skip_serializing_if = "Option::is_none")]
208    pub authoring: Option<toml::Value>,
209}
210
211impl Mover {
212    pub const PERIOD: f32 = 4.0;
213}
214
215#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
216#[serde(deny_unknown_fields)]
217pub struct Content {
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub id: Option<Id>,
220    pub image: String,
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub authoring: Option<toml::Value>,
223}
224
225#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
226#[serde(deny_unknown_fields)]
227pub struct Text {
228    #[serde(default, skip_serializing_if = "Option::is_none")]
229    pub id: Option<Id>,
230    pub text: String,
231    pub font: String,
232    pub size: f32,
233    #[serde(default, skip_serializing_if = "Option::is_none")]
234    pub color: Option<[f32; 4]>,
235    #[serde(default, skip_serializing_if = "is_zero3")]
236    pub at: [f32; 3],
237    #[serde(default, skip_serializing_if = "is_zero3")]
238    pub rotate: [f32; 3],
239    #[serde(default, skip_serializing_if = "is_false")]
240    pub lit: bool,
241    #[serde(default, skip_serializing_if = "is_false")]
242    pub dynamic: bool,
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub authoring: Option<toml::Value>,
245}
246
247impl Text {
248    pub const COLOR: [f32; 4] = [1.0; 4];
249}
250
251#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "snake_case")]
253pub enum Cue {
254    #[default]
255    Start,
256    Hit,
257    Game,
258}
259
260#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
261#[serde(deny_unknown_fields)]
262pub struct Sound {
263    #[serde(default, skip_serializing_if = "Option::is_none")]
264    pub id: Option<Id>,
265    pub file: String,
266    #[serde(default, skip_serializing_if = "Option::is_none")]
267    pub volume: Option<f32>,
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub pan: Option<f32>,
270    #[serde(default, rename = "loop", skip_serializing_if = "is_false")]
271    pub looping: bool,
272    #[serde(default, skip_serializing_if = "Option::is_none")]
273    pub play: Option<Cue>,
274    #[serde(default, skip_serializing_if = "Option::is_none")]
275    pub object: Option<String>,
276    #[serde(default, skip_serializing_if = "Option::is_none")]
277    pub authoring: Option<toml::Value>,
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
281#[serde(rename_all = "snake_case")]
282pub enum SunModel {
283    Daylight,
284    Authored,
285}
286
287#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
288#[serde(deny_unknown_fields)]
289pub struct Sun {
290    pub model: SunModel,
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub hour: Option<f64>,
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub day: Option<f64>,
295    #[serde(default, skip_serializing_if = "Option::is_none")]
296    pub latitude: Option<f64>,
297    #[serde(default, skip_serializing_if = "Option::is_none")]
298    pub heading: Option<f64>,
299    #[serde(default, skip_serializing_if = "Option::is_none")]
300    pub reference_hour: Option<f64>,
301    #[serde(default, skip_serializing_if = "Option::is_none")]
302    pub toward: Option<[f32; 3]>,
303    #[serde(default, skip_serializing_if = "Option::is_none")]
304    pub color: Option<[f32; 3]>,
305    #[serde(default, skip_serializing_if = "Option::is_none")]
306    pub irradiance: Option<f32>,
307    #[serde(default, skip_serializing_if = "Option::is_none")]
308    pub radius: Option<f32>,
309}
310
311impl Sun {
312    pub const REFERENCE_HOUR: f64 = 12.0;
313    pub const DAY: f64 = 172.0;
314    pub const LATITUDE: f64 = 45.0;
315    pub const HEADING: f64 = 180.0;
316}
317
318#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
319#[serde(deny_unknown_fields)]
320pub struct Haze {
321    pub lo: [f32; 3],
322    pub hi: [f32; 3],
323    #[serde(default, skip_serializing_if = "Option::is_none")]
324    pub amount: Option<f32>,
325    #[serde(default, skip_serializing_if = "Option::is_none")]
326    pub fog: Option<f32>,
327    #[serde(default, skip_serializing_if = "Option::is_none")]
328    pub smoke: Option<f32>,
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    pub mist: Option<f32>,
331    #[serde(default, skip_serializing_if = "Option::is_none")]
332    pub floor: Option<f32>,
333    #[serde(default, skip_serializing_if = "Option::is_none")]
334    pub phase: Option<f32>,
335    #[serde(default, skip_serializing_if = "Option::is_none")]
336    pub back: Option<f32>,
337    #[serde(default, skip_serializing_if = "Option::is_none")]
338    pub reach: Option<f32>,
339    #[serde(default, skip_serializing_if = "Option::is_none")]
340    pub gold: Option<[f32; 3]>,
341    #[serde(default, skip_serializing_if = "Option::is_none")]
342    pub ambient: Option<[f32; 3]>,
343    #[serde(default, skip_serializing_if = "Option::is_none")]
344    pub unmapped: Option<f32>,
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    pub seed: Option<u32>,
347}
348
349#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
350#[serde(rename_all = "snake_case")]
351pub enum Projection {
352    #[default]
353    Perspective,
354    Orthographic,
355}
356
357#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
358#[serde(deny_unknown_fields)]
359pub struct Camera {
360    #[serde(default, skip_serializing_if = "Option::is_none")]
361    pub projection: Option<Projection>,
362    #[serde(default, skip_serializing_if = "Option::is_none")]
363    pub at: Option<[f32; 3]>,
364    #[serde(default, skip_serializing_if = "Option::is_none")]
365    pub look_at: Option<[f32; 3]>,
366    #[serde(default, skip_serializing_if = "Option::is_none")]
367    pub up: Option<[f32; 3]>,
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub fov: Option<f32>,
370    #[serde(default, skip_serializing_if = "Option::is_none")]
371    pub focal: Option<f32>,
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub sensor: Option<f32>,
374    #[serde(default, skip_serializing_if = "Option::is_none")]
375    pub height: Option<f32>,
376    #[serde(default, skip_serializing_if = "Option::is_none")]
377    pub near: Option<f32>,
378    #[serde(default, skip_serializing_if = "Option::is_none")]
379    pub far: Option<f32>,
380    #[serde(default, skip_serializing_if = "Option::is_none")]
381    pub shift: Option<[f32; 2]>,
382    #[serde(default, skip_serializing_if = "Option::is_none")]
383    pub fstop: Option<f32>,
384    #[serde(default, skip_serializing_if = "Option::is_none")]
385    pub focus: Option<f32>,
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub preset: Option<Preset>,
388}
389
390impl Camera {
391    pub const AT: [f32; 3] = [0.0, 0.0, 5.0];
392    pub const LOOK_AT: [f32; 3] = [0.0; 3];
393    pub const UP: [f32; 3] = [0.0, 1.0, 0.0];
394    pub const FOV: f32 = 40.0;
395    pub const SENSOR: f32 = 24.0;
396    pub const NEAR: f32 = 0.05;
397    pub const FAR: f32 = 100.0;
398}
399
400#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
401#[serde(deny_unknown_fields)]
402pub struct Preset {
403    #[serde(default, skip_serializing_if = "Option::is_none")]
404    pub gain: Option<f32>,
405    #[serde(default, skip_serializing_if = "Option::is_none")]
406    pub orbit: Option<Orbit>,
407    #[serde(default, skip_serializing_if = "Option::is_none")]
408    pub look: Option<Look>,
409    #[serde(default, skip_serializing_if = "Option::is_none")]
410    pub zoom: Option<Zoom>,
411    #[serde(default, skip_serializing_if = "Option::is_none")]
412    pub wander: Option<Wander>,
413    #[serde(default, skip_serializing_if = "Option::is_none")]
414    pub nudge: Option<Nudge>,
415    #[serde(default, skip_serializing_if = "Option::is_none")]
416    pub hush: Option<Hush>,
417    #[serde(default, skip_serializing_if = "Option::is_none")]
418    pub depth: Option<Depth>,
419}
420
421#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
422#[serde(deny_unknown_fields)]
423pub struct Orbit {
424    #[serde(default, skip_serializing_if = "Option::is_none")]
425    pub stiffness: Option<f32>,
426    #[serde(default, skip_serializing_if = "Option::is_none")]
427    pub yaw: Option<f32>,
428    #[serde(default, skip_serializing_if = "Option::is_none")]
429    pub pitch: Option<f32>,
430    #[serde(default, skip_serializing_if = "Option::is_none")]
431    pub hold: Option<f32>,
432}
433
434#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
435#[serde(deny_unknown_fields)]
436pub struct Look {
437    #[serde(default, skip_serializing_if = "Option::is_none")]
438    pub stiffness: Option<f32>,
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub lean: Option<f32>,
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub yaw: Option<f32>,
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub pitch: Option<f32>,
445    #[serde(default, skip_serializing_if = "Option::is_none")]
446    pub sign: Option<f32>,
447}
448
449#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
450#[serde(deny_unknown_fields)]
451pub struct Zoom {
452    #[serde(default, skip_serializing_if = "Option::is_none")]
453    pub stiffness: Option<f32>,
454    #[serde(default, skip_serializing_if = "Option::is_none")]
455    pub hold: Option<f32>,
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    pub max: Option<f32>,
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub rate: Option<f32>,
460}
461
462#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
463#[serde(deny_unknown_fields)]
464pub struct Wander {
465    #[serde(default, skip_serializing_if = "Option::is_none")]
466    pub idle: Option<f32>,
467    #[serde(default, skip_serializing_if = "Option::is_none")]
468    pub every: Option<f32>,
469    #[serde(default, skip_serializing_if = "Option::is_none")]
470    pub hold: Option<f32>,
471}
472
473#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
474#[serde(deny_unknown_fields)]
475pub struct Nudge {
476    #[serde(default, skip_serializing_if = "Option::is_none")]
477    pub stiffness: Option<f32>,
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    pub pitch: Option<f32>,
480    #[serde(default, skip_serializing_if = "Option::is_none")]
481    pub hold: Option<f32>,
482    #[serde(default, skip_serializing_if = "Option::is_none")]
483    pub floor: Option<f32>,
484}
485
486#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
487#[serde(deny_unknown_fields)]
488pub struct Hush {
489    #[serde(default, skip_serializing_if = "Option::is_none")]
490    pub floor: Option<f32>,
491    #[serde(default, skip_serializing_if = "Option::is_none")]
492    pub stiffness: Option<f32>,
493    #[serde(default, skip_serializing_if = "Option::is_none")]
494    pub blocks_cursor: Option<bool>,
495}
496
497#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
498#[serde(deny_unknown_fields)]
499pub struct Depth {
500    #[serde(default, skip_serializing_if = "Option::is_none")]
501    pub focus: Option<f32>,
502    #[serde(default, skip_serializing_if = "Option::is_none")]
503    pub blur: Option<f32>,
504    #[serde(default, skip_serializing_if = "Option::is_none")]
505    pub floor: Option<f32>,
506}
507
508#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
509#[serde(deny_unknown_fields)]
510pub struct Trace {
511    #[serde(default, skip_serializing_if = "is_false")]
512    pub transmissive_shadows: bool,
513    #[serde(default, skip_serializing_if = "Option::is_none")]
514    pub clamp_indirect: Option<f32>,
515    #[serde(default, skip_serializing_if = "Option::is_none")]
516    pub filter_glossy: Option<f32>,
517}
518
519impl Trace {
520    pub const CLAMP_INDIRECT: f32 = 0.0;
521    pub const FILTER_GLOSSY: f32 = 0.0;
522}
523
524#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
525#[serde(rename_all = "snake_case")]
526pub enum Casters {
527    #[default]
528    Meshes,
529    Proxies,
530    None,
531}
532
533#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
534#[serde(deny_unknown_fields)]
535pub struct Plates {
536    pub dir: String,
537    #[serde(default, skip_serializing_if = "Option::is_none")]
538    pub proxies: Option<String>,
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub casters: Option<Casters>,
541}
542
543#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
544#[serde(deny_unknown_fields)]
545pub struct Physics {
546    #[serde(default, skip_serializing_if = "Option::is_none")]
547    pub gravity: Option<[f32; 3]>,
548    #[serde(default, skip_serializing_if = "Option::is_none")]
549    pub rate: Option<f32>,
550    #[serde(default, skip_serializing_if = "Option::is_none")]
551    pub substeps: Option<u32>,
552    #[serde(default, skip_serializing_if = "Option::is_none")]
553    pub iterations: Option<u32>,
554}
555
556impl Physics {
557    pub const GRAVITY: [f32; 3] = [0.0, -9.81, 0.0];
558    pub const RATE: f32 = 60.0;
559    pub const SUBSTEPS: u32 = 1;
560    pub const ITERATIONS: u32 = 4;
561}
562
563#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
564#[serde(rename_all = "snake_case")]
565pub enum BodyKind {
566    #[default]
567    Dynamic,
568    Kinematic,
569    Fixed,
570}
571
572#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
573#[serde(rename_all = "snake_case")]
574pub enum BodyShape {
575    #[default]
576    Box,
577    Sphere,
578    Capsule,
579    Cylinder,
580}
581
582#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
583#[serde(deny_unknown_fields)]
584pub struct Body {
585    #[serde(default, skip_serializing_if = "Option::is_none")]
586    pub kind: Option<BodyKind>,
587    #[serde(default, skip_serializing_if = "Option::is_none")]
588    pub shape: Option<BodyShape>,
589    #[serde(default, skip_serializing_if = "Option::is_none")]
590    pub half: Option<[f32; 3]>,
591    #[serde(default, skip_serializing_if = "Option::is_none")]
592    pub radius: Option<f32>,
593    #[serde(default, skip_serializing_if = "Option::is_none")]
594    pub half_height: Option<f32>,
595    #[serde(default, skip_serializing_if = "Option::is_none")]
596    pub offset: Option<[f32; 3]>,
597    #[serde(default, skip_serializing_if = "Option::is_none")]
598    pub density: Option<f32>,
599    #[serde(default, skip_serializing_if = "Option::is_none")]
600    pub friction: Option<f32>,
601    #[serde(default, skip_serializing_if = "Option::is_none")]
602    pub restitution: Option<f32>,
603    #[serde(default, skip_serializing_if = "Option::is_none")]
604    pub velocity: Option<[f32; 3]>,
605    #[serde(default, skip_serializing_if = "Option::is_none")]
606    pub spin: Option<[f32; 3]>,
607    #[serde(default, skip_serializing_if = "Option::is_none")]
608    pub gravity_scale: Option<f32>,
609    #[serde(default, skip_serializing_if = "Option::is_none")]
610    pub damping: Option<[f32; 2]>,
611    #[serde(default, skip_serializing_if = "is_false")]
612    pub ccd: bool,
613    #[serde(default, skip_serializing_if = "Option::is_none")]
614    pub layer: Option<String>,
615}
616
617impl Body {
618    pub const DENSITY: f32 = 1.0;
619    pub const FRICTION: f32 = 0.5;
620    pub const RESTITUTION: f32 = 0.0;
621    pub const GRAVITY_SCALE: f32 = 1.0;
622}
623
624#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
625pub enum CharacterKind {
626    #[default]
627    #[serde(rename = "3d")]
628    ThreeD,
629    #[serde(rename = "2d")]
630    TwoD,
631}
632
633#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
634#[serde(rename_all = "snake_case")]
635pub enum Plane {
636    #[default]
637    Xy,
638    Yz,
639}
640
641#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
642#[serde(deny_unknown_fields)]
643pub struct Character {
644    #[serde(default, skip_serializing_if = "Option::is_none")]
645    pub kind: Option<CharacterKind>,
646    #[serde(default, skip_serializing_if = "Option::is_none")]
647    pub radius: Option<f32>,
648    #[serde(default, skip_serializing_if = "Option::is_none")]
649    pub height: Option<f32>,
650    #[serde(default, skip_serializing_if = "Option::is_none")]
651    pub max_climb: Option<f32>,
652    #[serde(default, skip_serializing_if = "Option::is_none")]
653    pub max_step: Option<f32>,
654    #[serde(default, skip_serializing_if = "Option::is_none")]
655    pub snap: Option<f32>,
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub coyote: Option<u32>,
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub jump_buffer: Option<u32>,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub jump_speed: Option<f32>,
662    #[serde(default, skip_serializing_if = "Option::is_none")]
663    pub layer: Option<String>,
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub plane: Option<Plane>,
666    #[serde(default, skip_serializing_if = "Option::is_none")]
667    pub variable_jump: Option<f32>,
668    #[serde(default, skip_serializing_if = "Option::is_none")]
669    pub wall_slide: Option<f32>,
670    #[serde(default, skip_serializing_if = "Option::is_none")]
671    pub wall_jump: Option<[f32; 2]>,
672}
673
674impl Character {
675    pub const MAX_CLIMB: f32 = 45.0;
676    pub const MAX_STEP: f32 = 0.3;
677    pub const SNAP: f32 = 0.2;
678    pub const COYOTE: u32 = 6;
679    pub const JUMP_BUFFER: u32 = 6;
680    pub const JUMP_SPEED: f32 = 5.0;
681    pub const VARIABLE_JUMP: f32 = 0.0;
682    pub const TICKS: u32 = 1000;
683}
684
685#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
686#[serde(deny_unknown_fields)]
687pub struct Trigger {
688    #[serde(default, skip_serializing_if = "Option::is_none")]
689    pub shape: Option<BodyShape>,
690    #[serde(default, skip_serializing_if = "Option::is_none")]
691    pub half: Option<[f32; 3]>,
692    #[serde(default, skip_serializing_if = "Option::is_none")]
693    pub radius: Option<f32>,
694    #[serde(default, skip_serializing_if = "Option::is_none")]
695    pub half_height: Option<f32>,
696    #[serde(default, skip_serializing_if = "Option::is_none")]
697    pub offset: Option<[f32; 3]>,
698    #[serde(default, skip_serializing_if = "Option::is_none")]
699    pub layer: Option<String>,
700    #[serde(default, skip_serializing_if = "Option::is_none")]
701    pub mask: Option<Vec<String>>,
702}
703
704#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
705#[serde(rename_all = "kebab-case")]
706pub enum RigKind {
707    Follow,
708    FirstPerson,
709    ThirdPerson,
710}
711
712impl RigKind {
713    pub fn name(self) -> &'static str {
714        match self {
715            Self::Follow => "follow",
716            Self::FirstPerson => "first-person",
717            Self::ThirdPerson => "third-person",
718        }
719    }
720}
721
722#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
723#[serde(deny_unknown_fields)]
724pub struct Bounds {
725    pub min: [f32; 3],
726    pub max: [f32; 3],
727}
728
729#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
730#[serde(deny_unknown_fields)]
731pub struct Rig {
732    #[serde(default, skip_serializing_if = "Option::is_none")]
733    pub id: Option<Id>,
734    pub name: String,
735    pub kind: RigKind,
736    #[serde(default, skip_serializing_if = "Option::is_none")]
737    pub target: Option<String>,
738    #[serde(default, skip_serializing_if = "Option::is_none")]
739    pub offset: Option<[f32; 3]>,
740    #[serde(default, skip_serializing_if = "Option::is_none")]
741    pub dead_zone: Option<[f32; 2]>,
742    #[serde(default, skip_serializing_if = "Option::is_none")]
743    pub look_ahead: Option<f32>,
744    #[serde(default, skip_serializing_if = "Option::is_none")]
745    pub damping: Option<[f32; 3]>,
746    #[serde(default, skip_serializing_if = "Option::is_none")]
747    pub bounds: Option<Bounds>,
748    #[serde(default, skip_serializing_if = "Option::is_none")]
749    pub orthographic: Option<f32>,
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub snap: Option<f32>,
752    #[serde(default, skip_serializing_if = "Option::is_none")]
753    pub sensitivity: Option<f32>,
754    #[serde(default, skip_serializing_if = "Option::is_none")]
755    pub invert: Option<bool>,
756    #[serde(default, skip_serializing_if = "Option::is_none")]
757    pub smoothing: Option<f32>,
758    #[serde(default, skip_serializing_if = "Option::is_none")]
759    pub pitch: Option<[f32; 2]>,
760    #[serde(default, skip_serializing_if = "Option::is_none")]
761    pub head_bob: Option<f32>,
762    #[serde(default, skip_serializing_if = "Option::is_none")]
763    pub distance: Option<f32>,
764    #[serde(default, skip_serializing_if = "Option::is_none")]
765    pub collide: Option<bool>,
766    #[serde(default, skip_serializing_if = "Option::is_none")]
767    pub authoring: Option<toml::Value>,
768}
769
770impl Rig {
771    pub const DEAD_ZONE: [f32; 2] = [0.0; 2];
772    pub const LOOK_AHEAD: f32 = 0.0;
773    pub const DAMPING: [f32; 3] = [0.0; 3];
774    pub const SENSITIVITY: f32 = 1.0;
775    pub const INVERT: bool = false;
776    pub const SMOOTHING: f32 = 0.0;
777    pub const PITCH: [f32; 2] = [-85.0, 85.0];
778    pub const HEAD_BOB: f32 = 0.0;
779    pub const DISTANCE: f32 = 4.0;
780    pub const COLLIDE: bool = true;
781}
782
783#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
784#[serde(rename_all = "kebab-case")]
785pub enum Repeat {
786    #[default]
787    Loop,
788    Once,
789    PingPong,
790}
791
792#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
793#[serde(deny_unknown_fields)]
794pub struct BlendClip {
795    pub clip: String,
796    #[serde(default, skip_serializing_if = "Option::is_none")]
797    pub weight: Option<f32>,
798}
799
800impl BlendClip {
801    pub const WEIGHT: f32 = 1.0;
802}
803
804#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
805#[serde(deny_unknown_fields)]
806pub struct SpriteClip {
807    pub from: u32,
808    pub to: u32,
809    #[serde(default, skip_serializing_if = "Option::is_none")]
810    pub fps: Option<f32>,
811    #[serde(default, rename = "loop", skip_serializing_if = "Option::is_none")]
812    pub looping: Option<Repeat>,
813}
814
815impl SpriteClip {
816    pub fn count(&self) -> u32 {
817        self.to.saturating_sub(self.from).saturating_add(1)
818    }
819}
820
821#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
822#[serde(deny_unknown_fields)]
823pub struct ClipEvent {
824    pub name: String,
825    #[serde(default, skip_serializing_if = "Option::is_none")]
826    pub clip: Option<String>,
827    #[serde(default, skip_serializing_if = "Option::is_none")]
828    pub frame: Option<u32>,
829    #[serde(default, skip_serializing_if = "Option::is_none")]
830    pub time: Option<f32>,
831}
832
833#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
834#[serde(deny_unknown_fields)]
835pub struct Animation {
836    #[serde(default, skip_serializing_if = "Option::is_none")]
837    pub clip: Option<String>,
838    #[serde(default, skip_serializing_if = "Option::is_none")]
839    pub speed: Option<f32>,
840    #[serde(default, rename = "loop", skip_serializing_if = "Option::is_none")]
841    pub looping: Option<Repeat>,
842    #[serde(default, skip_serializing_if = "Option::is_none")]
843    pub start: Option<f32>,
844    #[serde(default, skip_serializing_if = "Option::is_none")]
845    pub blend: Option<Vec<BlendClip>>,
846    #[serde(default, skip_serializing_if = "Vec::is_empty")]
847    pub events: Vec<ClipEvent>,
848    #[serde(default, skip_serializing_if = "Option::is_none")]
849    pub atlas: Option<String>,
850    #[serde(default, skip_serializing_if = "Option::is_none")]
851    pub grid: Option<[u32; 2]>,
852    #[serde(default, skip_serializing_if = "Option::is_none")]
853    pub frames: Option<Vec<[u32; 4]>>,
854    #[serde(default, skip_serializing_if = "Option::is_none")]
855    pub fps: Option<f32>,
856    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
857    pub clips: BTreeMap<String, SpriteClip>,
858}
859
860impl Animation {
861    pub const SPEED: f32 = 1.0;
862    pub const START: f32 = 0.0;
863    pub const FPS: f32 = 12.0;
864
865    pub fn is_sprite(&self) -> bool {
866        self.atlas.is_some()
867    }
868
869    pub fn frame_count(&self) -> Option<u32> {
870        if let Some([columns, rows]) = self.grid {
871            return Some(columns.saturating_mul(rows));
872        }
873        self.frames
874            .as_ref()
875            .map(|frames| u32::try_from(frames.len()).unwrap_or(u32::MAX))
876    }
877}
878
879#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
880#[serde(rename_all = "snake_case")]
881pub enum TilePlane {
882    #[default]
883    Xy,
884    Xz,
885    Yz,
886}
887
888impl TilePlane {
889    pub fn axes(self) -> ([f32; 3], [f32; 3]) {
890        match self {
891            Self::Xy => ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]),
892            Self::Xz => ([1.0, 0.0, 0.0], [0.0, 0.0, -1.0]),
893            Self::Yz => ([0.0, 0.0, -1.0], [0.0, 1.0, 0.0]),
894        }
895    }
896}
897
898#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
899#[serde(untagged)]
900pub enum CellSize {
901    Square(f32),
902    Sides([f32; 2]),
903}
904
905either!(CellSize, "a number or two numbers", number => Square, seq => Sides([f32; 2]),);
906
907impl CellSize {
908    pub fn sides(self) -> [f32; 2] {
909        match self {
910            Self::Square(side) => [side; 2],
911            Self::Sides(sides) => sides,
912        }
913    }
914}
915
916#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
917#[serde(deny_unknown_fields)]
918pub struct TileCell {
919    pub at: [i32; 2],
920    pub tile: String,
921}
922
923#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
924#[serde(deny_unknown_fields)]
925pub struct Tiles {
926    #[serde(default, skip_serializing_if = "Option::is_none")]
927    pub id: Option<Id>,
928    pub name: String,
929    #[serde(default, skip_serializing_if = "Option::is_none")]
930    pub cell: Option<CellSize>,
931    #[serde(default, skip_serializing_if = "is_zero3")]
932    pub origin: [f32; 3],
933    #[serde(default, skip_serializing_if = "Option::is_none")]
934    pub plane: Option<TilePlane>,
935    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
936    pub palette: BTreeMap<String, String>,
937    #[serde(default, skip_serializing_if = "Option::is_none")]
938    pub rows: Option<Vec<String>>,
939    #[serde(default, skip_serializing_if = "Option::is_none")]
940    pub cells: Option<Vec<TileCell>>,
941    #[serde(default, skip_serializing_if = "Option::is_none")]
942    pub authoring: Option<toml::Value>,
943}
944
945impl Tiles {
946    pub const CELL: [f32; 2] = [1.0; 2];
947    pub const EMPTY: char = '.';
948
949    pub fn cell_sides(&self) -> [f32; 2] {
950        self.cell.map_or(Self::CELL, CellSize::sides)
951    }
952
953    pub fn cells(&self) -> Vec<([i32; 2], &str)> {
954        let mut out = Vec::new();
955        if let Some(rows) = &self.rows {
956            for (place, row) in rows.iter().enumerate() {
957                let j = (rows.len() - 1 - place) as i32;
958                for (i, (at, character)) in row.char_indices().enumerate() {
959                    if character != Self::EMPTY {
960                        out.push(([i as i32, j], &row[at..at + character.len_utf8()]));
961                    }
962                }
963            }
964        }
965        for cell in self.cells.iter().flatten() {
966            out.push((cell.at, cell.tile.as_str()));
967        }
968        out
969    }
970
971    pub fn position(&self, at: [i32; 2]) -> [f32; 3] {
972        let (across, up) = self.plane.unwrap_or_default().axes();
973        let [width, height] = self.cell_sides();
974        let (i, j) = (at[0] as f32 * width, at[1] as f32 * height);
975        [
976            self.origin[0] + i * across[0] + j * up[0],
977            self.origin[1] + i * across[1] + j * up[1],
978            self.origin[2] + i * across[2] + j * up[2],
979        ]
980    }
981}
982
983#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
984#[serde(rename_all = "snake_case")]
985pub enum ProxyKind {
986    Box,
987    Hull,
988}
989
990#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
991#[serde(deny_unknown_fields)]
992pub struct Proxy {
993    #[serde(default, skip_serializing_if = "Option::is_none")]
994    pub id: Option<Id>,
995    pub object: String,
996    pub kind: ProxyKind,
997    #[serde(default, skip_serializing_if = "Option::is_none")]
998    pub at: Option<[f32; 3]>,
999    #[serde(default, skip_serializing_if = "Option::is_none")]
1000    pub rotate: Option<[f32; 3]>,
1001    #[serde(default, skip_serializing_if = "Option::is_none")]
1002    pub size: Option<[f32; 3]>,
1003    #[serde(default, skip_serializing_if = "Option::is_none")]
1004    pub points: Option<Vec<[f32; 3]>>,
1005    #[serde(default, skip_serializing_if = "Option::is_none")]
1006    pub triangles: Option<Vec<[u32; 3]>>,
1007    #[serde(default, skip_serializing_if = "Option::is_none")]
1008    pub authoring: Option<toml::Value>,
1009}