mesh_loader/collada/
effect.rs

1use super::*;
2
3/// The `<library_effects>` element.
4///
5/// See the [specification][1.4] for details.
6///
7/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=277
8#[derive(Default)]
9pub(super) struct LibraryEffects<'a> {
10    // /// The unique identifier of this element.
11    // pub(super) id: Option<&'a str>,
12    // /// The name of this element.
13    // pub(super) name: Option<&'a str>,
14    pub(super) effects: HashMap<&'a str, Effect<'a>>,
15}
16
17/// The `<effect>` element.
18///
19/// See the [specification][1.4] for details.
20///
21/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=265
22pub(super) struct Effect<'a> {
23    /// The unique identifier of this element.
24    pub(super) id: &'a str,
25    // /// The name of this element.
26    // pub(super) name: Option<&'a str>,
27    pub(super) profile: ProfileCommon<'a>,
28}
29
30/// The `<profile_COMMON>` element.
31///
32/// See the [specification][1.4] for details.
33///
34/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=301
35pub(super) struct ProfileCommon<'a> {
36    // /// The unique identifier of this element.
37    // pub(super) id: Option<&'a str>,
38    pub(super) surfaces: HashMap<&'a str, Surface<'a>>,
39    pub(super) samplers: HashMap<&'a str, Sampler<'a>>,
40
41    pub(super) technique: Technique<'a>,
42}
43
44pub(super) struct Technique<'a> {
45    #[allow(dead_code)] // TODO
46    pub(super) ty: ShadeType,
47
48    // Colors/Textures
49    pub(super) emission: ColorAndTexture<'a>,
50    pub(super) ambient: ColorAndTexture<'a>,
51    pub(super) diffuse: ColorAndTexture<'a>,
52    pub(super) specular: ColorAndTexture<'a>,
53    pub(super) reflective: ColorAndTexture<'a>,
54    pub(super) transparent: ColorAndTexture<'a>,
55    pub(super) has_transparency: bool,
56    pub(super) rgb_transparency: bool,
57    pub(super) invert_transparency: bool,
58
59    pub(super) shininess: f32,
60    pub(super) reflectivity: f32,
61    pub(super) transparency: f32,
62    pub(super) index_of_refraction: f32,
63
64    // GOOGLEEARTH/OKINO extensions
65    pub(super) double_sided: bool,
66
67    // FCOLLADA extensions
68    pub(super) bump: Texture<'a>,
69
70    // MAX3D extensions
71    pub(super) wireframe: bool,
72    pub(super) faceted: bool,
73}
74
75/// The `<surface>` element.
76///
77/// See the [specification][1.4] for details.
78///
79/// [1.4]: https://www.khronos.org/files/collada_spec_1_4.pdf#page=332
80pub(super) struct Surface<'a> {
81    pub(super) init_from: Uri<'a, Image<'a>>,
82}
83
84/// The `<sampler2D>` element.
85///
86/// See the [specification][1.4] for details.
87///
88/// [1.4] https://www.khronos.org/files/collada_spec_1_4.pdf#page=312
89pub(super) struct Sampler<'a> {
90    // An xs:NCName, which is the sid of a <surface>. A
91    // <sampler*> is a definition of how a shader will resolve a
92    // color out of a <surface>. <source> identifies the
93    // <surface> to read.
94    pub(super) source: &'a str,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98pub(super) enum ShadeType {
99    Constant,
100    Lambert,
101    Phong,
102    Blinn,
103}
104
105pub(super) struct ColorAndTexture<'a> {
106    pub(super) color: Color4,
107    pub(super) texture: Texture<'a>,
108}
109
110impl ColorAndTexture<'_> {
111    fn new(color: Color4) -> Self {
112        Self {
113            color,
114            texture: Texture {
115                texture: "",
116                // texcoord: "",
117            },
118        }
119    }
120}
121
122pub(super) struct Texture<'a> {
123    pub(super) texture: &'a str,
124    // pub(super) texcoord: &'a str,
125}
126
127// -----------------------------------------------------------------------------
128// Parsing
129
130pub(super) fn parse_library_effects<'a>(
131    cx: &mut Context<'a>,
132    node: xml::Node<'a, '_>,
133) -> io::Result<()> {
134    debug_assert_eq!(node.tag_name().name(), "library_effects");
135    // cx.library_effects.id = node.attribute("id");
136    // cx.library_effects.name = node.attribute("name");
137
138    for child in node.element_children() {
139        match child.tag_name().name() {
140            "effect" => {
141                let effect = parse_effect(cx, child)?;
142                cx.library_effects.effects.insert(effect.id, effect);
143            }
144            "asset" | "extra" => { /* skip */ }
145            _ => return Err(error::unexpected_child_elem(child)),
146        }
147    }
148
149    // The specification says <library_effects> has 1 or more <effect> elements,
150    // but some exporters write empty <library_effects/> tags.
151
152    Ok(())
153}
154
155/*
156The `<effect>` element
157
158Attributes:
159- `id` (xs:ID, Required)
160- `name` (xs:token, Optional)
161
162Child elements must appear in the following order if present:
163- `<asset>` (0 or 1)
164- `<annotate>` (0 or more)
165- `<newparam>` (0 or more)
166- profile (1 or more)
167    At least one profile must appear, but any number of any of
168    the following profiles can be included:
169    - <profile_BRIDGE>
170    - <profile_CG>
171    - <profile_GLES>
172    - <profile_GLES2>
173    - <profile_GLSL>
174    - <profile_COMMON>
175- `<extra>` (0 or more)
176*/
177fn parse_effect<'a>(cx: &mut Context<'a>, node: xml::Node<'a, '_>) -> io::Result<Effect<'a>> {
178    debug_assert_eq!(node.tag_name().name(), "effect");
179    let id = node.required_attribute("id")?;
180    let mut profile = None;
181
182    for child in node.element_children() {
183        if child.tag_name().name() == "profile_COMMON" {
184            profile = Some(parse_profile_common(cx, child)?);
185        }
186    }
187
188    let profile = match profile {
189        Some(profile) => profile,
190        None => return Err(error::exactly_one_elem(node, "profile_COMMON")),
191    };
192
193    Ok(Effect {
194        id,
195        // name: node.attribute("name"),
196        profile,
197    })
198}
199
200/*
201The `<profile_COMMON>` element
202
203Attributes:
204- `id` (xs:ID, Optional)
205
206Child elements must appear in the following order if present:
207- `<asset>` (0 or 1)
208- `<newparam>` (0 or more)
209- `<technique>` (1)
210- `<extra>` (0 or more)
211
212Child Elements for `<profile_COMMON>` / `<technique>`
213Child elements must appear in the following order if present:
214- `<asset>` (0 or 1)
215- shader_element (0 or more)
216    One of `<constant>` (FX), `<lambert>`, `<phong>`, or `<blinn>`.
217- `<extra>` (0 or more)
218*/
219fn parse_profile_common<'a>(
220    cx: &mut Context<'a>,
221    node: xml::Node<'a, '_>,
222) -> io::Result<ProfileCommon<'a>> {
223    debug_assert_eq!(node.tag_name().name(), "profile_COMMON");
224    let mut surfaces = HashMap::default();
225    let mut samplers = HashMap::default();
226    let mut technique = None;
227
228    for child in node.element_children() {
229        match child.tag_name().name() {
230            "newparam" => {
231                parse_newparam(cx, child, &mut surfaces, &mut samplers)?;
232            }
233            "technique" => {
234                for t in child.element_children() {
235                    let name = t.tag_name().name();
236                    match name {
237                        "constant" | "lambert" | "phong" | "blinn" => {
238                            technique = Some(parse_technique(t, name.parse().unwrap())?);
239                        }
240                        "asset" | "extra" => { /* skip */ }
241                        _ => {}
242                    }
243                }
244            }
245            "asset" | "extra" => { /* skip */ }
246            _ => return Err(error::unexpected_child_elem(child)),
247        }
248    }
249
250    let technique = match technique {
251        Some(technique) => technique,
252        // TODO: technique maybe flatten?
253        None => return Err(error::exactly_one_elem(node, "technique")),
254    };
255
256    Ok(ProfileCommon {
257        // id: node.attribute("id"),
258        surfaces,
259        samplers,
260        technique,
261    })
262}
263
264fn parse_newparam<'a>(
265    _cx: &mut Context<'a>,
266    node: xml::Node<'a, '_>,
267    surfaces: &mut HashMap<&'a str, Surface<'a>>,
268    samplers: &mut HashMap<&'a str, Sampler<'a>>,
269) -> io::Result<()> {
270    debug_assert_eq!(node.tag_name().name(), "newparam");
271    let sid = node.required_attribute("sid")?;
272
273    for child in node.element_children() {
274        match child.tag_name().name() {
275            "surface" => {
276                // image ID given inside <init_from> tags
277                if let Some(init) = child.child("init_from") {
278                    surfaces.insert(
279                        sid,
280                        Surface {
281                            init_from: Uri::from_id(init.trimmed_text()),
282                        },
283                    );
284                }
285            }
286            "sampler2D" => {
287                // surface ID is given inside <source> tags
288                if let Some(source) = child.child("source") {
289                    samplers.insert(
290                        sid,
291                        Sampler {
292                            source: source.trimmed_text(),
293                        },
294                    );
295                }
296            }
297            _ => return Err(error::unexpected_child_elem(child)),
298        }
299    }
300
301    Ok(())
302}
303
304impl FromStr for ShadeType {
305    type Err = io::Error;
306
307    fn from_str(s: &str) -> Result<Self, Self::Err> {
308        Ok(match s {
309            "constant" => Self::Constant,
310            "lambert" => Self::Lambert,
311            "phong" => Self::Phong,
312            "blinn" => Self::Blinn,
313            _ => bail!("unknown shade type {:?}", s),
314        })
315    }
316}
317
318/*
319Child elements must appear in the following order if present:
320- <emission> (0 or 1, fx_common_color_or_texture_type)
321- <ambient> (FX) (0 or 1, fx_common_color_or_texture_type)
322- <diffuse> (0 or 1, fx_common_color_or_texture_type)
323- <specular> (0 or 1, fx_common_color_or_texture_type)
324- <shininess> (0 or 1, fx_common_float_or_param_type)
325- <reflective> (0 or 1, fx_common_color_or_texture_type)
326- <reflectivity> (0 or 1, fx_common_float_or_param_type 0.0 ..= 1.0)
327- <transparent> (0 or 1, fx_common_color_or_texture_type)
328- <transparency> (0 or 1, fx_common_float_or_param_type 0.0 ..= 1.0)
329- <index_of_refraction> (0 or 1, fx_common_float_or_param_type)
330*/
331fn parse_technique<'a>(node: xml::Node<'a, '_>, ty: ShadeType) -> io::Result<Technique<'a>> {
332    debug_assert_eq!(node.tag_name().name().parse::<ShadeType>().unwrap(), ty);
333    let mut effect = Technique::new(ty);
334
335    for child in node.element_children() {
336        let name = child.tag_name().name();
337        match name {
338            // fx_common_color_or_texture_type
339            "emission" => {
340                parse_effect_color(
341                    child,
342                    &mut effect.emission.color,
343                    &mut effect.emission.texture,
344                )?;
345            }
346            "ambient" => {
347                parse_effect_color(
348                    child,
349                    &mut effect.ambient.color,
350                    &mut effect.ambient.texture,
351                )?;
352            }
353            "diffuse" => {
354                parse_effect_color(
355                    child,
356                    &mut effect.diffuse.color,
357                    &mut effect.diffuse.texture,
358                )?;
359            }
360            "specular" => {
361                parse_effect_color(
362                    child,
363                    &mut effect.specular.color,
364                    &mut effect.specular.texture,
365                )?;
366            }
367            "reflective" => {
368                parse_effect_color(
369                    child,
370                    &mut effect.reflective.color,
371                    &mut effect.reflective.texture,
372                )?;
373            }
374            "transparent" => {
375                effect.has_transparency = true;
376                if let Some(opaque) = child.parse_attribute::<Opaque>("opaque")? {
377                    effect.rgb_transparency = opaque.rgb_transparency();
378                    effect.invert_transparency = opaque.invert_transparency();
379                }
380                parse_effect_color(
381                    child,
382                    &mut effect.transparent.color,
383                    &mut effect.transparent.texture,
384                )?;
385            }
386
387            // fx_common_float_or_param_type
388            "shininess" => {
389                if let Some(n) = parse_effect_float(child)? {
390                    effect.shininess = n;
391                }
392            }
393            "reflectivity" => {
394                if let Some(n) = parse_effect_float(child)? {
395                    effect.reflectivity = n;
396                }
397            }
398            "transparency" => {
399                if let Some(n) = parse_effect_float(child)? {
400                    effect.transparency = n;
401                }
402            }
403            "index_of_refraction" => {
404                if let Some(n) = parse_effect_float(child)? {
405                    effect.index_of_refraction = n;
406                }
407            }
408
409            // GOOGLEEARTH/OKINO extensions
410            "double_sided" => {
411                effect.double_sided = node.parse_required_attribute(name)?;
412            }
413
414            // FCOLLADA extensions
415            "bump" => {
416                let mut dummy = [0.; 4];
417                parse_effect_color(child, &mut dummy, &mut effect.bump)?;
418            }
419
420            // MAX3D extensions
421            "wireframe" => {
422                effect.wireframe = node.parse_required_attribute(name)?;
423            }
424            "faceted" => {
425                effect.faceted = node.parse_required_attribute(name)?;
426            }
427
428            _ => {}
429        }
430    }
431
432    Ok(effect)
433}
434
435impl Technique<'_> {
436    fn new(ty: ShadeType) -> Self {
437        Self {
438            ty,
439            emission: ColorAndTexture::new([0.0, 0.0, 0.0, 1.0]),
440            ambient: ColorAndTexture::new([0.1, 0.1, 0.1, 1.0]),
441            diffuse: ColorAndTexture::new([0.6, 0.6, 0.6, 1.0]),
442            specular: ColorAndTexture::new([0.4, 0.4, 0.4, 1.0]),
443            // refs: https://www.khronos.org/files/collada_spec_1_5.pdf#page=250
444            transparent: ColorAndTexture::new([1.0, 1.0, 1.0, 1.0]),
445            reflective: ColorAndTexture::new([0.0, 0.0, 0.0, 1.0]),
446            shininess: 10.0,
447            index_of_refraction: 1.0,
448            reflectivity: 0.0,
449            // refs: https://www.khronos.org/files/collada_spec_1_5.pdf#page=250
450            transparency: 1.0,
451            has_transparency: false,
452            rgb_transparency: false,
453            invert_transparency: false,
454            double_sided: false,
455            bump: Texture {
456                texture: "",
457                // texcoord: "",
458            },
459            wireframe: false,
460            faceted: false,
461        }
462    }
463}
464
465// Attributes:
466// Only <transparent> has an attribute
467// - `opaque` (Enumeration, Optional)
468//
469// Child Elements:
470// Note: Exactly one of the child elements `<color>`, `<param>`, or
471// `<texture>` must appear. They are mutually exclusive.
472// - `<color>`
473// - `<param>` (reference)
474// - `<texture>`
475//
476// See also fx_common_color_or_texture_type in specification.
477fn parse_effect_color<'a>(
478    node: xml::Node<'a, '_>,
479    color: &mut Color4,
480    texture: &mut Texture<'a>,
481) -> io::Result<()> {
482    for child in node.element_children() {
483        match child.tag_name().name() {
484            "color" => {
485                let content = xml::comma_to_period(child.trimmed_text());
486                let mut iter = xml::parse_float_array_exact(&content, 4);
487                // TODO: include in parse_float_array_exact?
488                let map_err = |e| {
489                    format_err!(
490                        "{e} in <{}> element ({})",
491                        child.tag_name().name(),
492                        child.text_location(),
493                    )
494                };
495                let r = iter.next().unwrap().map_err(map_err)?;
496                let g = iter.next().unwrap().map_err(map_err)?;
497                let b = iter.next().unwrap().map_err(map_err)?;
498                let a = iter.next().unwrap().map_err(map_err)?;
499                *color = [r, g, b, a];
500            }
501            "texture" => {
502                let _texcoord = child.required_attribute("texcoord")?;
503                *texture = Texture {
504                    texture: child.required_attribute("texture")?,
505                    // texcoord,
506                };
507            }
508            "param" => {
509                // warn!(
510                //     "<{}> child element in <{}> element is unsupported ({})",
511                //     child.tag_name().name(),
512                //     child.parent_element().unwrap().tag_name().name(),
513                //     child.node_location()
514                // );
515            }
516            _ => {}
517        }
518    }
519    Ok(())
520}
521
522fn parse_effect_float(node: xml::Node<'_, '_>) -> io::Result<Option<f32>> {
523    let mut float = None;
524
525    for child in node.element_children() {
526        match child.tag_name().name() {
527            "float" => {
528                let content = xml::comma_to_period(child.trimmed_text());
529                float = Some(
530                    float::parse(content.as_bytes())
531                        .ok_or_else(|| format_err!("error while parsing a float"))?,
532                );
533            }
534            "param" => {
535                // warn!(
536                //     "<{}> child element in <{}> element is unsupported ({})",
537                //     child.tag_name().name(),
538                //     child.parent_element().unwrap().tag_name().name(),
539                //     child.node_location()
540                // );
541            }
542            _ => return Err(error::unexpected_child_elem(child)),
543        }
544    }
545
546    Ok(float)
547}
548
549#[allow(non_camel_case_types)]
550#[derive(Clone, Copy, PartialEq, Eq)]
551enum Opaque {
552    A_ZERO,
553    A_ONE,
554    RGB_ZERO,
555    RGB_ONE,
556}
557
558impl Opaque {
559    fn rgb_transparency(self) -> bool {
560        matches!(self, Self::RGB_ZERO | Self::RGB_ONE)
561    }
562    fn invert_transparency(self) -> bool {
563        matches!(self, Self::RGB_ZERO | Self::A_ZERO)
564    }
565}
566
567impl FromStr for Opaque {
568    type Err = io::Error;
569
570    fn from_str(s: &str) -> Result<Self, Self::Err> {
571        Ok(match s {
572            "A_ZERO" => Self::A_ZERO,
573            "A_ONE" => Self::A_ONE,
574            "RGB_ZERO" => Self::RGB_ZERO,
575            "RGB_ONE" => Self::RGB_ONE,
576            _ => bail!("unknown opaque type {:?}", s),
577        })
578    }
579}