1use crate::error::Result;
7use indexmap::IndexMap;
8use unity_asset_core::UnityValue;
9
10#[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#[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#[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#[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 pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
88 let mut game_object = Self::new();
89
90 if let Some(UnityValue::String(name)) = properties.get("m_Name") {
92 game_object.name = name.clone();
93 }
94
95 if let Some(UnityValue::Integer(layer)) = properties.get("m_Layer") {
97 game_object.layer = *layer as i32;
98 }
99
100 if let Some(UnityValue::String(tag)) = properties.get("m_Tag") {
102 game_object.tag = tag.clone();
103 }
104
105 if let Some(UnityValue::Bool(active)) = properties.get("m_IsActive") {
107 game_object.active = *active;
108 }
109
110 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 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#[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 pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
171 let mut transform = Self::new();
172
173 if let Some(position_value) = properties.get("m_LocalPosition") {
175 transform.position = Self::parse_vector3(position_value)?;
176 }
177
178 if let Some(rotation_value) = properties.get("m_LocalRotation") {
180 transform.rotation = Self::parse_quaternion(rotation_value)?;
181 }
182
183 if let Some(scale_value) = properties.get("m_LocalScale") {
185 transform.scale = Self::parse_vector3(scale_value)?;
186 }
187
188 if let Some(parent_value) = properties.get("m_Father") {
190 transform.parent = Self::parse_object_ref(parent_value);
191 }
192
193 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}