1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
use crate::*;

/// Provides a self-contained description of a COLLADA effect.
#[derive(Clone, Debug)]
pub struct Effect {
    /// Global identifier for this object.
    pub id: String,
    /// The text string name of this element.
    pub name: Option<String>,
    /// Asset management information about this element.
    pub asset: Option<Box<Asset>>,
    /// A list of strongly typed annotation remarks.
    pub annotate: Vec<Annotate>,
    /// Declares a standard COLLADA image resource.
    pub image: Vec<Image>,
    /// Creates a new parameter from a constrained set of
    /// types recognizable by all platforms, see [`ParamType`].
    pub new_param: Vec<NewParam>,
    /// At least one profile must appear.
    pub profile: Vec<Profile>,
    /// Provides arbitrary additional information about this element.
    pub extra: Vec<Extra>,
}

impl XNode for Effect {
    const NAME: &'static str = "effect";
    fn parse(element: &Element) -> Result<Self> {
        debug_assert_eq!(element.name(), Self::NAME);
        let mut it = element.children().peekable();
        let res = Effect {
            id: element.attr("id").ok_or("expected id attr")?.into(),
            name: element.attr("name").map(Into::into),
            asset: Asset::parse_opt_box(&mut it)?,
            annotate: Annotate::parse_list(&mut it)?,
            image: Image::parse_list(&mut it)?,
            new_param: NewParam::parse_list(&mut it)?,
            profile: parse_list_many(&mut it, Profile::parse)?,
            extra: Extra::parse_many(it)?,
        };
        if res.profile.is_empty() {
            return Err("expected at least one profile".into());
        }
        Ok(res)
    }
}

impl XNodeWrite for Effect {
    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
        let mut e = Self::elem();
        e.attr("id", &self.id);
        e.opt_attr("name", &self.name);
        let e = e.start(w)?;
        self.asset.write_to(w)?;
        self.annotate.write_to(w)?;
        self.image.write_to(w)?;
        self.new_param.write_to(w)?;
        self.profile.write_to(w)?;
        self.extra.write_to(w)?;
        e.end(w)
    }
}

impl Effect {
    /// Construct a new `Effect` from common profile data.
    pub fn new(id: impl Into<String>, technique: TechniqueFx<CommonData>) -> Self {
        Self {
            id: id.into(),
            name: None,
            asset: None,
            annotate: vec![],
            image: vec![],
            new_param: vec![],
            profile: vec![ProfileCommon::new(technique).into()],
            extra: vec![],
        }
    }

    /// Construct a simple `Effect` with one shader.
    pub fn shader(id: impl Into<String>, shader: impl Into<Shader>) -> Self {
        Self::new(id, TechniqueFx::new("common", CommonData::shader(shader)))
    }

    /// Get the first [`ProfileCommon`] in this effect.
    pub fn get_common_profile(&self) -> Option<&ProfileCommon> {
        self.profile.iter().find_map(Profile::as_common)
    }

    /// Get a parameter of the effect by name.
    pub fn get_param(&self, sid: &str) -> Option<&NewParam> {
        self.new_param.iter().rev().find(|p| p.sid == sid)
    }
}

/// Extra data associated to [`Instance`]<[`Effect`]>.
#[derive(Clone, Debug, Default)]
pub struct InstanceEffectData {
    /// [`TechniqueHint`]s indicate the desired or last-used technique
    /// inside an effect profile.
    pub technique_hint: Vec<TechniqueHint>,
    /// [`EffectSetParam`]s assign values to specific effect and profile
    /// parameters that will be unique to the instance.
    pub set_param: Vec<EffectSetParam>,
}

impl XNodeWrite for InstanceEffectData {
    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
        self.technique_hint.write_to(w)?;
        self.set_param.write_to(w)
    }
}

impl Instantiate for Effect {
    const INSTANCE: &'static str = "instance_effect";
    type Data = InstanceEffectData;
    fn parse_data(_: &Element, it: &mut ElementIter<'_>) -> Result<Self::Data> {
        Ok(InstanceEffectData {
            technique_hint: TechniqueHint::parse_list(it)?,
            set_param: EffectSetParam::parse_list(it)?,
        })
    }
    fn is_empty(data: &Self::Data) -> bool {
        data.technique_hint.is_empty() && data.set_param.is_empty()
    }
}

/// Binds geometry vertex inputs to effect vertex inputs upon instantiation.
#[derive(Clone, Debug)]
pub struct BindVertexInput {
    /// Which effect parameter to bind.
    pub semantic: String,
    /// Which input semantic to bind.
    pub input_semantic: String,
    /// Which input set to bind.
    pub input_set: Option<u32>,
}

impl BindVertexInput {
    /// Construct a `BindVertexInput` from the input data.
    pub fn new(
        semantic: impl Into<String>,
        input_semantic: impl Into<String>,
        input_set: Option<u32>,
    ) -> Self {
        Self {
            semantic: semantic.into(),
            input_semantic: input_semantic.into(),
            input_set,
        }
    }
}

impl XNode for BindVertexInput {
    const NAME: &'static str = "bind_vertex_input";
    fn parse(element: &Element) -> Result<Self> {
        debug_assert_eq!(element.name(), Self::NAME);
        let semantic = element.attr("semantic");
        let input_semantic = element.attr("input_semantic");
        Ok(BindVertexInput {
            semantic: semantic.ok_or("missing semantic attribute")?.into(),
            input_semantic: input_semantic.ok_or("missing input semantic")?.into(),
            input_set: parse_attr(element.attr("input_set"))?,
        })
    }
}

impl XNodeWrite for BindVertexInput {
    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
        let mut e = Self::elem();
        e.attr("semantic", &self.semantic);
        e.attr("input_semantic", &self.input_semantic);
        e.opt_print_attr("input_set", &self.input_set);
        e.end(w)
    }
}

/// A trait for the types that are legal to go in a [`TechniqueFx<T>`].
pub trait ProfileData: XNodeWrite + Sized {
    /// Parse the embedded data from a subsequence of children in the `<technique>` node.
    fn parse(it: &mut ElementIter<'_>) -> Result<Self>;
}

/// Holds a description of the textures, samplers, shaders, parameters,
/// and passes necessary for rendering this effect using one method.
///
/// It is parameterized on additional data determined by the parent of this element.
#[derive(Clone, Debug)]
pub struct TechniqueFx<T> {
    /// A text string containing the unique identifier of the element.
    pub id: Option<String>,
    /// A text string value containing the subidentifier of this element.
    /// This value must be unique within the scope of the parent element.
    pub sid: String,
    /// Asset management information about this element.
    pub asset: Option<Box<Asset>>,
    /// The profile-specific child data.
    pub data: T,
    /// Provides arbitrary additional information about this element.
    pub extra: Vec<Extra>,
}

impl<T> TechniqueFx<T> {
    /// Construct a new `TechniqueFx` given the profile-specific data.
    pub fn new(sid: impl Into<String>, data: T) -> Self {
        Self {
            id: None,
            sid: sid.into(),
            asset: None,
            data,
            extra: vec![],
        }
    }

    /// Construct a new `TechniqueFx` with default data.
    pub fn default(sid: impl Into<String>) -> Self
    where
        T: Default,
    {
        Self::new(sid, T::default())
    }
}

impl<T: ProfileData> XNode for TechniqueFx<T> {
    const NAME: &'static str = "technique";
    fn parse(element: &Element) -> Result<Self> {
        debug_assert_eq!(element.name(), Self::NAME);
        let mut it = element.children().peekable();
        Ok(TechniqueFx {
            id: element.attr("id").map(Into::into),
            sid: element.attr("sid").ok_or("expecting sid attr")?.into(),
            asset: Asset::parse_opt_box(&mut it)?,
            data: T::parse(&mut it)?,
            extra: Extra::parse_many(it)?,
        })
    }
}

impl<T: ProfileData> XNodeWrite for TechniqueFx<T> {
    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
        let mut e = Self::elem();
        e.opt_attr("id", &self.id);
        e.attr("sid", &self.sid);
        let e = e.start(w)?;
        self.asset.write_to(w)?;
        self.data.write_to(w)?;
        self.extra.write_to(w)?;
        e.end(w)
    }
}

/// Adds a hint for a platform of which technique to use in this effect.
#[derive(Clone, Debug)]
pub struct TechniqueHint {
    /// Defines a string that specifies for which platform this hint is intended.
    pub platform: Option<String>,
    /// A reference to the name of the platform.
    pub ref_: String,
    /// A string that specifies for which API profile this hint is intended.
    /// It is the name of the profile within the effect that contains the technique.
    /// Profiles are constructed by appending this attribute’s value to "Profile".
    /// For example, to select [`ProfileCG`], specify `profile="CG"`.
    pub profile: Option<String>,
}

impl TechniqueHint {
    /// Construct a new `TechniqueHint`.
    pub fn new(
        platform: impl Into<String>,
        ref_: impl Into<String>,
        profile: impl Into<String>,
    ) -> Self {
        Self {
            platform: Some(platform.into()),
            ref_: ref_.into(),
            profile: Some(profile.into()),
        }
    }
}

impl XNode for TechniqueHint {
    const NAME: &'static str = "technique_hint";
    fn parse(element: &Element) -> Result<Self> {
        debug_assert_eq!(element.name(), Self::NAME);
        Ok(TechniqueHint {
            platform: element.attr("platform").map(Into::into),
            ref_: element.attr("ref").ok_or("expected 'ref' attr")?.into(),
            profile: element.attr("profile").map(Into::into),
        })
    }
}

impl XNodeWrite for TechniqueHint {
    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
        let mut e = Self::elem();
        e.opt_attr("platform", &self.platform);
        e.attr("ref", &self.ref_);
        e.opt_attr("profile", &self.profile);
        e.end(w)
    }
}