Skip to main content

fbx_dom/objects/
line_geometry.rs

1//! FBX `Geometry` / `Line` — Assimp [`LineGeometry`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXMeshGeometry.cpp).
2
3use std::convert::TryFrom;
4use std::num::ParseFloatError;
5use std::num::ParseIntError;
6
7use crate::OwnedObject;
8
9use super::AttrExtractor;
10use super::{FbxObjectTag, FbxTryFromReason, FbxTypeMismatch, fbx_object_tag};
11
12#[derive(Debug, PartialEq)]
13pub struct LineGeometry {
14    pub object: OwnedObject,
15    pub points: Vec<[f32; 3]>,
16    pub point_indices: Vec<i32>,
17}
18
19impl LineGeometry {
20    pub fn inner(&self) -> &OwnedObject {
21        &self.object
22    }
23
24    pub fn into_inner(self) -> OwnedObject {
25        self.object
26    }
27}
28
29impl TryFrom<OwnedObject> for LineGeometry {
30    type Error = FbxTypeMismatch;
31
32    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
33        match fbx_object_tag(&o) {
34            FbxObjectTag::LineGeometry => {}
35            _ => {
36                return Err(FbxTypeMismatch::wrong_object_kind(
37                    o,
38                    "LineGeometry".to_string(),
39                ));
40            }
41        }
42
43        let attrs = &o.attributes;
44
45        let points_tokens_attr = match attrs.extract_case_insensitive("Points") {
46            Some(a) => a,
47            None => {
48                return Err(FbxTypeMismatch::new(
49                    o,
50                    FbxTryFromReason::MissingAttribute {
51                        name: "Points".to_string(),
52                    },
53                ));
54            }
55        };
56        let children = points_tokens_attr.get_children_distinct();
57        let payload = children.get("a").unwrap_or(points_tokens_attr);
58        let points_tokens = payload.get_tokens();
59        let points_result = points_tokens
60            .iter()
61            .flat_map(|t| t.split(','))
62            .map(|t| t.trim())
63            .filter(|t| !t.is_empty())
64            .map(|t| t.parse::<f32>())
65            .collect::<Result<Vec<f32>, ParseFloatError>>();
66        let Ok(points_unchunked) = points_result else {
67            return Err(FbxTypeMismatch::new(
68                o,
69                FbxTryFromReason::InvalidAttributeFormat {
70                    name: "Points".to_string(),
71                    detail: format!("invalid float token: {}", points_result.unwrap_err()),
72                },
73            ));
74        };
75        let points = points_unchunked
76            .chunks_exact(3)
77            .map(|c| [c[0], c[1], c[2]])
78            .collect::<Vec<[f32; 3]>>();
79
80        let idx_tokens = match attrs.extract_case_insensitive("PointsIndex") {
81            Some(a) => a,
82            None => {
83                return Err(FbxTypeMismatch::new(
84                    o,
85                    FbxTryFromReason::MissingAttribute {
86                        name: "PointsIndex".to_string(),
87                    },
88                ));
89            }
90        };
91        let children = idx_tokens.get_children_distinct();
92        let payload = children.get("a").unwrap_or(idx_tokens);
93        let idx_tokens = payload.get_tokens();
94        let point_indices_result = idx_tokens
95            .iter()
96            .flat_map(|t| t.split(','))
97            .map(|t| t.trim())
98            .filter(|t| !t.is_empty())
99            .map(|t| t.parse::<i32>())
100            .collect::<Result<Vec<i32>, ParseIntError>>();
101        let Ok(point_indices) = point_indices_result else {
102            return Err(FbxTypeMismatch::new(
103                o,
104                FbxTryFromReason::InvalidAttributeFormat {
105                    name: "PointsIndex".to_string(),
106                    detail: format!("invalid int token: {}", point_indices_result.unwrap_err()),
107                },
108            ));
109        };
110
111        Ok(LineGeometry {
112            object: o,
113            points,
114            point_indices,
115        })
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use std::{collections::HashMap, convert::TryFrom};
122
123    use fbxscii::{ElementAttribute, LeafAttribute};
124
125    use crate::OwnedObject;
126
127    use super::super::{GEOMETRY_LINE_CLASS_NAME, GEOMETRY_TYPE_NAME};
128    use super::LineGeometry;
129
130    fn leaf(tokens: &[&str]) -> ElementAttribute {
131        ElementAttribute::Leaf(Box::new(LeafAttribute {
132            key: String::new(),
133            tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
134        }))
135    }
136
137    fn owned_line(attrs: HashMap<String, ElementAttribute>) -> OwnedObject {
138        OwnedObject {
139            object_index: 10,
140            name: "Geometry::TestLine".into(),
141            type_name: GEOMETRY_TYPE_NAME.into(),
142            class_name: GEOMETRY_LINE_CLASS_NAME.into(),
143            properties: HashMap::new(),
144            attributes: attrs,
145            connected_object_ids: vec![],
146            object_property_targets: vec![],
147            pp_property_targets: HashMap::new(),
148        }
149    }
150    #[test]
151    fn basic_parse() {
152        let mut attrs = HashMap::new();
153        attrs.insert("Points".into(), leaf(&["0,0,0,1,0,0,0,1,0"]));
154        attrs.insert("PointsIndex".into(), leaf(&["0,1,2"]));
155        let o = owned_line(attrs);
156        let lg = LineGeometry::try_from(o).unwrap();
157        let expected_points = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
158        let expected_point_indices = vec![0, 1, 2];
159        assert_eq!(lg.points, expected_points);
160        assert_eq!(lg.point_indices, expected_point_indices);
161    }
162}