1use 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 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}