dae_parser/physics/
model.rs

1use crate::*;
2
3/// Allows for building complex combinations of rigid bodies and constraints
4/// that may be instantiated multiple times.
5#[derive(Clone, Default, Debug)]
6pub struct PhysicsModel {
7    /// A text string containing the unique identifier of the element.
8    pub id: Option<String>,
9    /// The text string name of this element.
10    pub name: Option<String>,
11    /// Asset management information about this element.
12    pub asset: Option<Box<Asset>>,
13    /// Defines a [`RigidBody`] element and sets its nondefault properties.
14    pub rigid_body: Vec<RigidBody>,
15    /// Defines a [`RigidConstraint`] element and allows for overriding
16    /// some or all of its properties.
17    pub rigid_constraint: Vec<RigidConstraint>,
18    /// Instantiates a physics model from the given url,
19    /// and assigns an sid to it, to distinguish it from other child elements.
20    pub instances: Vec<Instance<PhysicsModel>>,
21    /// Provides arbitrary additional information about this element.
22    pub extra: Vec<Extra>,
23}
24
25impl XNode for PhysicsModel {
26    const NAME: &'static str = "physics_model";
27    fn parse(element: &Element) -> Result<Self> {
28        debug_assert_eq!(element.name(), Self::NAME);
29        let mut it = element.children().peekable();
30        Ok(PhysicsModel {
31            id: element.attr("id").map(Into::into),
32            name: element.attr("name").map(Into::into),
33            asset: Asset::parse_opt_box(&mut it)?,
34            rigid_body: RigidBody::parse_list(&mut it)?,
35            rigid_constraint: RigidConstraint::parse_list(&mut it)?,
36            instances: Instance::parse_list(&mut it)?,
37            extra: Extra::parse_many(it)?,
38        })
39    }
40}
41
42impl XNodeWrite for PhysicsModel {
43    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
44        let mut e = Self::elem();
45        e.opt_attr("id", &self.id);
46        e.opt_attr("name", &self.name);
47        let e = e.start(w)?;
48        self.asset.write_to(w)?;
49        self.rigid_body.write_to(w)?;
50        self.rigid_constraint.write_to(w)?;
51        self.instances.write_to(w)?;
52        self.extra.write_to(w)?;
53        e.end(w)
54    }
55}
56
57/// Extra data associated to [`Instance`]<[`PhysicsModel`]>.
58#[derive(Clone, Debug, Default)]
59pub struct InstancePhysicsModelData {
60    /// Points to the id of a node in the visual scene. This allows a physics model to be
61    /// instantiated under a specific transform node, which will dictate the initial position
62    /// and orientation, and could be animated to influence kinematic rigid bodies.
63    pub parent: Option<UrlRef<Node>>,
64    /// Instantiates a [`ForceField`] element to influence this physics model.
65    pub instance_force_field: Vec<Instance<ForceField>>,
66    /// Instantiates a [`RigidBody`] element and allows for overriding some or all of its
67    /// properties.
68    ///
69    /// The target attribute defines the [`Node`] element that has its transforms overwritten
70    /// by this rigid-body instance.
71    pub instance_rigid_body: Vec<InstanceRigidBody>,
72    /// Instantiates a [`RigidConstraint`] element to override some of its properties.
73    /// This element does not have a `target` field because its [`RigidConstraint`]
74    /// children define which [`Node`] elements are targeted.
75    pub instance_rigid_constraint: Vec<InstanceRigidConstraint>,
76}
77
78impl XNodeWrite for InstancePhysicsModelData {
79    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
80        self.instance_force_field.write_to(w)?;
81        self.instance_rigid_body.write_to(w)?;
82        self.instance_rigid_constraint.write_to(w)
83    }
84}
85
86impl Instantiate for PhysicsModel {
87    const INSTANCE: &'static str = "instance_physics_model";
88    type Data = InstancePhysicsModelData;
89    fn parse_data(e: &Element, it: &mut ElementIter<'_>) -> Result<Self::Data> {
90        Ok(InstancePhysicsModelData {
91            parent: parse_attr(e.attr("parent"))?,
92            instance_force_field: Instance::parse_list(it)?,
93            instance_rigid_body: InstanceRigidBody::parse_list(it)?,
94            instance_rigid_constraint: InstanceRigidConstraint::parse_list(it)?,
95        })
96    }
97
98    fn is_empty(data: &Self::Data) -> bool {
99        data.instance_force_field.is_empty()
100            && data.instance_rigid_body.is_empty()
101            && data.instance_rigid_constraint.is_empty()
102    }
103
104    fn write_attr(data: &Self::Data, e: &mut ElemBuilder) {
105        e.opt_print_attr("parent", &data.parent)
106    }
107}
108
109impl CollectLocalMaps for InstancePhysicsModelData {
110    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
111        self.instance_rigid_body.collect_local_maps(maps);
112    }
113}
114
115/// Describes simulated bodies that do not deform.
116///
117/// These bodies may or may not be connected by constraints (hinge, ball-joint, and so on).
118#[derive(Clone, Default, Debug)]
119pub struct RigidBody {
120    /// A text string containing the scoped identifier of the [`RigidBody`] element.
121    /// This value must be unique among its sibling elements.
122    /// Associates each rigid body with a visual [`Node`] when a [`PhysicsModel`]
123    /// is instantiated.
124    pub sid: Option<String>,
125    /// The text string name of this element.
126    pub name: Option<String>,
127    /// Specifies rigid-body information for the common profile
128    /// that every COLLADA implmentation must support.
129    pub common: RigidBodyCommon,
130    /// Declares the information used to process some portion of the content. (optional)
131    pub technique: Vec<Technique>,
132    /// Provides arbitrary additional information about this element.
133    pub extra: Vec<Extra>,
134}
135
136impl Deref for RigidBody {
137    type Target = RigidBodyCommon;
138
139    fn deref(&self) -> &Self::Target {
140        &self.common
141    }
142}
143
144impl XNode for RigidBody {
145    const NAME: &'static str = "rigid_body";
146    fn parse(element: &Element) -> Result<Self> {
147        debug_assert_eq!(element.name(), Self::NAME);
148        let mut it = element.children().peekable();
149        Ok(RigidBody {
150            sid: element.attr("sid").map(Into::into),
151            name: element.attr("name").map(Into::into),
152            common: parse_one(Technique::COMMON, &mut it, |e| {
153                RigidBodyCommon::parse(e.children().peekable())
154            })?,
155            technique: Technique::parse_list(&mut it)?,
156            extra: Extra::parse_many(it)?,
157        })
158    }
159}
160
161impl XNodeWrite for RigidBody {
162    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
163        let mut e = Self::elem();
164        e.opt_attr("sid", &self.sid);
165        e.opt_attr("name", &self.name);
166        let e = e.start(w)?;
167        let common = ElemBuilder::new(Technique::COMMON).start(w)?;
168        self.common.write_to(w)?;
169        common.end(w)?;
170        self.technique.write_to(w)?;
171        self.extra.write_to(w)?;
172        e.end(w)
173    }
174}
175
176impl CollectLocalMaps for RigidBody {
177    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
178        self.common.collect_local_maps(maps);
179    }
180}
181
182/// Specifies rigid-body information for the common profile
183/// that every COLLADA implmentation must support.
184#[derive(Clone, Default, Debug)]
185pub struct RigidBodyCommon {
186    /// A Boolean that specifies whether the [`RigidBody`] is movable, if present.
187    pub dynamic: Option<bool>,
188    /// Contains a floating-point value that specifies the total mass of the [`RigidBody`].
189    pub mass: Option<f32>,
190    /// Defines the center and orientation of mass of the [`RigidBody`] relative
191    /// to the local origin of the "root" shape.
192    /// This makes the off-diagonal elements of the inertia tensor
193    /// (products of inertia) all 0 and allows us to just store the diagonal elements
194    /// (moments of inertia).
195    pub mass_frame: Vec<RigidTransform>,
196    /// Contains three floating-point numbers, which are the diagonal elements of the
197    /// inertia tensor (moments of inertia), which is represented in the local frame
198    /// of the center of mass. See `mass_frame`.
199    pub inertia: Option<Box<[f32; 3]>>,
200    /// Defines or references a [`PhysicsMaterial`] for the [`RigidBody`].
201    pub physics_material: Option<Box<DefInstance<PhysicsMaterial>>>,
202    /// The components of the [`RigidBody`].
203    pub shape: Vec<Shape>,
204}
205
206impl RigidBodyCommon {
207    fn parse(mut it: ElementIter<'_>) -> Result<Self> {
208        let res = Self {
209            dynamic: parse_opt("dynamic", &mut it, parse_elem)?,
210            mass: parse_opt("mass", &mut it, parse_elem)?,
211            mass_frame: parse_opt("mass_frame", &mut it, |e| {
212                let mut it = e.children().peekable();
213                finish(parse_list_many(&mut it, RigidTransform::parse)?, it)
214            })?
215            .unwrap_or_default(),
216            inertia: parse_opt("inertia", &mut it, parse_array_n)?,
217            physics_material: parse_opt_many(&mut it, DefInstance::parse)?.map(Box::new),
218            shape: Shape::parse_list(&mut it)?,
219        };
220        finish(res, it)
221    }
222}
223
224impl XNodeWrite for RigidBodyCommon {
225    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
226        ElemBuilder::opt_print("dynamic", &self.dynamic, w)?;
227        ElemBuilder::opt_print("mass", &self.mass, w)?;
228        if !self.mass_frame.is_empty() {
229            let frame = ElemBuilder::new("mass_frame").start(w)?;
230            self.mass_frame.write_to(w)?;
231            frame.end(w)?
232        }
233        opt(&self.inertia, |e| {
234            ElemBuilder::print_arr("inertia", &**e, w)
235        })?;
236        self.physics_material.write_to(w)?;
237        self.shape.write_to(w)
238    }
239}
240
241impl CollectLocalMaps for RigidBodyCommon {
242    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
243        self.physics_material.collect_local_maps(maps);
244        self.shape.collect_local_maps(maps);
245    }
246}
247
248/// Instantiates an object described by a [`RigidBody`] within an [`Instance`]<[`PhysicsModel`]>.
249#[derive(Clone, Debug)]
250pub struct InstanceRigidBody {
251    /// Which [`RigidBody`] to instantiate.
252    pub body: NameRef<RigidBody>,
253    /// Which [`Node`] is influenced by this [`RigidBody`] instance.
254    /// Can refer to a local instance or external reference.
255    pub target: UrlRef<Node>,
256    /// Specifies the rigid-body information for the common
257    /// profile that all COLLADA implementations must support.
258    pub common: InstanceRigidBodyCommon,
259    /// Declares the information used to process some portion of the content. (optional)
260    pub technique: Vec<Technique>,
261    /// Provides arbitrary additional information about this element.
262    pub extra: Vec<Extra>,
263}
264
265impl InstanceRigidBody {
266    /// Construct a new `InstanceRigidBody` which initializes a [`RigidBody`] `body`
267    /// attached to [`Node`] `target`.
268    pub fn new(body: impl Into<String>, target: Url) -> Self {
269        Self {
270            body: Ref::new(body.into()),
271            target: Ref::new(target),
272            common: Default::default(),
273            technique: vec![],
274            extra: vec![],
275        }
276    }
277}
278
279impl Deref for InstanceRigidBody {
280    type Target = InstanceRigidBodyCommon;
281
282    fn deref(&self) -> &Self::Target {
283        &self.common
284    }
285}
286
287impl XNode for InstanceRigidBody {
288    const NAME: &'static str = "rigid_body";
289    fn parse(element: &Element) -> Result<Self> {
290        debug_assert_eq!(element.name(), Self::NAME);
291        let mut it = element.children().peekable();
292        Ok(InstanceRigidBody {
293            body: Ref::new(element.attr("body").ok_or("missing body attribute")?.into()),
294            target: parse_attr(element.attr("target"))?.ok_or("missing url attribute")?,
295            common: parse_one(Technique::COMMON, &mut it, InstanceRigidBodyCommon::parse)?,
296            technique: Technique::parse_list(&mut it)?,
297            extra: Extra::parse_many(it)?,
298        })
299    }
300}
301
302impl XNodeWrite for InstanceRigidBody {
303    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
304        let mut e = Self::elem();
305        e.print_attr("body", &self.body);
306        e.print_attr("target", &self.target);
307        let e = e.start(w)?;
308        let common = ElemBuilder::new(Technique::COMMON).start(w)?;
309        self.common.write_to(w)?;
310        common.end(w)?;
311        self.technique.write_to(w)?;
312        self.extra.write_to(w)?;
313        e.end(w)
314    }
315}
316
317impl CollectLocalMaps for InstanceRigidBody {
318    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
319        self.common.collect_local_maps(maps);
320    }
321}
322
323/// Specifies the rigid-body information for the common
324/// profile that all COLLADA implementations must support.
325#[derive(Clone, Default, Debug)]
326pub struct InstanceRigidBodyCommon {
327    /// Contains three floating-point values that
328    /// specify the initial angular velocity of the
329    /// rigid_body instance around each axis, in
330    /// the form of an x-y-z Euler rotation. The
331    /// measurement is in degrees per second.
332    pub angular_velocity: [f32; 3],
333    /// Contains three floating-point values that specify
334    /// the initial linear velocity of the [`RigidBody`] instance.
335    pub velocity: [f32; 3],
336    /// Additional fields are inherited from [`RigidBodyCommon`].
337    pub common: RigidBodyCommon,
338}
339
340impl Deref for InstanceRigidBodyCommon {
341    type Target = RigidBodyCommon;
342
343    fn deref(&self) -> &Self::Target {
344        &self.common
345    }
346}
347
348impl InstanceRigidBodyCommon {
349    /// Parse a [`InstanceRigidBodyCommon`] from a
350    /// `<instance_rigid_body>/<technique_common>` XML element.
351    pub fn parse(e: &Element) -> Result<Self> {
352        let mut it = e.children().peekable();
353        Ok(Self {
354            angular_velocity: parse_opt("angular_velocity", &mut it, parse_array_n)?
355                .map_or([0.; 3], |a| *a),
356            velocity: parse_opt("velocity", &mut it, parse_array_n)?.map_or([0.; 3], |a| *a),
357            common: RigidBodyCommon::parse(it)?,
358        })
359    }
360}
361
362impl XNodeWrite for InstanceRigidBodyCommon {
363    #[allow(clippy::float_cmp)]
364    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
365        if self.angular_velocity != [0.; 3] {
366            ElemBuilder::print_arr("angular_velocity", &self.angular_velocity, w)?;
367        }
368        if self.velocity != [0.; 3] {
369            ElemBuilder::print_arr("velocity", &self.velocity, w)?;
370        }
371        self.common.write_to(w)
372    }
373}
374
375impl CollectLocalMaps for InstanceRigidBodyCommon {
376    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
377        self.common.collect_local_maps(maps);
378    }
379}
380
381/// Connects components, such as [`RigidBody`], into complex physics models with moveable parts.
382#[derive(Clone, Debug)]
383pub struct RigidConstraint {
384    /// A text string containing the scoped identifier of the [`RigidConstraint`]
385    /// element. This value must be unique within the scope of the parent element.
386    pub sid: Option<String>,
387    /// The text string name of this element.
388    pub name: Option<String>,
389    /// Defines the attachment frame of reference (to a [`RigidBody`] or a [`Node`])
390    /// within a rigid constraint.
391    pub ref_attachment: Attachment,
392    /// Defines an attachment frame (to a [`RigidBody`] or a [`Node`])
393    /// within a rigid constraint.
394    pub attachment: Attachment,
395    /// Specifies rigid-constraint information for the common profile that all COLLADA
396    /// implementations must support.
397    pub common: RigidConstraintCommon,
398    /// Declares the information used to process some portion of the content. (optional)
399    pub technique: Vec<Technique>,
400    /// Provides arbitrary additional information about this element.
401    pub extra: Vec<Extra>,
402}
403
404impl RigidConstraint {
405    /// Construct a `RigidConstraint` with default arguments.
406    /// `ref_attachment` and `attachment` are URI references to a [`RigidBody`] or [`Node`].
407    /// It must refer to a [`RigidBody`] in `attachment` or in `ref_attachment`;
408    /// they cannot both be [`Node`]s.
409    pub fn new(ref_attachment: Url, attachment: Url) -> Self {
410        Self {
411            sid: Default::default(),
412            name: Default::default(),
413            ref_attachment: Attachment::new(ref_attachment),
414            attachment: Attachment::new(attachment),
415            common: Default::default(),
416            technique: Default::default(),
417            extra: Default::default(),
418        }
419    }
420}
421
422impl XNode for RigidConstraint {
423    const NAME: &'static str = "rigid_constraint";
424    fn parse(element: &Element) -> Result<Self> {
425        debug_assert_eq!(element.name(), Self::NAME);
426        let mut it = element.children().peekable();
427        Ok(RigidConstraint {
428            sid: element.attr("sid").map(Into::into),
429            name: element.attr("name").map(Into::into),
430            ref_attachment: parse_one(Attachment::REF, &mut it, Attachment::parse)?,
431            attachment: Attachment::parse_one(&mut it)?,
432            common: parse_one(Technique::COMMON, &mut it, RigidConstraintCommon::parse)?,
433            technique: Technique::parse_list(&mut it)?,
434            extra: Extra::parse_many(it)?,
435        })
436    }
437}
438
439impl XNodeWrite for RigidConstraint {
440    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
441        let mut e = Self::elem();
442        e.opt_attr("sid", &self.sid);
443        e.opt_attr("name", &self.name);
444        let e = e.start(w)?;
445        self.ref_attachment
446            .write_inner(ElemBuilder::new(Attachment::REF), w)?;
447        self.attachment.write_to(w)?;
448        let common = ElemBuilder::new(Technique::COMMON).start(w)?;
449        self.common.write_to(w)?;
450        common.end(w)?;
451        self.technique.write_to(w)?;
452        self.extra.write_to(w)?;
453        e.end(w)
454    }
455}
456
457/// Specifies rigid-constraint information for the common profile that all COLLADA
458/// implementations must support.
459#[derive(Clone, Default, Debug)]
460pub struct RigidConstraintCommon {
461    /// If false, the [`RigidConstraint`] doesn’t exert any force or influence on
462    /// the rigid bodies.
463    pub enabled: bool,
464    /// If true, the attached rigid bodies may interpenetrate.
465    pub interpenetrate: bool,
466    /// Describes the angular limits along each rotation axis in degrees.
467    pub swing_cone_and_twist: Limits,
468    /// Describes linear (translational) limits along each axis.
469    pub linear: Limits,
470    /// Describes angular spring constraints.
471    pub spring_angular: Spring,
472    /// Describes linear spring constraints.
473    pub spring_linear: Spring,
474}
475
476impl RigidConstraintCommon {
477    /// Parse a [`RigidConstraintCommon`] from a
478    /// `<rigid_constraint>/<technique_common>` XML element.
479    pub fn parse(e: &Element) -> Result<Self> {
480        let mut it = e.children().peekable();
481        let enabled = parse_opt("enabled", &mut it, parse_elem)?.unwrap_or(true);
482        let interpenetrate = parse_opt("interpenetrate", &mut it, parse_elem)?.unwrap_or(false);
483        let (swing_cone_and_twist, linear) = parse_opt("limits", &mut it, |e| {
484            let mut it = e.children().peekable();
485            let scat =
486                parse_opt("swing_cone_and_twist", &mut it, Limits::parse)?.unwrap_or_default();
487            let lin = parse_opt("linear", &mut it, Limits::parse)?.unwrap_or_default();
488            finish((scat, lin), it)
489        })?
490        .unwrap_or_default();
491        let (spring_angular, spring_linear) = parse_opt("spring", &mut it, |e| {
492            let mut it = e.children().peekable();
493            let ang = parse_opt("angular", &mut it, Spring::parse)?.unwrap_or_default();
494            let lin = parse_opt("linear", &mut it, Spring::parse)?.unwrap_or_default();
495            finish((ang, lin), it)
496        })?
497        .unwrap_or_default();
498        let res = Self {
499            enabled,
500            interpenetrate,
501            swing_cone_and_twist,
502            linear,
503            spring_angular,
504            spring_linear,
505        };
506        finish(res, it)
507    }
508}
509
510impl XNodeWrite for RigidConstraintCommon {
511    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
512        ElemBuilder::def_print("enabled", self.enabled, true, w)?;
513        ElemBuilder::def_print("interpenetrate", self.interpenetrate, false, w)?;
514        macro_rules! write_lim {
515            ($n:expr, $a1:ident: $n1:expr, $a2:ident: $n2:expr) => {
516                let has1 = !self.$a1.is_empty();
517                let has2 = !self.$a2.is_empty();
518                if has1 || has2 {
519                    let e = ElemBuilder::new($n).start(w)?;
520                    if has1 {
521                        self.$a1.write_to($n1, w)?
522                    }
523                    if has2 {
524                        self.$a2.write_to($n2, w)?
525                    }
526                    e.end(w)?
527                }
528            };
529        }
530        write_lim!("limits", swing_cone_and_twist: "swing_cone_and_twist", linear: "linear");
531        write_lim!("spring", spring_angular: "angular", spring_linear: "linear");
532        Ok(())
533    }
534}
535
536/// Instantiates an object described by a [`RigidConstraint`]
537/// within an [`Instance`]<[`PhysicsModel`]>.
538#[derive(Clone, Debug)]
539pub struct InstanceRigidConstraint {
540    /// Which [`RigidConstraint`] to instantiate.
541    pub constraint: NameRef<RigidConstraint>,
542    /// Provides arbitrary additional information about this element.
543    pub extra: Vec<Extra>,
544}
545
546impl InstanceRigidConstraint {
547    /// Construct a new `InstanceRigidConstraint`.
548    pub fn new(constraint: impl Into<String>) -> Self {
549        Self {
550            constraint: Ref::new(constraint.into()),
551            extra: vec![],
552        }
553    }
554}
555
556impl XNode for InstanceRigidConstraint {
557    const NAME: &'static str = "instance_rigid_constraint";
558    fn parse(e: &Element) -> Result<Self> {
559        debug_assert_eq!(e.name(), Self::NAME);
560        let constraint = e.attr("constraint").ok_or("missing constraint attr")?;
561        Ok(InstanceRigidConstraint {
562            constraint: Ref::new(constraint.into()),
563            extra: Extra::parse_many(e.children())?,
564        })
565    }
566}
567
568impl XNodeWrite for InstanceRigidConstraint {
569    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
570        let mut e = Self::elem();
571        e.print_attr("constraint", &self.constraint);
572        let e = e.start(w)?;
573        self.extra.write_to(w)?;
574        e.end(w)
575    }
576}
577
578/// A set of min/max limits used for both linear and angular constraints.
579#[derive(Clone, Default, Debug)]
580pub struct Limits {
581    /// The minimum value in each direction.
582    pub min: Option<Box<[f32; 3]>>,
583    /// The maximum value in each direction.
584    pub max: Option<Box<[f32; 3]>>,
585}
586
587impl Limits {
588    /// Parse a [`Limits`] from a `<swing_cone_and_twist>` or `<linear>` XML element.
589    pub fn parse(e: &Element) -> Result<Self> {
590        let mut it = e.children().peekable();
591        let res = Self {
592            min: parse_opt("min", &mut it, parse_array_n)?,
593            max: parse_opt("max", &mut it, parse_array_n)?,
594        };
595        finish(res, it)
596    }
597
598    /// Is this [`Limits`] instance set to the default value?
599    pub fn is_empty(&self) -> bool {
600        self.min.is_none() && self.max.is_none()
601    }
602
603    fn write_to<W: Write>(&self, name: &str, w: &mut XWriter<W>) -> Result<()> {
604        let e = ElemBuilder::new(name).start(w)?;
605        opt(&self.min, |e| ElemBuilder::print_arr("min", &**e, w))?;
606        opt(&self.max, |e| ElemBuilder::print_arr("max", &**e, w))?;
607        e.end(w)
608    }
609}
610
611/// A spring constraint, used for both linear and angular constraints.
612#[derive(Clone, Copy, Debug, PartialEq)]
613pub struct Spring {
614    /// The `stiffness` (also called spring coefficient)
615    /// has units of force/distance for `spring_linear`
616    /// or force/angle in degrees for `spring_angular`.
617    pub stiffness: f32,
618    /// The damping coefficient of the spring.
619    pub damping: f32,
620    /// The resting position of the spring.
621    pub target_value: f32,
622}
623
624impl Default for Spring {
625    fn default() -> Self {
626        Self {
627            stiffness: 1.,
628            damping: 0.,
629            target_value: 0.,
630        }
631    }
632}
633
634impl Spring {
635    /// Parse a [`Spring`] from a `<linear>` or `<angular>` XML element.
636    pub fn parse(e: &Element) -> Result<Self> {
637        let mut it = e.children().peekable();
638        let res = Self {
639            stiffness: parse_opt("stiffness", &mut it, parse_elem)?.unwrap_or(1.),
640            damping: parse_opt("damping", &mut it, parse_elem)?.unwrap_or(0.),
641            target_value: parse_opt("target_value", &mut it, parse_elem)?.unwrap_or(0.),
642        };
643        finish(res, it)
644    }
645
646    /// Is this [`Spring`] instance set to the default value?
647    pub fn is_empty(&self) -> bool {
648        *self == Default::default()
649    }
650
651    fn write_to<W: Write>(&self, name: &str, w: &mut XWriter<W>) -> Result<()> {
652        let e = ElemBuilder::new(name).start(w)?;
653        ElemBuilder::def_print("stiffness", self.stiffness, 1., w)?;
654        ElemBuilder::def_print("damping", self.damping, 0., w)?;
655        ElemBuilder::def_print("target_value", self.target_value, 0., w)?;
656        e.end(w)
657    }
658}