unity_asset_binary/
unity_objects.rs

1//! Unity Core Object Types
2//!
3//! This module implements specific Unity object types like GameObject, Transform, etc.
4//! These are the concrete implementations that parse TypeTree data into structured objects.
5
6use crate::error::Result;
7use indexmap::IndexMap;
8use unity_asset_core::UnityValue;
9
10/// Reference to another Unity object
11#[derive(Debug, Clone)]
12pub struct ObjectRef {
13    pub file_id: i32,
14    pub path_id: i64,
15}
16
17impl ObjectRef {
18    pub fn new(file_id: i32, path_id: i64) -> Self {
19        Self { file_id, path_id }
20    }
21
22    pub fn is_null(&self) -> bool {
23        self.path_id == 0
24    }
25}
26
27/// 3D Vector
28#[derive(Debug, Clone, Default)]
29pub struct Vector3 {
30    pub x: f32,
31    pub y: f32,
32    pub z: f32,
33}
34
35impl Vector3 {
36    pub fn new(x: f32, y: f32, z: f32) -> Self {
37        Self { x, y, z }
38    }
39}
40
41/// Quaternion for rotations
42#[derive(Debug, Clone, Default)]
43pub struct Quaternion {
44    pub x: f32,
45    pub y: f32,
46    pub z: f32,
47    pub w: f32,
48}
49
50impl Quaternion {
51    pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
52        Self { x, y, z, w }
53    }
54
55    pub fn identity() -> Self {
56        Self {
57            x: 0.0,
58            y: 0.0,
59            z: 0.0,
60            w: 1.0,
61        }
62    }
63}
64
65/// Unity GameObject
66#[derive(Debug, Clone)]
67pub struct GameObject {
68    pub name: String,
69    pub components: Vec<ObjectRef>,
70    pub layer: i32,
71    pub tag: String,
72    pub active: bool,
73}
74
75impl GameObject {
76    pub fn new() -> Self {
77        Self {
78            name: String::new(),
79            components: Vec::new(),
80            layer: 0,
81            tag: "Untagged".to_string(),
82            active: true,
83        }
84    }
85
86    /// Parse GameObject from TypeTree data
87    pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
88        let mut game_object = Self::new();
89
90        // Extract name
91        if let Some(UnityValue::String(name)) = properties.get("m_Name") {
92            game_object.name = name.clone();
93        }
94
95        // Extract layer
96        if let Some(UnityValue::Integer(layer)) = properties.get("m_Layer") {
97            game_object.layer = *layer as i32;
98        }
99
100        // Extract tag
101        if let Some(UnityValue::String(tag)) = properties.get("m_Tag") {
102            game_object.tag = tag.clone();
103        }
104
105        // Extract active state
106        if let Some(UnityValue::Bool(active)) = properties.get("m_IsActive") {
107            game_object.active = *active;
108        }
109
110        // Extract components array
111        if let Some(UnityValue::Array(components_array)) = properties.get("m_Component") {
112            for component in components_array {
113                if let UnityValue::Object(component_obj) = component {
114                    // Each component is typically a structure with file_id and path_id
115                    let file_id = component_obj
116                        .get("fileID")
117                        .and_then(|v| match v {
118                            UnityValue::Integer(id) => Some(*id as i32),
119                            _ => None,
120                        })
121                        .unwrap_or(0);
122
123                    let path_id = component_obj
124                        .get("pathID")
125                        .and_then(|v| match v {
126                            UnityValue::Integer(id) => Some(*id),
127                            _ => None,
128                        })
129                        .unwrap_or(0);
130
131                    game_object
132                        .components
133                        .push(ObjectRef::new(file_id, path_id));
134                }
135            }
136        }
137
138        Ok(game_object)
139    }
140}
141
142impl Default for GameObject {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148/// Unity Transform component
149#[derive(Debug, Clone)]
150pub struct Transform {
151    pub position: Vector3,
152    pub rotation: Quaternion,
153    pub scale: Vector3,
154    pub parent: Option<ObjectRef>,
155    pub children: Vec<ObjectRef>,
156}
157
158impl Transform {
159    pub fn new() -> Self {
160        Self {
161            position: Vector3::default(),
162            rotation: Quaternion::identity(),
163            scale: Vector3::new(1.0, 1.0, 1.0),
164            parent: None,
165            children: Vec::new(),
166        }
167    }
168
169    /// Parse Transform from TypeTree data
170    pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
171        let mut transform = Self::new();
172
173        // Extract position
174        if let Some(position_value) = properties.get("m_LocalPosition") {
175            transform.position = Self::parse_vector3(position_value)?;
176        }
177
178        // Extract rotation
179        if let Some(rotation_value) = properties.get("m_LocalRotation") {
180            transform.rotation = Self::parse_quaternion(rotation_value)?;
181        }
182
183        // Extract scale
184        if let Some(scale_value) = properties.get("m_LocalScale") {
185            transform.scale = Self::parse_vector3(scale_value)?;
186        }
187
188        // Extract parent
189        if let Some(parent_value) = properties.get("m_Father") {
190            transform.parent = Self::parse_object_ref(parent_value);
191        }
192
193        // Extract children
194        if let Some(UnityValue::Array(children_array)) = properties.get("m_Children") {
195            for child in children_array {
196                if let Some(child_ref) = Self::parse_object_ref(child) {
197                    transform.children.push(child_ref);
198                }
199            }
200        }
201
202        Ok(transform)
203    }
204
205    fn parse_vector3(value: &UnityValue) -> Result<Vector3> {
206        match value {
207            UnityValue::Object(obj) => {
208                let x = obj
209                    .get("x")
210                    .and_then(|v| match v {
211                        UnityValue::Float(f) => Some(*f as f32),
212                        UnityValue::Integer(i) => Some(*i as f32),
213                        _ => None,
214                    })
215                    .unwrap_or(0.0);
216
217                let y = obj
218                    .get("y")
219                    .and_then(|v| match v {
220                        UnityValue::Float(f) => Some(*f as f32),
221                        UnityValue::Integer(i) => Some(*i as f32),
222                        _ => None,
223                    })
224                    .unwrap_or(0.0);
225
226                let z = obj
227                    .get("z")
228                    .and_then(|v| match v {
229                        UnityValue::Float(f) => Some(*f as f32),
230                        UnityValue::Integer(i) => Some(*i as f32),
231                        _ => None,
232                    })
233                    .unwrap_or(0.0);
234
235                Ok(Vector3::new(x, y, z))
236            }
237            _ => Ok(Vector3::default()),
238        }
239    }
240
241    fn parse_quaternion(value: &UnityValue) -> Result<Quaternion> {
242        match value {
243            UnityValue::Object(obj) => {
244                let x = obj
245                    .get("x")
246                    .and_then(|v| match v {
247                        UnityValue::Float(f) => Some(*f as f32),
248                        UnityValue::Integer(i) => Some(*i as f32),
249                        _ => None,
250                    })
251                    .unwrap_or(0.0);
252
253                let y = obj
254                    .get("y")
255                    .and_then(|v| match v {
256                        UnityValue::Float(f) => Some(*f as f32),
257                        UnityValue::Integer(i) => Some(*i as f32),
258                        _ => None,
259                    })
260                    .unwrap_or(0.0);
261
262                let z = obj
263                    .get("z")
264                    .and_then(|v| match v {
265                        UnityValue::Float(f) => Some(*f as f32),
266                        UnityValue::Integer(i) => Some(*i as f32),
267                        _ => None,
268                    })
269                    .unwrap_or(0.0);
270
271                let w = obj
272                    .get("w")
273                    .and_then(|v| match v {
274                        UnityValue::Float(f) => Some(*f as f32),
275                        UnityValue::Integer(i) => Some(*i as f32),
276                        _ => None,
277                    })
278                    .unwrap_or(1.0);
279
280                Ok(Quaternion::new(x, y, z, w))
281            }
282            _ => Ok(Quaternion::identity()),
283        }
284    }
285
286    fn parse_object_ref(value: &UnityValue) -> Option<ObjectRef> {
287        match value {
288            UnityValue::Object(obj) => {
289                let file_id = obj
290                    .get("fileID")
291                    .and_then(|v| match v {
292                        UnityValue::Integer(id) => Some(*id as i32),
293                        _ => None,
294                    })
295                    .unwrap_or(0);
296
297                let path_id = obj
298                    .get("pathID")
299                    .and_then(|v| match v {
300                        UnityValue::Integer(id) => Some(*id),
301                        _ => None,
302                    })
303                    .unwrap_or(0);
304
305                let obj_ref = ObjectRef::new(file_id, path_id);
306                if obj_ref.is_null() {
307                    None
308                } else {
309                    Some(obj_ref)
310                }
311            }
312            _ => None,
313        }
314    }
315}
316
317impl Default for Transform {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_gameobject_creation() {
329        let game_object = GameObject::new();
330        assert_eq!(game_object.name, "");
331        assert_eq!(game_object.layer, 0);
332        assert_eq!(game_object.tag, "Untagged");
333        assert!(game_object.active);
334        assert!(game_object.components.is_empty());
335    }
336
337    #[test]
338    fn test_transform_creation() {
339        let transform = Transform::new();
340        assert_eq!(transform.position.x, 0.0);
341        assert_eq!(transform.position.y, 0.0);
342        assert_eq!(transform.position.z, 0.0);
343        assert_eq!(transform.rotation.w, 1.0);
344        assert_eq!(transform.scale.x, 1.0);
345        assert_eq!(transform.scale.y, 1.0);
346        assert_eq!(transform.scale.z, 1.0);
347        assert!(transform.parent.is_none());
348        assert!(transform.children.is_empty());
349    }
350
351    #[test]
352    fn test_object_ref() {
353        let obj_ref = ObjectRef::new(0, 12345);
354        assert_eq!(obj_ref.file_id, 0);
355        assert_eq!(obj_ref.path_id, 12345);
356        assert!(!obj_ref.is_null());
357
358        let null_ref = ObjectRef::new(0, 0);
359        assert!(null_ref.is_null());
360    }
361}