From aebb5e6783541e4b2ef7d17710d309f889f91f42 Mon Sep 17 00:00:00 2001 From: Connor Date: Wed, 31 Dec 2025 13:55:39 +0900 Subject: [PATCH] components store data instead of yaml --- src/types/component.rs | 93 ++++++++------ src/types/game_object.rs | 135 +++++++++++--------- src/types/transform.rs | 268 ++++++++++++++++++++++++--------------- 3 files changed, 295 insertions(+), 201 deletions(-) diff --git a/src/types/component.rs b/src/types/component.rs index 839b14e..ca19d29 100644 --- a/src/types/component.rs +++ b/src/types/component.rs @@ -1,6 +1,7 @@ //! Component trait and generic component wrapper use crate::model::UnityDocument; +use crate::types::FileID; use crate::types::FileRef; /// A trait for Unity components @@ -15,8 +16,8 @@ pub trait Component { true // Default implementation } - /// Get the underlying UnityDocument - fn document(&self) -> &UnityDocument; + /// Get the file ID of this component + fn file_id(&self) -> FileID; } /// A generic component wrapper that works with any component type @@ -29,8 +30,8 @@ pub trait Component { /// let file = UnityFile::from_path("Scene.unity")?; /// for doc in &file.documents { /// if !doc.is_game_object() { -/// if let Some(comp) = GenericComponent::new(doc) { -/// println!("Component: {}", doc.class_name); +/// if let Some(comp) = GenericComponent::parse(doc) { +/// println!("Component: {}", comp.class_name()); /// if let Some(go_ref) = comp.game_object() { /// println!(" Attached to: {}", go_ref.file_id); /// } @@ -39,52 +40,63 @@ pub trait Component { /// } /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` -#[derive(Debug)] -pub struct GenericComponent<'a> { - document: &'a UnityDocument, +#[derive(Debug, Clone)] +pub struct GenericComponent { + file_id: FileID, + class_name: String, + game_object: Option, + is_enabled: bool, } -impl<'a> GenericComponent<'a> { - /// Create a GenericComponent wrapper from a UnityDocument +impl GenericComponent { + /// Parse a GenericComponent from a UnityDocument /// /// Returns None if the document is a GameObject (GameObjects are not components). - pub fn new(document: &'a UnityDocument) -> Option { - if !document.is_game_object() { - Some(Self { document }) - } else { - None + pub fn parse(document: &UnityDocument) -> Option { + if document.is_game_object() { + return None; } + + // Extract m_GameObject and m_Enabled from the component properties + let props = document + .get(&document.class_name) + .and_then(|obj| obj.as_object()); + + let game_object = props + .and_then(|p| p.get("m_GameObject")) + .and_then(|v| v.as_file_ref()) + .copied(); + + let is_enabled = props + .and_then(|p| p.get("m_Enabled")) + .and_then(|v| v.as_bool()) + .unwrap_or(true); + + Some(Self { + file_id: document.file_id, + class_name: document.class_name.clone(), + game_object, + is_enabled, + }) } /// Get the class name of this component pub fn class_name(&self) -> &str { - &self.document.class_name + &self.class_name } } -impl<'a> Component for GenericComponent<'a> { +impl Component for GenericComponent { fn game_object(&self) -> Option { - // Look for m_GameObject property which is common to all components - self.document - .get(&self.document.class_name) - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_GameObject")) - .and_then(|v| v.as_file_ref()) - .copied() + self.game_object } fn is_enabled(&self) -> bool { - // Look for m_Enabled property - self.document - .get(&self.document.class_name) - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_Enabled")) - .and_then(|v| v.as_bool()) - .unwrap_or(true) // Default to enabled + self.is_enabled } - fn document(&self) -> &UnityDocument { - self.document + fn file_id(&self) -> FileID { + self.file_id } } @@ -118,14 +130,14 @@ mod tests { #[test] fn test_component_creation() { let doc = create_test_component(); - let comp = GenericComponent::new(&doc); + let comp = GenericComponent::parse(&doc); assert!(comp.is_some()); } #[test] fn test_component_game_object_ref() { let doc = create_test_component(); - let comp = GenericComponent::new(&doc).unwrap(); + let comp = GenericComponent::parse(&doc).unwrap(); let go_ref = comp.game_object(); assert!(go_ref.is_some()); assert_eq!(go_ref.unwrap().file_id.as_i64(), 67890); @@ -134,17 +146,24 @@ mod tests { #[test] fn test_component_is_enabled() { let doc = create_test_component(); - let comp = GenericComponent::new(&doc).unwrap(); + let comp = GenericComponent::parse(&doc).unwrap(); assert!(comp.is_enabled()); } #[test] fn test_component_class_name() { let doc = create_test_component(); - let comp = GenericComponent::new(&doc).unwrap(); + let comp = GenericComponent::parse(&doc).unwrap(); assert_eq!(comp.class_name(), "Transform"); } + #[test] + fn test_component_file_id() { + let doc = create_test_component(); + let comp = GenericComponent::parse(&doc).unwrap(); + assert_eq!(comp.file_id().as_i64(), 12345); + } + #[test] fn test_game_object_is_not_component() { let mut properties = IndexMap::new(); @@ -157,7 +176,7 @@ mod tests { properties, }; - let comp = GenericComponent::new(&doc); + let comp = GenericComponent::parse(&doc); assert!(comp.is_none()); } } diff --git a/src/types/game_object.rs b/src/types/game_object.rs index 8559df0..f85060a 100644 --- a/src/types/game_object.rs +++ b/src/types/game_object.rs @@ -14,7 +14,7 @@ use crate::types::{FileID, FileRef}; /// /// let file = UnityFile::from_path("Scene.unity")?; /// for doc in &file.documents { -/// if let Some(go) = GameObject::new(doc) { +/// if let Some(go) = GameObject::parse(doc) { /// println!("GameObject: {}", go.name().unwrap_or("Unnamed")); /// println!(" Active: {}", go.is_active()); /// println!(" Components: {}", go.components().len()); @@ -22,66 +22,50 @@ use crate::types::{FileID, FileRef}; /// } /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` -#[derive(Debug)] -pub struct GameObject<'a> { - document: &'a UnityDocument, +#[derive(Debug, Clone)] +pub struct GameObject { + file_id: FileID, + name: Option, + is_active: bool, + layer: Option, + tag: Option, + components: Vec, } -impl<'a> GameObject<'a> { - /// Create a GameObject wrapper from a UnityDocument +impl GameObject { + /// Parse a GameObject from a UnityDocument /// /// Returns None if the document is not a GameObject. - pub fn new(document: &'a UnityDocument) -> Option { - if document.is_game_object() { - Some(Self { document }) - } else { - None + pub fn parse(document: &UnityDocument) -> Option { + if !document.is_game_object() { + return None; } - } - /// Get the GameObject's name - pub fn name(&self) -> Option<&str> { - self.document + // Get the GameObject properties object + let props = document .get("GameObject") - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_Name")) + .and_then(|obj| obj.as_object()); + + let name = props + .and_then(|p| p.get("m_Name")) .and_then(|v| v.as_str()) - } + .map(|s| s.to_string()); - /// Check if the GameObject is active - pub fn is_active(&self) -> bool { - self.document - .get("GameObject") - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_IsActive")) + let is_active = props + .and_then(|p| p.get("m_IsActive")) .and_then(|v| v.as_bool()) - .unwrap_or(true) // Default to true if not specified - } + .unwrap_or(true); - /// Get the GameObject's layer - pub fn layer(&self) -> Option { - self.document - .get("GameObject") - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_Layer")) - .and_then(|v| v.as_i64()) - } + let layer = props + .and_then(|p| p.get("m_Layer")) + .and_then(|v| v.as_i64()); - /// Get the GameObject's tag as a tag ID - pub fn tag(&self) -> Option { - self.document - .get("GameObject") - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_TagString")) - .and_then(|v| v.as_i64()) - } + let tag = props + .and_then(|p| p.get("m_TagString")) + .and_then(|v| v.as_i64()); - /// Get the list of component references attached to this GameObject - pub fn components(&self) -> Vec { - self.document - .get("GameObject") - .and_then(|obj| obj.as_object()) - .and_then(|props| props.get("m_Component")) + let components = props + .and_then(|p| p.get("m_Component")) .and_then(|v| v.as_array()) .map(|arr| { arr.iter() @@ -94,17 +78,46 @@ impl<'a> GameObject<'a> { }) .collect() }) - .unwrap_or_default() + .unwrap_or_default(); + + Some(Self { + file_id: document.file_id, + name, + is_active, + layer, + tag, + components, + }) + } + + /// Get the GameObject's name + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Check if the GameObject is active + pub fn is_active(&self) -> bool { + self.is_active + } + + /// Get the GameObject's layer + pub fn layer(&self) -> Option { + self.layer + } + + /// Get the GameObject's tag as a tag ID + pub fn tag(&self) -> Option { + self.tag + } + + /// Get the list of component references attached to this GameObject + pub fn components(&self) -> &[FileRef] { + &self.components } /// Get the file ID of this GameObject pub fn file_id(&self) -> FileID { - self.document.file_id - } - - /// Get the underlying UnityDocument - pub fn document(&self) -> &'a UnityDocument { - self.document + self.file_id } } @@ -136,35 +149,35 @@ mod tests { #[test] fn test_game_object_creation() { let doc = create_test_game_object(); - let go = GameObject::new(&doc); + let go = GameObject::parse(&doc); assert!(go.is_some()); } #[test] fn test_game_object_name() { let doc = create_test_game_object(); - let go = GameObject::new(&doc).unwrap(); + let go = GameObject::parse(&doc).unwrap(); assert_eq!(go.name(), Some("TestObject")); } #[test] fn test_game_object_is_active() { let doc = create_test_game_object(); - let go = GameObject::new(&doc).unwrap(); + let go = GameObject::parse(&doc).unwrap(); assert!(go.is_active()); } #[test] fn test_game_object_layer() { let doc = create_test_game_object(); - let go = GameObject::new(&doc).unwrap(); + let go = GameObject::parse(&doc).unwrap(); assert_eq!(go.layer(), Some(0)); } #[test] fn test_game_object_file_id() { let doc = create_test_game_object(); - let go = GameObject::new(&doc).unwrap(); + let go = GameObject::parse(&doc).unwrap(); assert_eq!(go.file_id().as_i64(), 12345); } @@ -174,7 +187,7 @@ mod tests { doc.type_id = 4; // Transform type ID doc.class_name = "Transform".to_string(); - let go = GameObject::new(&doc); + let go = GameObject::parse(&doc); assert!(go.is_none()); } } diff --git a/src/types/transform.rs b/src/types/transform.rs index 537bac9..45176fd 100644 --- a/src/types/transform.rs +++ b/src/types/transform.rs @@ -1,7 +1,7 @@ //! Transform and RectTransform component wrappers use crate::model::UnityDocument; -use crate::types::{Component, FileRef, Quaternion, Vector2, Vector3}; +use crate::types::{Component, FileID, FileRef, Quaternion, Vector2, Vector3}; /// A wrapper around a UnityDocument that represents a Transform component /// @@ -14,7 +14,7 @@ use crate::types::{Component, FileRef, Quaternion, Vector2, Vector3}; /// /// let file = UnityFile::from_path("Scene.unity")?; /// for doc in &file.documents { -/// if let Some(transform) = Transform::new(doc) { +/// if let Some(transform) = Transform::parse(doc) { /// if let Some(pos) = transform.local_position() { /// println!("Position: ({}, {}, {})", pos.x, pos.y, pos.z); /// } @@ -22,56 +22,58 @@ use crate::types::{Component, FileRef, Quaternion, Vector2, Vector3}; /// } /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` -#[derive(Debug)] -pub struct Transform<'a> { - document: &'a UnityDocument, +#[derive(Debug, Clone)] +pub struct Transform { + file_id: FileID, + game_object: Option, + local_position: Option, + local_rotation: Option, + local_scale: Option, + parent: Option, + children: Vec, } -impl<'a> Transform<'a> { - /// Create a Transform wrapper from a UnityDocument +impl Transform { + /// Parse a Transform from a UnityDocument /// /// Returns None if the document is not a Transform or RectTransform. - pub fn new(document: &'a UnityDocument) -> Option { - if document.class_name == "Transform" || document.class_name == "RectTransform" { - Some(Self { document }) - } else { - None + pub fn parse(document: &UnityDocument) -> Option { + if document.class_name != "Transform" && document.class_name != "RectTransform" { + return None; } - } - /// Get the local position of this transform - pub fn local_position(&self) -> Option<&Vector3> { - self.get_transform_props() - .and_then(|props| props.get("m_LocalPosition")) - .and_then(|v| v.as_vector3()) - } + // Get the transform properties object + let props = document + .get(&document.class_name) + .and_then(|v| v.as_object()); - /// Get the local rotation of this transform - pub fn local_rotation(&self) -> Option<&Quaternion> { - self.get_transform_props() - .and_then(|props| props.get("m_LocalRotation")) - .and_then(|v| v.as_quaternion()) - } - - /// Get the local scale of this transform - pub fn local_scale(&self) -> Option<&Vector3> { - self.get_transform_props() - .and_then(|props| props.get("m_LocalScale")) - .and_then(|v| v.as_vector3()) - } - - /// Get the parent transform reference - pub fn parent(&self) -> Option { - self.get_transform_props() - .and_then(|props| props.get("m_Father")) + let game_object = props + .and_then(|p| p.get("m_GameObject")) .and_then(|v| v.as_file_ref()) - .copied() - } + .copied(); - /// Get the list of child transform references - pub fn children(&self) -> Vec { - self.get_transform_props() - .and_then(|props| props.get("m_Children")) + let local_position = props + .and_then(|p| p.get("m_LocalPosition")) + .and_then(|v| v.as_vector3()) + .copied(); + + let local_rotation = props + .and_then(|p| p.get("m_LocalRotation")) + .and_then(|v| v.as_quaternion()) + .copied(); + + let local_scale = props + .and_then(|p| p.get("m_LocalScale")) + .and_then(|v| v.as_vector3()) + .copied(); + + let parent = props + .and_then(|p| p.get("m_Father")) + .and_then(|v| v.as_file_ref()) + .copied(); + + let children = props + .and_then(|p| p.get("m_Children")) .and_then(|v| v.as_array()) .map(|arr| { arr.iter() @@ -79,31 +81,56 @@ impl<'a> Transform<'a> { .copied() .collect() }) - .unwrap_or_default() + .unwrap_or_default(); + + Some(Self { + file_id: document.file_id, + game_object, + local_position, + local_rotation, + local_scale, + parent, + children, + }) } - /// Helper to get the transform properties object - fn get_transform_props(&self) -> Option<&indexmap::IndexMap> { - self.document - .get(&self.document.class_name) - .and_then(|v| v.as_object()) + /// Get the local position of this transform + pub fn local_position(&self) -> Option<&Vector3> { + self.local_position.as_ref() + } + + /// Get the local rotation of this transform + pub fn local_rotation(&self) -> Option<&Quaternion> { + self.local_rotation.as_ref() + } + + /// Get the local scale of this transform + pub fn local_scale(&self) -> Option<&Vector3> { + self.local_scale.as_ref() + } + + /// Get the parent transform reference + pub fn parent(&self) -> Option { + self.parent + } + + /// Get the list of child transform references + pub fn children(&self) -> &[FileRef] { + &self.children } } -impl<'a> Component for Transform<'a> { +impl Component for Transform { fn game_object(&self) -> Option { - self.get_transform_props() - .and_then(|props| props.get("m_GameObject")) - .and_then(|v| v.as_file_ref()) - .copied() + self.game_object } fn is_enabled(&self) -> bool { true // Transforms are always enabled } - fn document(&self) -> &UnityDocument { - self.document + fn file_id(&self) -> FileID { + self.file_id } } @@ -118,7 +145,7 @@ impl<'a> Component for Transform<'a> { /// /// let file = UnityFile::from_path("Canvas.prefab")?; /// for doc in &file.documents { -/// if let Some(rect_transform) = RectTransform::new(doc) { +/// if let Some(rect_transform) = RectTransform::parse(doc) { /// if let Some(anchor_min) = rect_transform.anchor_min() { /// println!("Anchor Min: ({}, {})", anchor_min.x, anchor_min.y); /// } @@ -126,58 +153,91 @@ impl<'a> Component for Transform<'a> { /// } /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` -#[derive(Debug)] -pub struct RectTransform<'a> { - transform: Transform<'a>, +#[derive(Debug, Clone)] +pub struct RectTransform { + transform: Transform, + anchor_min: Option, + anchor_max: Option, + anchored_position: Option, + size_delta: Option, + pivot: Option, } -impl<'a> RectTransform<'a> { - /// Create a RectTransform wrapper from a UnityDocument +impl RectTransform { + /// Parse a RectTransform from a UnityDocument /// /// Returns None if the document is not a RectTransform. - pub fn new(document: &'a UnityDocument) -> Option { - if document.class_name == "RectTransform" { - Some(Self { - transform: Transform { document }, - }) - } else { - None + pub fn parse(document: &UnityDocument) -> Option { + if document.class_name != "RectTransform" { + return None; } + + // Parse the base Transform + let transform = Transform::parse(document)?; + + // Get the RectTransform properties object + let props = document + .get("RectTransform") + .and_then(|v| v.as_object()); + + let anchor_min = props + .and_then(|p| p.get("m_AnchorMin")) + .and_then(|v| v.as_vector2()) + .copied(); + + let anchor_max = props + .and_then(|p| p.get("m_AnchorMax")) + .and_then(|v| v.as_vector2()) + .copied(); + + let anchored_position = props + .and_then(|p| p.get("m_AnchoredPosition")) + .and_then(|v| v.as_vector2()) + .copied(); + + let size_delta = props + .and_then(|p| p.get("m_SizeDelta")) + .and_then(|v| v.as_vector2()) + .copied(); + + let pivot = props + .and_then(|p| p.get("m_Pivot")) + .and_then(|v| v.as_vector2()) + .copied(); + + Some(Self { + transform, + anchor_min, + anchor_max, + anchored_position, + size_delta, + pivot, + }) } /// Get the anchor min (bottom-left anchor) pub fn anchor_min(&self) -> Option<&Vector2> { - self.get_rect_props() - .and_then(|props| props.get("m_AnchorMin")) - .and_then(|v| v.as_vector2()) + self.anchor_min.as_ref() } /// Get the anchor max (top-right anchor) pub fn anchor_max(&self) -> Option<&Vector2> { - self.get_rect_props() - .and_then(|props| props.get("m_AnchorMax")) - .and_then(|v| v.as_vector2()) + self.anchor_max.as_ref() } /// Get the anchored position pub fn anchored_position(&self) -> Option<&Vector2> { - self.get_rect_props() - .and_then(|props| props.get("m_AnchoredPosition")) - .and_then(|v| v.as_vector2()) + self.anchored_position.as_ref() } /// Get the size delta pub fn size_delta(&self) -> Option<&Vector2> { - self.get_rect_props() - .and_then(|props| props.get("m_SizeDelta")) - .and_then(|v| v.as_vector2()) + self.size_delta.as_ref() } /// Get the pivot point pub fn pivot(&self) -> Option<&Vector2> { - self.get_rect_props() - .and_then(|props| props.get("m_Pivot")) - .and_then(|v| v.as_vector2()) + self.pivot.as_ref() } /// Get the local position (from Transform) @@ -201,17 +261,12 @@ impl<'a> RectTransform<'a> { } /// Get the list of child transform references (from Transform) - pub fn children(&self) -> Vec { + pub fn children(&self) -> &[FileRef] { self.transform.children() } - - /// Helper to get the RectTransform properties object - fn get_rect_props(&self) -> Option<&indexmap::IndexMap> { - self.transform.get_transform_props() - } } -impl<'a> Component for RectTransform<'a> { +impl Component for RectTransform { fn game_object(&self) -> Option { self.transform.game_object() } @@ -220,8 +275,8 @@ impl<'a> Component for RectTransform<'a> { self.transform.is_enabled() } - fn document(&self) -> &UnityDocument { - self.transform.document() + fn file_id(&self) -> FileID { + self.transform.file_id() } } @@ -314,14 +369,14 @@ mod tests { #[test] fn test_transform_creation() { let doc = create_test_transform(); - let transform = Transform::new(&doc); + let transform = Transform::parse(&doc); assert!(transform.is_some()); } #[test] fn test_transform_local_position() { let doc = create_test_transform(); - let transform = Transform::new(&doc).unwrap(); + let transform = Transform::parse(&doc).unwrap(); let pos = transform.local_position().unwrap(); assert_eq!(pos.x, 1.0); assert_eq!(pos.y, 2.0); @@ -331,7 +386,7 @@ mod tests { #[test] fn test_transform_local_rotation() { let doc = create_test_transform(); - let transform = Transform::new(&doc).unwrap(); + let transform = Transform::parse(&doc).unwrap(); let rot = transform.local_rotation().unwrap(); assert_eq!(rot.w, 1.0); } @@ -339,22 +394,29 @@ mod tests { #[test] fn test_transform_local_scale() { let doc = create_test_transform(); - let transform = Transform::new(&doc).unwrap(); + let transform = Transform::parse(&doc).unwrap(); let scale = transform.local_scale().unwrap(); assert_eq!(scale, &Vector3::ONE); } + #[test] + fn test_transform_file_id() { + let doc = create_test_transform(); + let transform = Transform::parse(&doc).unwrap(); + assert_eq!(transform.file_id().as_i64(), 12345); + } + #[test] fn test_rect_transform_creation() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc); + let rect_transform = RectTransform::parse(&doc); assert!(rect_transform.is_some()); } #[test] fn test_rect_transform_anchor_min() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc).unwrap(); + let rect_transform = RectTransform::parse(&doc).unwrap(); let anchor_min = rect_transform.anchor_min().unwrap(); assert_eq!(anchor_min, &Vector2::ZERO); } @@ -362,7 +424,7 @@ mod tests { #[test] fn test_rect_transform_anchor_max() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc).unwrap(); + let rect_transform = RectTransform::parse(&doc).unwrap(); let anchor_max = rect_transform.anchor_max().unwrap(); assert_eq!(anchor_max, &Vector2::ONE); } @@ -370,7 +432,7 @@ mod tests { #[test] fn test_rect_transform_size_delta() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc).unwrap(); + let rect_transform = RectTransform::parse(&doc).unwrap(); let size_delta = rect_transform.size_delta().unwrap(); assert_eq!(size_delta.x, 100.0); assert_eq!(size_delta.y, 50.0); @@ -379,7 +441,7 @@ mod tests { #[test] fn test_rect_transform_pivot() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc).unwrap(); + let rect_transform = RectTransform::parse(&doc).unwrap(); let pivot = rect_transform.pivot().unwrap(); assert_eq!(pivot.x, 0.5); assert_eq!(pivot.y, 0.5); @@ -388,7 +450,7 @@ mod tests { #[test] fn test_rect_transform_inherits_from_transform() { let doc = create_test_rect_transform(); - let rect_transform = RectTransform::new(&doc).unwrap(); + let rect_transform = RectTransform::parse(&doc).unwrap(); // Test inherited methods assert!(rect_transform.local_position().is_some());