use crate::error::Result;
use indexmap::IndexMap;
use unity_asset_core::UnityValue;
#[derive(Debug, Clone)]
pub struct ObjectRef {
pub file_id: i32,
pub path_id: i64,
}
impl ObjectRef {
pub fn new(file_id: i32, path_id: i64) -> Self {
Self { file_id, path_id }
}
pub fn is_null(&self) -> bool {
self.path_id == 0
}
}
#[derive(Debug, Clone, Default)]
pub struct Vector3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vector3 {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Default)]
pub struct Quaternion {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
impl Quaternion {
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self { x, y, z, w }
}
pub fn identity() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct GameObject {
pub name: String,
pub components: Vec<ObjectRef>,
pub layer: i32,
pub tag: String,
pub active: bool,
}
impl GameObject {
pub fn new() -> Self {
Self {
name: String::new(),
components: Vec::new(),
layer: 0,
tag: "Untagged".to_string(),
active: true,
}
}
pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
let mut game_object = Self::new();
if let Some(UnityValue::String(name)) = properties.get("m_Name") {
game_object.name = name.clone();
}
if let Some(UnityValue::Integer(layer)) = properties.get("m_Layer") {
game_object.layer = *layer as i32;
}
if let Some(UnityValue::String(tag)) = properties.get("m_Tag") {
game_object.tag = tag.clone();
}
if let Some(UnityValue::Bool(active)) = properties.get("m_IsActive") {
game_object.active = *active;
}
if let Some(UnityValue::Array(components_array)) = properties.get("m_Component") {
for component in components_array {
if let UnityValue::Object(component_obj) = component {
let file_id = component_obj
.get("fileID")
.and_then(|v| match v {
UnityValue::Integer(id) => Some(*id as i32),
_ => None,
})
.unwrap_or(0);
let path_id = component_obj
.get("pathID")
.and_then(|v| match v {
UnityValue::Integer(id) => Some(*id),
_ => None,
})
.unwrap_or(0);
game_object
.components
.push(ObjectRef::new(file_id, path_id));
}
}
}
Ok(game_object)
}
}
impl Default for GameObject {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Transform {
pub position: Vector3,
pub rotation: Quaternion,
pub scale: Vector3,
pub parent: Option<ObjectRef>,
pub children: Vec<ObjectRef>,
}
impl Transform {
pub fn new() -> Self {
Self {
position: Vector3::default(),
rotation: Quaternion::identity(),
scale: Vector3::new(1.0, 1.0, 1.0),
parent: None,
children: Vec::new(),
}
}
pub fn from_typetree(properties: &IndexMap<String, UnityValue>) -> Result<Self> {
let mut transform = Self::new();
if let Some(position_value) = properties.get("m_LocalPosition") {
transform.position = Self::parse_vector3(position_value)?;
}
if let Some(rotation_value) = properties.get("m_LocalRotation") {
transform.rotation = Self::parse_quaternion(rotation_value)?;
}
if let Some(scale_value) = properties.get("m_LocalScale") {
transform.scale = Self::parse_vector3(scale_value)?;
}
if let Some(parent_value) = properties.get("m_Father") {
transform.parent = Self::parse_object_ref(parent_value);
}
if let Some(UnityValue::Array(children_array)) = properties.get("m_Children") {
for child in children_array {
if let Some(child_ref) = Self::parse_object_ref(child) {
transform.children.push(child_ref);
}
}
}
Ok(transform)
}
fn parse_vector3(value: &UnityValue) -> Result<Vector3> {
match value {
UnityValue::Object(obj) => {
let x = obj
.get("x")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
let y = obj
.get("y")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
let z = obj
.get("z")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
Ok(Vector3::new(x, y, z))
}
_ => Ok(Vector3::default()),
}
}
fn parse_quaternion(value: &UnityValue) -> Result<Quaternion> {
match value {
UnityValue::Object(obj) => {
let x = obj
.get("x")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
let y = obj
.get("y")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
let z = obj
.get("z")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(0.0);
let w = obj
.get("w")
.and_then(|v| match v {
UnityValue::Float(f) => Some(*f as f32),
UnityValue::Integer(i) => Some(*i as f32),
_ => None,
})
.unwrap_or(1.0);
Ok(Quaternion::new(x, y, z, w))
}
_ => Ok(Quaternion::identity()),
}
}
fn parse_object_ref(value: &UnityValue) -> Option<ObjectRef> {
match value {
UnityValue::Object(obj) => {
let file_id = obj
.get("fileID")
.and_then(|v| match v {
UnityValue::Integer(id) => Some(*id as i32),
_ => None,
})
.unwrap_or(0);
let path_id = obj
.get("pathID")
.and_then(|v| match v {
UnityValue::Integer(id) => Some(*id),
_ => None,
})
.unwrap_or(0);
let obj_ref = ObjectRef::new(file_id, path_id);
if obj_ref.is_null() {
None
} else {
Some(obj_ref)
}
}
_ => None,
}
}
}
impl Default for Transform {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gameobject_creation() {
let game_object = GameObject::new();
assert_eq!(game_object.name, "");
assert_eq!(game_object.layer, 0);
assert_eq!(game_object.tag, "Untagged");
assert!(game_object.active);
assert!(game_object.components.is_empty());
}
#[test]
fn test_transform_creation() {
let transform = Transform::new();
assert_eq!(transform.position.x, 0.0);
assert_eq!(transform.position.y, 0.0);
assert_eq!(transform.position.z, 0.0);
assert_eq!(transform.rotation.w, 1.0);
assert_eq!(transform.scale.x, 1.0);
assert_eq!(transform.scale.y, 1.0);
assert_eq!(transform.scale.z, 1.0);
assert!(transform.parent.is_none());
assert!(transform.children.is_empty());
}
#[test]
fn test_object_ref() {
let obj_ref = ObjectRef::new(0, 12345);
assert_eq!(obj_ref.file_id, 0);
assert_eq!(obj_ref.path_id, 12345);
assert!(!obj_ref.is_null());
let null_ref = ObjectRef::new(0, 0);
assert!(null_ref.is_null());
}
}