Skip to main content

fbx_dom/objects/
light.rs

1//! FBX `NodeAttribute` / `Light` — Assimp [`Light`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXDocument.h).
2
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use crate::{OwnedObject, Property};
7
8use super::{FbxObjectTag, FbxTypeMismatch, fbx_object_tag};
9
10const PROP_COLOR: &str = "Color";
11const PROP_LIGHT_TYPE: &str = "LightType";
12const PROP_CAST_LIGHT_ON_OBJECT: &str = "CastLightOnObject";
13const PROP_DRAW_VOLUMETRIC_LIGHT: &str = "DrawVolumetricLight";
14const PROP_DRAW_GROUND_PROJECTION: &str = "DrawGroundProjection";
15const PROP_DRAW_FRONT_FACING_VOLUMETRIC_LIGHT: &str = "DrawFrontFacingVolumetricLight";
16const PROP_INTENSITY: &str = "Intensity";
17const PROP_INNER_ANGLE: &str = "InnerAngle";
18const PROP_OUTER_ANGLE: &str = "OuterAngle";
19const PROP_FOG: &str = "Fog";
20const PROP_DECAY_TYPE: &str = "DecayType";
21const PROP_DECAY_START: &str = "DecayStart";
22const PROP_FILE_NAME: &str = "FileName";
23const PROP_ENABLE_NEAR_ATTENUATION: &str = "EnableNearAttenuation";
24const PROP_NEAR_ATTENUATION_START: &str = "NearAttenuationStart";
25const PROP_NEAR_ATTENUATION_END: &str = "NearAttenuationEnd";
26const PROP_ENABLE_FAR_ATTENUATION: &str = "EnableFarAttenuation";
27const PROP_FAR_ATTENUATION_START: &str = "FarAttenuationStart";
28const PROP_FAR_ATTENUATION_END: &str = "FarAttenuationEnd";
29const PROP_CAST_SHADOWS: &str = "CastShadows";
30const PROP_SHADOW_COLOR: &str = "ShadowColor";
31const PROP_AREA_LIGHT_SHAPE: &str = "AreaLightShape";
32const PROP_LEFT_BARN_DOOR: &str = "LeftBarnDoor";
33const PROP_RIGHT_BARN_DOOR: &str = "RightBarnDoor";
34const PROP_TOP_BARN_DOOR: &str = "TopBarnDoor";
35const PROP_BOTTOM_BARN_DOOR: &str = "BottomBarnDoor";
36const PROP_ENABLE_BARN_DOOR: &str = "EnableBarnDoor";
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum LightType {
40    Point,
41    Directional,
42    Spot,
43    Area,
44    Volume,
45}
46
47impl LightType {
48    fn from_i32(value: i32) -> Self {
49        match value {
50            1 => Self::Directional,
51            2 => Self::Spot,
52            3 => Self::Area,
53            4 => Self::Volume,
54            _ => Self::Point,
55        }
56    }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum LightDecay {
61    None,
62    Linear,
63    Quadratic,
64    Cubic,
65}
66
67impl LightDecay {
68    fn from_i32(value: i32) -> Self {
69        match value {
70            0 => Self::None,
71            1 => Self::Linear,
72            2 => Self::Quadratic,
73            3 => Self::Cubic,
74            _ => Self::Quadratic,
75        }
76    }
77}
78
79#[derive(Debug, PartialEq)]
80pub struct Light(pub OwnedObject);
81
82impl Light {
83    pub fn inner(&self) -> &OwnedObject {
84        &self.0
85    }
86
87    pub fn into_inner(self) -> OwnedObject {
88        self.0
89    }
90
91    /// Temporary bridge to Assimp-style light properties until typed accessors are added.
92    pub fn properties(&self) -> &HashMap<String, Property> {
93        &self.0.properties
94    }
95
96    pub fn property(&self, name: &str) -> Option<&Property> {
97        self.0.properties.get(name)
98    }
99
100    pub fn color(&self) -> [f32; 3] {
101        match self.property(PROP_COLOR) {
102            Some(Property::Vec3(v)) => *v,
103            _ => [1.0, 1.0, 1.0],
104        }
105    }
106
107    pub fn light_type(&self) -> LightType {
108        match self.property(PROP_LIGHT_TYPE) {
109            Some(Property::Int(v)) => LightType::from_i32(*v),
110            _ => LightType::Point,
111        }
112    }
113
114    pub fn cast_light_on_object(&self) -> bool {
115        match self.property(PROP_CAST_LIGHT_ON_OBJECT) {
116            Some(Property::Bool(v)) => *v,
117            _ => false,
118        }
119    }
120
121    pub fn draw_volumetric_light(&self) -> bool {
122        match self.property(PROP_DRAW_VOLUMETRIC_LIGHT) {
123            Some(Property::Bool(v)) => *v,
124            _ => true,
125        }
126    }
127
128    pub fn draw_ground_projection(&self) -> bool {
129        match self.property(PROP_DRAW_GROUND_PROJECTION) {
130            Some(Property::Bool(v)) => *v,
131            _ => true,
132        }
133    }
134
135    pub fn draw_front_facing_volumetric_light(&self) -> bool {
136        match self.property(PROP_DRAW_FRONT_FACING_VOLUMETRIC_LIGHT) {
137            Some(Property::Bool(v)) => *v,
138            _ => false,
139        }
140    }
141
142    pub fn intensity(&self) -> f32 {
143        match self.property(PROP_INTENSITY) {
144            Some(Property::Float(v)) => *v,
145            _ => 100.0,
146        }
147    }
148
149    pub fn inner_angle(&self) -> f32 {
150        match self.property(PROP_INNER_ANGLE) {
151            Some(Property::Float(v)) => *v,
152            _ => 0.0,
153        }
154    }
155
156    pub fn outer_angle(&self) -> f32 {
157        match self.property(PROP_OUTER_ANGLE) {
158            Some(Property::Float(v)) => *v,
159            _ => 45.0,
160        }
161    }
162
163    pub fn fog(&self) -> i32 {
164        match self.property(PROP_FOG) {
165            Some(Property::Int(v)) => *v,
166            _ => 50,
167        }
168    }
169
170    pub fn decay_type(&self) -> LightDecay {
171        match self.property(PROP_DECAY_TYPE) {
172            Some(Property::Int(v)) => LightDecay::from_i32(*v),
173            _ => LightDecay::Quadratic,
174        }
175    }
176
177    pub fn decay_start(&self) -> f32 {
178        match self.property(PROP_DECAY_START) {
179            Some(Property::Float(v)) => *v,
180            _ => 1.0,
181        }
182    }
183
184    pub fn file_name(&self) -> &str {
185        match self.property(PROP_FILE_NAME) {
186            Some(Property::String(v)) => v.as_str(),
187            _ => "",
188        }
189    }
190
191    pub fn enable_near_attenuation(&self) -> bool {
192        match self.property(PROP_ENABLE_NEAR_ATTENUATION) {
193            Some(Property::Bool(v)) => *v,
194            _ => false,
195        }
196    }
197
198    pub fn near_attenuation_start(&self) -> f32 {
199        match self.property(PROP_NEAR_ATTENUATION_START) {
200            Some(Property::Float(v)) => *v,
201            _ => 0.0,
202        }
203    }
204
205    pub fn near_attenuation_end(&self) -> f32 {
206        match self.property(PROP_NEAR_ATTENUATION_END) {
207            Some(Property::Float(v)) => *v,
208            _ => 0.0,
209        }
210    }
211
212    pub fn enable_far_attenuation(&self) -> bool {
213        match self.property(PROP_ENABLE_FAR_ATTENUATION) {
214            Some(Property::Bool(v)) => *v,
215            _ => false,
216        }
217    }
218
219    pub fn far_attenuation_start(&self) -> f32 {
220        match self.property(PROP_FAR_ATTENUATION_START) {
221            Some(Property::Float(v)) => *v,
222            _ => 0.0,
223        }
224    }
225
226    pub fn far_attenuation_end(&self) -> f32 {
227        match self.property(PROP_FAR_ATTENUATION_END) {
228            Some(Property::Float(v)) => *v,
229            _ => 0.0,
230        }
231    }
232
233    pub fn cast_shadows(&self) -> bool {
234        match self.property(PROP_CAST_SHADOWS) {
235            Some(Property::Bool(v)) => *v,
236            _ => true,
237        }
238    }
239
240    pub fn shadow_color(&self) -> [f32; 3] {
241        match self.property(PROP_SHADOW_COLOR) {
242            Some(Property::Vec3(v)) => *v,
243            _ => [0.0, 0.0, 0.0],
244        }
245    }
246
247    pub fn area_light_shape(&self) -> i32 {
248        match self.property(PROP_AREA_LIGHT_SHAPE) {
249            Some(Property::Int(v)) => *v,
250            _ => 0,
251        }
252    }
253
254    pub fn left_barn_door(&self) -> f32 {
255        match self.property(PROP_LEFT_BARN_DOOR) {
256            Some(Property::Float(v)) => *v,
257            _ => 20.0,
258        }
259    }
260
261    pub fn right_barn_door(&self) -> f32 {
262        match self.property(PROP_RIGHT_BARN_DOOR) {
263            Some(Property::Float(v)) => *v,
264            _ => 20.0,
265        }
266    }
267
268    pub fn top_barn_door(&self) -> f32 {
269        match self.property(PROP_TOP_BARN_DOOR) {
270            Some(Property::Float(v)) => *v,
271            _ => 20.0,
272        }
273    }
274
275    pub fn bottom_barn_door(&self) -> f32 {
276        match self.property(PROP_BOTTOM_BARN_DOOR) {
277            Some(Property::Float(v)) => *v,
278            _ => 20.0,
279        }
280    }
281
282    pub fn enable_barn_door(&self) -> bool {
283        match self.property(PROP_ENABLE_BARN_DOOR) {
284            Some(Property::Bool(v)) => *v,
285            _ => true,
286        }
287    }
288}
289
290impl TryFrom<OwnedObject> for Light {
291    type Error = FbxTypeMismatch;
292
293    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
294        match fbx_object_tag(&o) {
295            FbxObjectTag::Light => Ok(Light(o)),
296            _ => Err(FbxTypeMismatch::wrong_object_kind(o, "Light".to_string())),
297        }
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use std::collections::HashMap;
304    use std::convert::TryFrom;
305
306    use crate::objects::{NODE_ATTRIBUTE_LIGHT_CLASS_NAME, NODE_ATTRIBUTE_TYPE_NAME};
307    use crate::{OwnedObject, Property};
308
309    use super::{Light, LightDecay, LightType};
310
311    #[test]
312    fn property_accessors_return_owned_object_properties() {
313        let mut properties = HashMap::new();
314        properties.insert("Intensity".to_string(), Property::Float(100.0));
315        let o = OwnedObject {
316            object_index: 99,
317            name: "Light".into(),
318            type_name: NODE_ATTRIBUTE_TYPE_NAME.into(),
319            class_name: NODE_ATTRIBUTE_LIGHT_CLASS_NAME.into(),
320            properties,
321            attributes: HashMap::new(),
322            connected_object_ids: vec![],
323            object_property_targets: vec![],
324            pp_property_targets: HashMap::new(),
325        };
326        let light = Light::try_from(o).unwrap();
327        assert_eq!(light.property("Intensity"), Some(&Property::Float(100.0)));
328        assert_eq!(light.properties().len(), 1);
329    }
330
331    #[test]
332    fn typed_accessors_return_defaults() {
333        let o = OwnedObject {
334            object_index: 99,
335            name: "Light".into(),
336            type_name: NODE_ATTRIBUTE_TYPE_NAME.into(),
337            class_name: NODE_ATTRIBUTE_LIGHT_CLASS_NAME.into(),
338            properties: HashMap::new(),
339            attributes: HashMap::new(),
340            connected_object_ids: vec![],
341            object_property_targets: vec![],
342            pp_property_targets: HashMap::new(),
343        };
344        let light = Light::try_from(o).unwrap();
345        assert_eq!(light.color(), [1.0, 1.0, 1.0]);
346        assert_eq!(light.light_type(), LightType::Point);
347        assert_eq!(light.decay_type(), LightDecay::Quadratic);
348        assert_eq!(light.intensity(), 100.0);
349        assert_eq!(light.outer_angle(), 45.0);
350        assert_eq!(light.cast_shadows(), true);
351        assert_eq!(light.file_name(), "");
352    }
353
354    #[test]
355    fn typed_accessors_return_property_values() {
356        let mut properties = HashMap::new();
357        properties.insert("Color".to_string(), Property::Vec3([0.2, 0.3, 0.4]));
358        properties.insert("LightType".to_string(), Property::Int(2));
359        properties.insert("CastLightOnObject".to_string(), Property::Bool(true));
360        properties.insert("DrawVolumetricLight".to_string(), Property::Bool(false));
361        properties.insert("DrawGroundProjection".to_string(), Property::Bool(false));
362        properties.insert(
363            "DrawFrontFacingVolumetricLight".to_string(),
364            Property::Bool(true),
365        );
366        properties.insert("Intensity".to_string(), Property::Float(250.0));
367        properties.insert("InnerAngle".to_string(), Property::Float(5.0));
368        properties.insert("OuterAngle".to_string(), Property::Float(30.0));
369        properties.insert("Fog".to_string(), Property::Int(10));
370        properties.insert("DecayType".to_string(), Property::Int(1));
371        properties.insert("DecayStart".to_string(), Property::Float(2.0));
372        properties.insert("FileName".to_string(), Property::String("gobo.png".into()));
373        properties.insert("EnableNearAttenuation".to_string(), Property::Bool(true));
374        properties.insert("NearAttenuationStart".to_string(), Property::Float(1.0));
375        properties.insert("NearAttenuationEnd".to_string(), Property::Float(2.0));
376        properties.insert("EnableFarAttenuation".to_string(), Property::Bool(true));
377        properties.insert("FarAttenuationStart".to_string(), Property::Float(20.0));
378        properties.insert("FarAttenuationEnd".to_string(), Property::Float(30.0));
379        properties.insert("CastShadows".to_string(), Property::Bool(false));
380        properties.insert("ShadowColor".to_string(), Property::Vec3([0.1, 0.1, 0.1]));
381        properties.insert("AreaLightShape".to_string(), Property::Int(3));
382        properties.insert("LeftBarnDoor".to_string(), Property::Float(11.0));
383        properties.insert("RightBarnDoor".to_string(), Property::Float(12.0));
384        properties.insert("TopBarnDoor".to_string(), Property::Float(13.0));
385        properties.insert("BottomBarnDoor".to_string(), Property::Float(14.0));
386        properties.insert("EnableBarnDoor".to_string(), Property::Bool(false));
387        let o = OwnedObject {
388            object_index: 99,
389            name: "Light".into(),
390            type_name: NODE_ATTRIBUTE_TYPE_NAME.into(),
391            class_name: NODE_ATTRIBUTE_LIGHT_CLASS_NAME.into(),
392            properties,
393            attributes: HashMap::new(),
394            connected_object_ids: vec![],
395            object_property_targets: vec![],
396            pp_property_targets: HashMap::new(),
397        };
398        let light = Light::try_from(o).unwrap();
399        assert_eq!(light.color(), [0.2, 0.3, 0.4]);
400        assert_eq!(light.light_type(), LightType::Spot);
401        assert_eq!(light.cast_light_on_object(), true);
402        assert_eq!(light.draw_volumetric_light(), false);
403        assert_eq!(light.draw_ground_projection(), false);
404        assert_eq!(light.draw_front_facing_volumetric_light(), true);
405        assert_eq!(light.intensity(), 250.0);
406        assert_eq!(light.inner_angle(), 5.0);
407        assert_eq!(light.outer_angle(), 30.0);
408        assert_eq!(light.fog(), 10);
409        assert_eq!(light.decay_type(), LightDecay::Linear);
410        assert_eq!(light.decay_start(), 2.0);
411        assert_eq!(light.file_name(), "gobo.png");
412        assert_eq!(light.enable_near_attenuation(), true);
413        assert_eq!(light.near_attenuation_start(), 1.0);
414        assert_eq!(light.near_attenuation_end(), 2.0);
415        assert_eq!(light.enable_far_attenuation(), true);
416        assert_eq!(light.far_attenuation_start(), 20.0);
417        assert_eq!(light.far_attenuation_end(), 30.0);
418        assert_eq!(light.cast_shadows(), false);
419        assert_eq!(light.shadow_color(), [0.1, 0.1, 0.1]);
420        assert_eq!(light.area_light_shape(), 3);
421        assert_eq!(light.left_barn_door(), 11.0);
422        assert_eq!(light.right_barn_door(), 12.0);
423        assert_eq!(light.top_barn_door(), 13.0);
424        assert_eq!(light.bottom_barn_door(), 14.0);
425        assert_eq!(light.enable_barn_door(), false);
426    }
427}