1use std::collections::HashMap;
7use std::convert::TryFrom;
8
9use crate::Property;
10use crate::{ObjectPropertyConnection, OwnedDocument, OwnedObject};
11
12use super::{
13 AnimationCurve, FbxObjectTag, FbxTypeMismatch, Model, NodeAttributeRef, fbx_object_tag,
14};
15
16#[derive(Debug, PartialEq)]
17pub struct AnimationCurveNode(pub OwnedObject);
18
19impl AnimationCurveNode {
20 pub fn inner(&self) -> &OwnedObject {
21 &self.0
22 }
23
24 pub fn into_inner(self) -> OwnedObject {
25 self.0
26 }
27
28 pub fn properties(&self) -> &HashMap<String, Property> {
29 &self.0.properties
30 }
31
32 pub fn property(&self, name: &str) -> Option<&Property> {
33 self.0.properties.get(name)
34 }
35
36 fn get_target_property_connection(&self) -> Option<&ObjectPropertyConnection> {
38 self.inner()
39 .object_property_targets
40 .iter()
41 .find(|c| !c.property.is_empty())
42 }
43
44 pub fn get_target_model<'a>(&'a self, document: &'a OwnedDocument) -> Option<&'a Model> {
45 let target = self.get_target_property_connection()?;
46 document
47 .models
48 .iter()
49 .find(|m| m.inner().object_index == target.dest)
50 }
51
52 pub fn get_target_node_attribute<'a>(
53 &'a self,
54 document: &'a OwnedDocument,
55 ) -> Option<NodeAttributeRef<'a>> {
56 let target = self.get_target_property_connection()?;
57 if let Some(v) = document
58 .cameras
59 .iter()
60 .find(|x| x.inner().object_index == target.dest)
61 {
62 return Some(NodeAttributeRef::Camera(v));
63 }
64 if let Some(v) = document
65 .camera_switchers
66 .iter()
67 .find(|x| x.inner().object_index == target.dest)
68 {
69 return Some(NodeAttributeRef::CameraSwitcher(v));
70 }
71 if let Some(v) = document
72 .lights
73 .iter()
74 .find(|x| x.inner().object_index == target.dest)
75 {
76 return Some(NodeAttributeRef::Light(v));
77 }
78 if let Some(v) = document
79 .null_nodes
80 .iter()
81 .find(|x| x.inner().object_index == target.dest)
82 {
83 return Some(NodeAttributeRef::NullNode(v));
84 }
85 if let Some(v) = document
86 .limb_nodes
87 .iter()
88 .find(|x| x.inner().object_index == target.dest)
89 {
90 return Some(NodeAttributeRef::LimbNode(v));
91 }
92 document
93 .unknown_node_attributes
94 .iter()
95 .find(|x| x.object_index == target.dest)
96 .map(NodeAttributeRef::Unknown)
97 }
98
99 pub fn get_target_deformer<'a>(
100 &'a self,
101 document: &'a OwnedDocument,
102 ) -> Option<&'a OwnedObject> {
103 let target = self.get_target_property_connection()?;
104 if let Some(v) = document
105 .clusters
106 .iter()
107 .find(|x| x.inner().object_index == target.dest)
108 {
109 return Some(v.inner());
110 }
111 if let Some(v) = document
112 .skins
113 .iter()
114 .find(|x| x.inner().object_index == target.dest)
115 {
116 return Some(v.inner());
117 }
118 if let Some(v) = document
119 .blend_shapes
120 .iter()
121 .find(|x| x.inner().object_index == target.dest)
122 {
123 return Some(v.inner());
124 }
125 if let Some(v) = document
126 .blend_shape_channels
127 .iter()
128 .find(|x| x.inner().object_index == target.dest)
129 {
130 return Some(v.inner());
131 }
132 document
133 .unknown_deformers
134 .iter()
135 .find(|x| x.object_index == target.dest)
136 }
137
138 pub fn get_curves<'a>(
140 &'a self,
141 document: &'a OwnedDocument,
142 ) -> HashMap<&'a str, &'a AnimationCurve> {
143 let node_id = self.inner().object_index;
144 let mut out = HashMap::new();
145 for curve in &document.animation_curves {
146 for conn in &curve.inner().object_property_targets {
147 if conn.dest == node_id && !conn.property.is_empty() {
148 out.insert(conn.property.as_str(), curve);
149 }
150 }
151 }
152 out
153 }
154}
155
156impl TryFrom<OwnedObject> for AnimationCurveNode {
157 type Error = FbxTypeMismatch;
158
159 fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
160 match fbx_object_tag(&o) {
161 FbxObjectTag::AnimationCurveNode => Ok(AnimationCurveNode(o)),
162 _ => Err(FbxTypeMismatch::wrong_object_kind(
163 o,
164 "AnimationCurveNode".to_string(),
165 )),
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use std::collections::HashMap;
173 use std::convert::TryFrom;
174
175 use crate::objects::{
176 ANIMATION_CURVE_CLASS_NAME, ANIMATION_CURVE_NODE_CLASS_NAME,
177 ANIMATION_CURVE_NODE_TYPE_NAME, ANIMATION_CURVE_TYPE_NAME, DEFORMER_TYPE_NAME,
178 MODEL_TYPE_NAME, NodeAttributeRef,
179 };
180 use crate::{ObjectPropertyConnection, OwnedDocument, OwnedObject, Property};
181
182 use super::AnimationCurveNode;
183
184 #[test]
185 fn property_accessors() {
186 let mut properties = HashMap::new();
187 properties.insert("d|Visibility".to_string(), Property::Float(1.0));
188 let o = OwnedObject {
189 object_index: 2,
190 name: "AnimCurveNode::T".into(),
191 type_name: ANIMATION_CURVE_NODE_TYPE_NAME.into(),
192 class_name: ANIMATION_CURVE_NODE_CLASS_NAME.into(),
193 properties,
194 attributes: HashMap::new(),
195 connected_object_ids: vec![],
196 object_property_targets: vec![],
197 pp_property_targets: HashMap::new(),
198 };
199 let n = AnimationCurveNode::try_from(o).unwrap();
200 assert_eq!(n.property("d|Visibility"), Some(&Property::Float(1.0)));
201 }
202
203 #[test]
204 fn resolves_target_model_and_curves() {
205 let node = AnimationCurveNode::try_from(OwnedObject {
206 object_index: 3000,
207 name: "AnimCurveNode::T".into(),
208 type_name: ANIMATION_CURVE_NODE_TYPE_NAME.into(),
209 class_name: ANIMATION_CURVE_NODE_CLASS_NAME.into(),
210 properties: HashMap::new(),
211 attributes: HashMap::new(),
212 connected_object_ids: vec![],
213 object_property_targets: vec![ObjectPropertyConnection {
214 dest: 3001,
215 property: "Lcl Translation".into(),
216 }],
217 pp_property_targets: HashMap::new(),
218 })
219 .unwrap();
220
221 let model = crate::objects::Model::try_from(OwnedObject {
222 object_index: 3001,
223 name: "Model::Node".into(),
224 type_name: MODEL_TYPE_NAME.into(),
225 class_name: "Mesh".into(),
226 properties: HashMap::new(),
227 attributes: HashMap::new(),
228 connected_object_ids: vec![],
229 object_property_targets: vec![],
230 pp_property_targets: HashMap::new(),
231 })
232 .unwrap();
233
234 let curve = crate::objects::AnimationCurve::try_from(OwnedObject {
235 object_index: 3002,
236 name: "AnimCurve::X".into(),
237 type_name: ANIMATION_CURVE_TYPE_NAME.into(),
238 class_name: ANIMATION_CURVE_CLASS_NAME.into(),
239 properties: HashMap::new(),
240 attributes: HashMap::from([
241 ("KeyTime".to_string(), {
242 use fbxscii::{ElementAttribute, LeafAttribute};
243 ElementAttribute::Leaf(Box::new(LeafAttribute {
244 key: String::new(),
245 tokens: vec!["0,1".into()],
246 }))
247 }),
248 ("KeyValueFloat".to_string(), {
249 use fbxscii::{ElementAttribute, LeafAttribute};
250 ElementAttribute::Leaf(Box::new(LeafAttribute {
251 key: String::new(),
252 tokens: vec!["0,1".into()],
253 }))
254 }),
255 ]),
256 connected_object_ids: vec![],
257 object_property_targets: vec![ObjectPropertyConnection {
258 dest: 3000,
259 property: "d|X".into(),
260 }],
261 pp_property_targets: HashMap::new(),
262 })
263 .unwrap();
264
265 let mut owned = OwnedDocument::default();
266 owned.models = vec![model];
267 owned.animation_curves = vec![curve];
268
269 assert_eq!(
270 node.get_target_model(&owned)
271 .map(|m| m.inner().object_index),
272 Some(3001)
273 );
274 let curves = node.get_curves(&owned);
275 assert_eq!(curves.len(), 1);
276 assert_eq!(
277 curves.get("d|X").map(|curve| curve.inner().object_index),
278 Some(3002)
279 );
280 }
281
282 #[test]
283 fn resolves_target_node_attribute_ref() {
284 let node = AnimationCurveNode::try_from(OwnedObject {
285 object_index: 3100,
286 name: "AnimCurveNode::T".into(),
287 type_name: ANIMATION_CURVE_NODE_TYPE_NAME.into(),
288 class_name: ANIMATION_CURVE_NODE_CLASS_NAME.into(),
289 properties: HashMap::new(),
290 attributes: HashMap::new(),
291 connected_object_ids: vec![],
292 object_property_targets: vec![ObjectPropertyConnection {
293 dest: 3101,
294 property: "d|Visibility".into(),
295 }],
296 pp_property_targets: HashMap::new(),
297 })
298 .unwrap();
299
300 let camera = crate::objects::Camera::try_from(OwnedObject {
301 object_index: 3101,
302 name: "NodeAttribute::Cam".into(),
303 type_name: "NodeAttribute".into(),
304 class_name: "Camera".into(),
305 properties: HashMap::new(),
306 attributes: HashMap::new(),
307 connected_object_ids: vec![],
308 object_property_targets: vec![],
309 pp_property_targets: HashMap::new(),
310 })
311 .unwrap();
312
313 let mut owned = OwnedDocument::default();
314 owned.cameras = vec![camera];
315 let target = node.get_target_node_attribute(&owned).unwrap();
316 assert!(matches!(target, NodeAttributeRef::Camera(_)));
317 }
318
319 #[test]
320 fn resolves_target_deformer() {
321 let node = AnimationCurveNode::try_from(OwnedObject {
322 object_index: 3200,
323 name: "AnimCurveNode::T".into(),
324 type_name: ANIMATION_CURVE_NODE_TYPE_NAME.into(),
325 class_name: ANIMATION_CURVE_NODE_CLASS_NAME.into(),
326 properties: HashMap::new(),
327 attributes: HashMap::new(),
328 connected_object_ids: vec![],
329 object_property_targets: vec![ObjectPropertyConnection {
330 dest: 3201,
331 property: "DeformPercent".into(),
332 }],
333 pp_property_targets: HashMap::new(),
334 })
335 .unwrap();
336
337 let mut owned = OwnedDocument::default();
338 owned.unknown_deformers = vec![OwnedObject {
339 object_index: 3201,
340 name: "Deformer::Custom".into(),
341 type_name: DEFORMER_TYPE_NAME.into(),
342 class_name: "CustomDeformer".into(),
343 properties: HashMap::new(),
344 attributes: HashMap::new(),
345 connected_object_ids: vec![],
346 object_property_targets: vec![],
347 pp_property_targets: HashMap::new(),
348 }];
349
350 let target = node.get_target_deformer(&owned).unwrap();
351 assert_eq!(target.object_index, 3201);
352 }
353}