mesh_loader/collada/
mod.rs

1//! [COLLADA] (.dae) parser.
2//!
3//! [COLLADA]: https://en.wikipedia.org/wiki/COLLADA
4
5#![allow(clippy::wildcard_imports)] // TODO
6#![allow(clippy::many_single_char_names)]
7
8mod effect;
9mod error;
10mod geometry;
11mod image;
12mod instance;
13mod iter;
14mod material;
15mod scene;
16
17use std::{
18    cmp,
19    collections::{BTreeMap, HashMap},
20    fmt, io,
21    marker::PhantomData,
22    ops,
23    path::Path,
24    str::{self, FromStr},
25};
26
27use self::{effect::*, geometry::*, image::*, material::*, scene::*};
28use crate::{
29    common,
30    utils::{
31        float, hex,
32        utf16::decode_string,
33        xml::{self, XmlNodeExt},
34    },
35    Color4,
36};
37
38/// Parses meshes from bytes of COLLADA text.
39#[inline]
40pub fn from_slice(bytes: &[u8]) -> io::Result<common::Scene> {
41    from_slice_internal(bytes, None)
42}
43
44/// Parses meshes from a string of COLLADA text.
45#[inline]
46pub fn from_str(s: &str) -> io::Result<common::Scene> {
47    from_str_internal(s, None)
48}
49
50#[inline]
51pub(crate) fn from_slice_internal(bytes: &[u8], path: Option<&Path>) -> io::Result<common::Scene> {
52    let bytes = &decode_string(bytes)?;
53    from_str_internal(bytes, path)
54}
55
56#[inline]
57pub(crate) fn from_str_internal(s: &str, path: Option<&Path>) -> io::Result<common::Scene> {
58    let xml = xml::Document::parse(s).map_err(crate::error::invalid_data)?;
59    let mut collada = Document::parse(&xml)?;
60    Ok(instance::build(&mut collada, path.and_then(Path::parent)))
61}
62
63// Inspired by gltf-json's `Get` trait.
64/// Helper trait for retrieving top-level objects by a universal identifier.
65trait Get<T> {
66    type Target;
67
68    fn get(&self, uri: &T) -> Option<&Self::Target>;
69}
70
71macro_rules! impl_get_by_uri {
72    ($ty:ty, $($field:ident).*) => {
73        impl<'a> Get<Uri<'a, $ty>> for Document<'a> {
74            type Target = $ty;
75
76            fn get(&self, index: &Uri<'a, $ty>) -> Option<&Self::Target> {
77                self.$($field).*.get(&*index.0)
78            }
79        }
80    };
81}
82
83impl_get_by_uri!(Accessor<'a>, library_geometries.accessors);
84impl_get_by_uri!(ArrayData<'a>, library_geometries.array_data);
85impl_get_by_uri!(Effect<'a>, library_effects.effects);
86impl_get_by_uri!(Geometry<'a>, library_geometries.geometries);
87impl_get_by_uri!(Image<'a>, library_images.images);
88impl_get_by_uri!(Material<'a>, library_materials.materials);
89
90struct Uri<'a, T>(&'a str, PhantomData<fn() -> T>);
91
92impl<'a, T> Uri<'a, T> {
93    fn parse(url: &'a str) -> io::Result<Self> {
94        // skipping the leading #, hopefully the remaining text is the accessor ID only
95        if let Some(id) = url.strip_prefix('#') {
96            Ok(Self(id, PhantomData))
97        } else {
98            Err(format_err!("unknown reference format {:?}", url))
99        }
100    }
101
102    fn from_id(id: &'a str) -> Self {
103        Self(id, PhantomData)
104    }
105
106    fn cast<U>(self) -> Uri<'a, U> {
107        Uri(self.0, PhantomData)
108    }
109
110    fn as_str(&self) -> &'a str {
111        self.0
112    }
113}
114
115impl<T> PartialEq for Uri<'_, T> {
116    fn eq(&self, other: &Self) -> bool {
117        self.0 == other.0
118    }
119}
120
121impl<T> Eq for Uri<'_, T> {}
122
123impl<T, S> PartialEq<S> for Uri<'_, T>
124where
125    S: ?Sized + AsRef<str>,
126{
127    fn eq(&self, other: &S) -> bool {
128        self.0 == other.as_ref()
129    }
130}
131
132impl<T> PartialEq<Uri<'_, T>> for str {
133    #[inline]
134    fn eq(&self, other: &Uri<'_, T>) -> bool {
135        self == other.0
136    }
137}
138
139impl<T> PartialEq<Uri<'_, T>> for &str {
140    #[inline]
141    fn eq(&self, other: &Uri<'_, T>) -> bool {
142        *self == other.0
143    }
144}
145
146trait ColladaXmlNodeExt<'a, 'input> {
147    fn parse_url<T>(&self, name: &str) -> io::Result<Uri<'a, T>>;
148    // fn parse_url_opt<T>(&self, name: &str) -> io::Result<Option<Uri<'a, T>>>;
149}
150
151impl<'a, 'input> ColladaXmlNodeExt<'a, 'input> for xml::Node<'a, 'input> {
152    fn parse_url<T>(&self, name: &str) -> io::Result<Uri<'a, T>> {
153        let url = self.required_attribute(name)?;
154        Uri::parse(url).map_err(|e| {
155            format_err!(
156                "{} in {} attribute of <{}> element at {}",
157                e,
158                name,
159                self.tag_name().name(),
160                self.attr_value_location(name),
161            )
162        })
163    }
164
165    // fn parse_url_opt<T>(&self, name: &str) -> io::Result<Option<Uri<'a, T>>> {
166    //     if let Some(url) = self.attribute(name) {
167    //         Uri::parse(url).map(Some).map_err(|e| {
168    //             format_err!(
169    //                 "{} in {} attribute of <{}> element at {}",
170    //                 e,
171    //                 name,
172    //                 self.tag_name().name(),
173    //                 self.attr_value_location(name),
174    //             )
175    //         })
176    //     } else {
177    //         Ok(None)
178    //     }
179    // }
180}
181
182#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
183struct Version {
184    minor: u32,
185    patch: u32,
186}
187
188impl Version {
189    const MIN: Self = Self::new(4, 0);
190
191    const fn new(minor: u32, patch: u32) -> Self {
192        Self { minor, patch }
193    }
194    fn is_1_4(self) -> bool {
195        self >= Self::new(4, 0) && self < Self::new(5, 0)
196    }
197}
198
199impl FromStr for Version {
200    type Err = io::Error;
201
202    fn from_str(s: &str) -> Result<Self, Self::Err> {
203        (|| {
204            let mut digits = s.splitn(3, '.');
205            let major = digits.next()?;
206            if major != "1" {
207                return None;
208            }
209            let minor = digits.next()?.parse().ok()?;
210            let patch = digits.next()?.parse().ok()?;
211            Some(Self::new(minor, patch))
212        })()
213        .ok_or_else(|| format_err!("unrecognized version format {:?}", s))
214    }
215}
216
217impl fmt::Display for Version {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "1.{}.{}", self.minor, self.patch)
220    }
221}
222
223struct Context<'a> {
224    version: Version,
225    asset: Asset,
226    library_effects: LibraryEffects<'a>,
227    library_geometries: LibraryGeometries<'a>,
228    library_images: LibraryImages<'a>,
229    library_materials: LibraryMaterials<'a>,
230    library_visual_scenes: LibraryVisualScenes<'a>,
231    scene: Scene<'a>,
232}
233
234struct Document<'a> {
235    asset: Asset,
236    library_effects: LibraryEffects<'a>,
237    library_geometries: LibraryGeometries<'a>,
238    library_images: LibraryImages<'a>,
239    library_materials: LibraryMaterials<'a>,
240    library_visual_scenes: LibraryVisualScenes<'a>,
241    scene: Scene<'a>,
242}
243
244impl<'a> Document<'a> {
245    /*
246    The `<COLLADA>` element.
247
248    Attributes:
249    - `version` (Required)
250    - `xmlns` (xs:anyURI)
251    - `base` (xs:anyURI)
252
253    Child elements must appear in the following order if present:
254    - `<asset>` (1)
255    - library_element (0 or more)
256        Any quantity and combination of any library elements can appear in any order:
257        - `<library_animation_clips>`
258        - `<library_animations>`
259        - `<library_articulated_systems>` (in Kinematics)
260        - `<library_cameras>`
261        - `<library_controllers>`
262        - `<library_effects>` (in FX)
263        - `<library_force_fields>` (in Physics)
264        - `<library_formulas>`
265        - `<library_geometries>`
266        - `<library_images>` (in FX)
267        - `<library_joints>` (in Kinematics)
268        - `<library_kinematics_models>` (in Kinematics)
269        - `<library_kinematics_scenes>` (in Kinematics)
270        - `<library_lights>`
271        - `<library_materials>` (in FX)
272        - `<library_nodes>`
273        - `<library_physics_materials>` (in Physics)
274        - `<library_physics_models>` (in Physics)
275        - `<library_physics_scenes>` (in Physics)
276        - `<library_visual_scenes>`
277    - `<scene>` (0 or 1)
278    - `<extra>` (0 or more)
279    */
280    fn parse(doc: &'a xml::Document<'_>) -> io::Result<Self> {
281        let node = doc.root_element();
282        if node.tag_name().name() != "COLLADA" {
283            bail!("root element is not <COLLADA>");
284        }
285
286        let version: Version = node.required_attribute("version")?.parse()?;
287        if version < Version::MIN {
288            bail!("collada schema version {} is not supported", version);
289        };
290        // debug!("collada schema version is {}", version);
291
292        let mut cx = Context {
293            version,
294            asset: Asset {
295                unit: DEFAULT_UNIT_SIZE,
296            },
297            library_effects: LibraryEffects::default(),
298            library_geometries: LibraryGeometries::default(),
299            library_images: LibraryImages::default(),
300            library_materials: LibraryMaterials::default(),
301            library_visual_scenes: LibraryVisualScenes::default(),
302            scene: Scene::default(),
303        };
304
305        for node in node.element_children() {
306            match node.tag_name().name() {
307                "library_effects" => {
308                    parse_library_effects(&mut cx, node)?;
309                }
310                "library_geometries" => {
311                    parse_library_geometries(&mut cx, node)?;
312                }
313                "library_images" => {
314                    parse_library_images(&mut cx, node)?;
315                }
316                "library_materials" => {
317                    parse_library_materials(&mut cx, node)?;
318                }
319                "library_visual_scenes" => {
320                    parse_library_visual_scenes(&mut cx, node)?;
321                }
322                "asset" => {
323                    cx.asset = Asset::parse(node)?;
324                }
325                "scene" => {
326                    cx.scene = parse_scene(&mut cx, node)?;
327                }
328                _name => {
329                    // debug!("ignored <{}> element", name);
330                }
331            }
332        }
333
334        Ok(Self {
335            asset: cx.asset,
336            library_effects: cx.library_effects,
337            library_geometries: cx.library_geometries,
338            library_images: cx.library_images,
339            library_materials: cx.library_materials,
340            library_visual_scenes: cx.library_visual_scenes,
341            scene: cx.scene,
342        })
343    }
344
345    fn get<T>(&self, url: &T) -> Option<&<Self as Get<T>>::Target>
346    where
347        Self: Get<T>,
348    {
349        <Self as Get<T>>::get(self, url)
350    }
351}
352
353impl<T> ops::Index<&T> for Document<'_>
354where
355    Self: Get<T>,
356{
357    type Output = <Self as Get<T>>::Target;
358
359    #[track_caller]
360    fn index(&self, url: &T) -> &Self::Output {
361        self.get(url).expect("no entry found for key")
362    }
363}
364
365const DEFAULT_UNIT_SIZE: f32 = 1.;
366
367/// The `<asset>` element of the `<COLLADA>` element.
368struct Asset {
369    // <unit meter="<float>" name="..."/>
370    unit: f32,
371}
372
373impl Asset {
374    fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
375        debug_assert_eq!(node.tag_name().name(), "asset");
376
377        let mut unit = None;
378        for child in node.element_children() {
379            match child.tag_name().name() {
380                "unit" => {
381                    if let Some(v) = child.attribute("meter") {
382                        let v = xml::comma_to_period(v);
383                        unit = Some(v.parse().map_err(|e| {
384                            format_err!(
385                                "{} in <{}> element at {}: {:?}",
386                                e,
387                                child.tag_name().name(),
388                                child.attr_value_location("meter"),
389                                v
390                            )
391                        })?);
392                    }
393                }
394                "up_axis" => {} // TODO
395                _ => { /* ignore */ }
396            }
397        }
398
399        Ok(Self {
400            unit: unit.unwrap_or(DEFAULT_UNIT_SIZE),
401        })
402    }
403}
404
405struct Source<'a> {
406    // Required
407    id: &'a str,
408    // // Optional
409    // name: Option<&'a str>,
410
411    // 0 or 1
412    array_element: Option<ArrayElement<'a>>,
413    // 0 or 1
414    accessor: Option<Accessor<'a>>,
415}
416
417impl<'a> Source<'a> {
418    /*
419    The `<source>` element (core)
420
421    Attributes:
422    - `id` (xs:ID, Required)
423    - `name` (xs:token, Optional)
424
425    Child elements must appear in the following order if present:
426    - `<asset>` (0 or 1)
427    - array_element (0 or 1)
428        Can be one of:
429        - `<bool_array>`
430        - `<float_array>`
431        - `<IDREF_array>`
432        - `<int_array>`
433        - `<Name_array>`
434        - `<SIDREF_array>`
435        - `<token_array>`
436    - `<technique_common>` (0 or 1)
437    - `<technique>` (core) (0 or more)
438    */
439    fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
440        debug_assert_eq!(node.tag_name().name(), "source");
441        let id = node.required_attribute("id")?;
442        let mut array_element = None;
443        let mut accessor = None;
444
445        for child in node.element_children() {
446            match child.tag_name().name() {
447                "float_array" | "IDREF_array" | "Name_array" => {
448                    array_element = Some(parse_array_element(child)?);
449                }
450                "technique_common" => {
451                    for technique in child.element_children() {
452                        match technique.tag_name().name() {
453                            "accessor" => {
454                                accessor = Some(Accessor::parse(technique)?);
455                            }
456                            _ => return Err(error::unexpected_child_elem(technique)),
457                        }
458                    }
459                }
460                "bool_array" | "int_array" | "SIDREF_array" | "token_array" => {
461                    // warn!(
462                    //     "ignored array element {} ({})",
463                    //     child.tag_name().name(),
464                    //     child.node_location()
465                    // );
466                }
467                "asset" | "technique" => { /* skip */ }
468                _ => return Err(error::unexpected_child_elem(child)),
469            }
470        }
471
472        Ok(Self {
473            id,
474            // name: node.attribute("name"),
475            array_element,
476            accessor,
477        })
478    }
479}
480
481struct ArrayElement<'a> {
482    // Required
483    id: &'a str,
484    // // Required
485    // count: u32,
486    data: ArrayData<'a>,
487}
488
489fn parse_array_element<'a>(node: xml::Node<'a, '_>) -> io::Result<ArrayElement<'a>> {
490    let name = node.tag_name().name();
491    let is_string_array = name == "IDREF_array" || name == "Name_array";
492
493    let id = node.required_attribute("id")?;
494    let count: u32 = node.parse_required_attribute("count")?;
495    let mut content = node.trimmed_text();
496
497    // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them
498    if content.is_empty() {
499        let data = if is_string_array {
500            ArrayData::String(vec![])
501        } else {
502            ArrayData::Float(vec![])
503        };
504        return Ok(ArrayElement {
505            id,
506            // count,
507            data,
508        });
509    }
510
511    if is_string_array {
512        // TODO: check large count
513        let mut values = Vec::with_capacity(count as usize);
514        for _ in 0..count {
515            if content.is_empty() {
516                bail!(
517                    "expected more values while reading <{}> contents at {}",
518                    node.tag_name().name(),
519                    node.node_location()
520                );
521            }
522
523            let mut n = 0;
524            while content
525                .as_bytes()
526                .first()
527                .map_or(false, |&b| !xml::is_whitespace(b as char))
528            {
529                n += 1;
530            }
531            values.push(&content[..n]);
532
533            content = xml::trim_start(content.get(n..).unwrap_or_default());
534        }
535
536        Ok(ArrayElement {
537            id,
538            // count,
539            data: ArrayData::String(values),
540        })
541    } else {
542        // TODO: check large count
543        let mut values = Vec::with_capacity(count as usize);
544        let content = xml::comma_to_period(content);
545        // TODO: include in parse_float_array_exact?
546        let map_err = |e| {
547            format_err!(
548                "{e} in <{}> element ({})",
549                node.tag_name().name(),
550                node.text_location(),
551            )
552        };
553        for res in xml::parse_float_array_exact(&content, count as usize) {
554            let value = res.map_err(map_err)?;
555            values.push(value);
556        }
557
558        Ok(ArrayElement {
559            id,
560            // count,
561            data: ArrayData::Float(values),
562        })
563    }
564}
565
566/// Data source array.
567enum ArrayData<'a> {
568    /// <float_array>
569    Float(Vec<f32>),
570    /// <IDREF_array> or <Name_array>
571    String(
572        #[allow(dead_code)] // TODO
573        Vec<&'a str>,
574    ),
575    // /// <int_array>
576    // Int(Vec<i32>),
577    // /// <bool_array>
578    // Bool(Vec<bool>),
579}
580
581impl ArrayData<'_> {
582    // fn is_float(&self) -> bool {
583    //     matches!(self, Self::Float(..))
584    // }
585    // fn is_string(&self) -> bool {
586    //     matches!(self, Self::String(..))
587    // }
588
589    fn as_float(&self) -> Option<&[f32]> {
590        match self {
591            Self::Float(v) => Some(v),
592            Self::String(..) => None,
593        }
594    }
595    // fn as_string(&self) -> Option<&[&'a str]> {
596    //     match self {
597    //         Self::String(v) => Some(v),
598    //         _ => None,
599    //     }
600    // }
601
602    // fn len(&self) -> usize {
603    //     match self {
604    //         Self::Float(v) => v.len(),
605    //         Self::String(v) => v.len(),
606    //         Self::Int(v) => v.len(),
607    //         Self::Bool(v) => v.len(),
608    //     }
609    // }
610    // fn is_empty(&self) -> bool {
611    //     match self {
612    //         Self::Float(v) => v.is_empty(),
613    //         Self::String(v) => v.is_empty(),
614    //         Self::Int(v) => v.is_empty(),
615    //         Self::Bool(v) => v.is_empty(),
616    //     }
617    // }
618}
619
620struct Accessor<'a> {
621    // Required
622    count: u32,
623    // // Optional
624    // offset: u32,
625    // Required
626    source: Uri<'a, ArrayData<'a>>,
627    // Optional
628    stride: u32,
629
630    // 0 or more
631    params: Vec<Param<'a>>,
632}
633
634impl<'a> Accessor<'a> {
635    /*
636    The `<accessor>` element
637
638    Attributes:
639    - `count` (uint_type, Required)
640    - `offset` (uint_type, Optional, default: 0)
641    - `source` (xs:anyURI, Required)
642    - `stride` (uint_type, Optional, default: 1)
643
644    Child elements:
645    - `<param>` (0 or more)
646    */
647    fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
648        debug_assert_eq!(node.tag_name().name(), "accessor");
649        let count: u32 = node.parse_required_attribute("count")?;
650        let source = node.parse_url("source")?;
651        let _offset: u32 = node.parse_attribute("offset")?.unwrap_or(0);
652        let stride: u32 = node.parse_attribute("stride")?.unwrap_or(1);
653        let mut params = vec![];
654
655        for child in node.element_children() {
656            match child.tag_name().name() {
657                "param" => {
658                    params.push(Param::parse(child)?);
659                }
660                _ => return Err(error::unexpected_child_elem(child)),
661            }
662        }
663
664        Ok(Self {
665            count,
666            // offset,
667            source,
668            stride,
669            params,
670        })
671    }
672}
673
674/// The `<param>` element (data flow).
675///
676/// See the specifications ([1.4], [1.5]) for details.
677///
678/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=125
679/// [1.5]: https://www.khronos.org/files/collada_spec_1_5.pdf#page=144
680struct Param<'a> {
681    // /// The name of this element.
682    // name: Option<&'a str>,
683    // /// The scoped identifier of this element.
684    // sid: Option<&'a str>,
685    // Required
686    ty: &'a str,
687    // // Optional
688    // semantic: Option<&'a str>,
689}
690
691impl<'a> Param<'a> {
692    /*
693    The `<param>` element (data flow)
694    Attributes:
695    - `name` (xs:token, Optional)
696    - `sid` (sid_type, Optional)
697    - `type` (xs:NMTOKEN, Required)
698    - `semantic` (xs:NMTOKEN, Optional)
699
700    Child elements: None
701    */
702    fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
703        let ty = node.required_attribute("type")?;
704        // let name = node.attribute("name");
705        // let sid = node.attribute("sid");
706        // let semantic = node.attribute("semantic");
707        if let Some(child) = node.element_children().next() {
708            return Err(error::unexpected_child_elem(child));
709        }
710        Ok(Self {
711            // name,
712            // sid,
713            ty,
714            // semantic,
715        })
716    }
717}
718
719/// The `<input>` element (shared).
720///
721/// See the specifications ([1.4], [1.5]) for details.
722///
723/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=73
724/// [1.5]: https://www.khronos.org/files/collada_spec_1_5.pdf#page=87
725struct SharedInput<'a, T = Accessor<'a>> {
726    // Required
727    offset: u32,
728    // Required
729    semantic: InputSemantic,
730    // Required
731    source: Uri<'a, T>,
732    // Optional
733    set: u32,
734}
735
736impl<'a, T> SharedInput<'a, T> {
737    /*
738    The `<input>` element (shared)
739
740    Attributes:
741    - `offset` (uint_type, Required)
742    - `semantic` (xs:NMTOKEN, Required)
743    - `source` (uri_fragment_type, Required)
744    - `set` (uint_type, Optional)
745
746    Child elements: None
747    */
748    fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
749        debug_assert_eq!(node.tag_name().name(), "input");
750        let semantic = node.parse_required_attribute("semantic")?;
751        let source = node.parse_url("source")?;
752        let offset: u32 = node.parse_required_attribute("offset")?;
753        let set: u32 = node.parse_attribute("set")?.unwrap_or(0);
754        if let Some(child) = node.element_children().next() {
755            return Err(error::unexpected_child_elem(child));
756        }
757        Ok(Self {
758            offset,
759            semantic,
760            source,
761            set,
762        })
763    }
764
765    fn cast<U>(self) -> SharedInput<'a, U> {
766        SharedInput {
767            offset: self.offset,
768            semantic: self.semantic,
769            source: self.source.cast(),
770            set: self.set,
771        }
772    }
773}
774
775/// The `<input>` element (unshared).
776///
777/// See the specifications ([1.4], [1.5]) for details.
778///
779/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=76
780/// [1.5]: https://www.khronos.org/files/collada_spec_1_5.pdf#page=90
781struct UnsharedInput<'a> {
782    // Required
783    semantic: InputSemantic,
784    // Required
785    source: Uri<'a, Accessor<'a>>,
786}
787
788impl<'a> UnsharedInput<'a> {
789    /*
790    The `<input>` element (unshared)
791
792    Attributes:
793    - `semantic` (xs:NMTOKEN, Required)
794    - `source` (uri_fragment_type, Required)
795
796    Child elements: None
797    */
798    fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
799        debug_assert_eq!(node.tag_name().name(), "input");
800        let semantic = node.parse_required_attribute("semantic")?;
801        let source = node.parse_url("source")?;
802        if let Some(child) = node.element_children().next() {
803            return Err(error::unexpected_child_elem(child));
804        }
805        Ok(Self { semantic, source })
806    }
807}
808
809/// The value of the `semantic` attribute in the `<input>` element.
810///
811/// See the specifications ([1.4], [1.5]) for details.
812///
813/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=74
814/// [1.5]: https://www.khronos.org/files/collada_spec_1_5.pdf#page=88
815#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
816#[derive(Clone, Copy, PartialEq, Eq, Hash)]
817enum InputSemantic {
818    /// Geometric binormal (bitangent) vector.
819    BINORMAL,
820    /// Color coordinate vector. Color inputs are RGB (float3_type).
821    COLOR,
822    /// Continuity constraint at the control vertex (CV).
823    CONTINUITY,
824    /// Raster or MIP-level input.
825    IMAGE,
826    /// Sampler input.
827    INPUT,
828    /// Tangent vector for preceding control point.
829    IN_TANGENT,
830    /// Sampler interpolation type.
831    INTERPOLATION,
832    /// Inverse of local-to-world matrix.
833    INV_BIND_MATRIX,
834    /// Skin influence identifier.
835    JOINT,
836    /// Number of piece-wise linear approximation steps to use for the spline segment that follows this CV.
837    LINEAR_STEPS,
838    /// Morph targets for mesh morphing.
839    MORPH_TARGET,
840    /// Weights for mesh morphing
841    MORPH_WEIGHT,
842    /// Normal vector
843    NORMAL,
844    /// Sampler output.
845    OUTPUT,
846    /// Tangent vector for succeeding control point.
847    OUT_TANGENT,
848    /// Geometric coordinate vector.
849    POSITION,
850    /// Geometric tangent vector.
851    TANGENT,
852    /// Texture binormal (bitangent) vector.
853    TEXBINORMAL,
854    /// Texture coordinate vector.
855    TEXCOORD,
856    /// Texture tangent vector.
857    TEXTANGENT,
858    /// Generic parameter vector.
859    UV,
860    /// Mesh vertex.
861    VERTEX,
862    /// Skin influence weighting value.
863    WEIGHT,
864}
865
866impl FromStr for InputSemantic {
867    type Err = io::Error;
868
869    fn from_str(s: &str) -> Result<Self, Self::Err> {
870        Ok(match s {
871            "BINORMAL" => Self::BINORMAL,
872            "COLOR" => Self::COLOR,
873            "CONTINUITY" => Self::CONTINUITY,
874            "IMAGE" => Self::IMAGE,
875            "INPUT" => Self::INPUT,
876            "IN_TANGENT" => Self::IN_TANGENT,
877            "INTERPOLATION" => Self::INTERPOLATION,
878            "INV_BIND_MATRIX" => Self::INV_BIND_MATRIX,
879            "JOINT" => Self::JOINT,
880            "LINEAR_STEPS" => Self::LINEAR_STEPS,
881            "MORPH_TARGET" => Self::MORPH_TARGET,
882            "MORPH_WEIGHT" => Self::MORPH_WEIGHT,
883            "NORMAL" => Self::NORMAL,
884            "OUTPUT" => Self::OUTPUT,
885            "OUT_TANGENT" => Self::OUT_TANGENT,
886            "POSITION" => Self::POSITION,
887            "TANGENT" => Self::TANGENT,
888            "TEXBINORMAL" => Self::TEXBINORMAL,
889            "TEXCOORD" => Self::TEXCOORD,
890            "TEXTANGENT" => Self::TEXTANGENT,
891            "UV" => Self::UV,
892            "VERTEX" => Self::VERTEX,
893            "WEIGHT" => Self::WEIGHT,
894            _ => bail!("unknown input semantic {:?}", s),
895        })
896    }
897}