world positions
This commit is contained in:
@@ -40,13 +40,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Parse the scene using the project
|
||||
match project.parse_scene(scene_path) {
|
||||
Ok(scene) => {
|
||||
Ok(mut scene) => {
|
||||
info!("✅ Scene parsed successfully!");
|
||||
info!(" Total entities: {}", scene.entity_map.len());
|
||||
|
||||
// Post-processing: Compute world transforms
|
||||
info!("🔄 Computing world transforms...");
|
||||
unity_parser::compute_world_transforms(&mut scene.world, &scene.entity_map);
|
||||
info!(" ✓ World transforms computed");
|
||||
|
||||
// Get views for component types we need
|
||||
let resource_view = scene.world.borrow::<InteractableResource>();
|
||||
let transform_view = scene.world.borrow::<unity_parser::Transform>();
|
||||
let world_transform_view = scene.world.borrow::<unity_parser::WorldTransform>();
|
||||
let gameobject_view = scene.world.borrow::<unity_parser::GameObject>();
|
||||
|
||||
info!("DEBUG: entity_map size: {}", scene.entity_map.len());
|
||||
@@ -72,17 +78,24 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for entity in scene.entity_map.values() {
|
||||
if let Some(resource) = resource_view.get(*entity) {
|
||||
let transform = transform_view.get(*entity);
|
||||
let world_transform = world_transform_view.get(*entity);
|
||||
let game_object = gameobject_view.get(*entity);
|
||||
|
||||
let name = game_object
|
||||
.and_then(|go| go.name())
|
||||
.unwrap_or("(unnamed)");
|
||||
|
||||
let position = transform
|
||||
let local_position = transform
|
||||
.and_then(|t| t.local_position())
|
||||
.map(|p| (p.x, p.y, p.z));
|
||||
|
||||
found_resources.push((name.to_string(), resource.clone(), position));
|
||||
let world_position = world_transform
|
||||
.map(|wt| {
|
||||
let pos = wt.position();
|
||||
(pos.x, pos.y, pos.z)
|
||||
});
|
||||
|
||||
found_resources.push((name.to_string(), resource.clone(), local_position, world_position));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +104,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
if !found_resources.is_empty() {
|
||||
// Display resources in console
|
||||
for (name, resource, position) in &found_resources {
|
||||
for (name, resource, local_pos, world_pos) in &found_resources {
|
||||
info!(" 📦 Resource: \"{}\"", name);
|
||||
info!(" • typeId: {}", resource.type_id);
|
||||
info!(" • maxHealth: {}", resource.max_health);
|
||||
if let Some((x, y, z)) = position {
|
||||
info!(" • Position: ({:.2}, {:.2}, {:.2})", x, y, z);
|
||||
if let Some((x, y, z)) = local_pos {
|
||||
info!(" • Local Position: ({:.2}, {:.2}, {:.2})", x, y, z);
|
||||
} else {
|
||||
info!(" • Position: (no transform)");
|
||||
info!(" • Local Position: (no transform)");
|
||||
}
|
||||
if let Some((x, y, z)) = world_pos {
|
||||
info!(" • World Position: ({:.2}, {:.2}, {:.2})", x, y, z);
|
||||
} else {
|
||||
info!(" • World Position: (not computed)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,14 +132,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
writeln!(output_file, "{}", "-".repeat(70))?;
|
||||
writeln!(output_file)?;
|
||||
|
||||
for (name, resource, position) in &found_resources {
|
||||
for (name, resource, local_pos, world_pos) in &found_resources {
|
||||
writeln!(output_file, "Resource: {}", name)?;
|
||||
writeln!(output_file, " TypeID: {}", resource.type_id)?;
|
||||
writeln!(output_file, " MaxHealth: {}", resource.max_health)?;
|
||||
if let Some((x, y, z)) = position {
|
||||
writeln!(output_file, " Position: ({:.6}, {:.6}, {:.6})", x, y, z)?;
|
||||
if let Some((x, y, z)) = local_pos {
|
||||
writeln!(output_file, " Local Position: ({:.6}, {:.6}, {:.6})", x, y, z)?;
|
||||
} else {
|
||||
writeln!(output_file, " Position: N/A")?;
|
||||
writeln!(output_file, " Local Position: N/A")?;
|
||||
}
|
||||
if let Some((x, y, z)) = world_pos {
|
||||
writeln!(output_file, " World Position: ({:.6}, {:.6}, {:.6})", x, y, z)?;
|
||||
} else {
|
||||
writeln!(output_file, " World Position: N/A")?;
|
||||
}
|
||||
writeln!(output_file)?;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{Error, Result};
|
||||
use sparsey::{Entity, World};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use crate::post_processing::WorldTransform;
|
||||
|
||||
/// Build a Sparsey ECS World from raw Unity documents
|
||||
///
|
||||
@@ -40,7 +41,8 @@ pub fn build_world_from_documents(
|
||||
.register::<Transform>()
|
||||
.register::<RectTransform>()
|
||||
.register::<PrefabInstanceComponent>()
|
||||
.register::<StrippedReference>();
|
||||
.register::<StrippedReference>()
|
||||
.register::<WorldTransform>();
|
||||
|
||||
// Register all custom components from inventory
|
||||
for reg in inventory::iter::<crate::types::ComponentRegistration> {
|
||||
@@ -161,8 +163,6 @@ pub fn build_world_from_documents(
|
||||
// PASS 3: Execute all deferred linking callbacks
|
||||
let entity_map = linking_ctx.into_inner().execute_callbacks(&mut world);
|
||||
|
||||
info!("DEBUG (build_world_from_documents): Final scene entity_map size: {}", entity_map.len());
|
||||
|
||||
Ok((world, entity_map))
|
||||
}
|
||||
|
||||
@@ -314,14 +314,9 @@ pub fn build_world_from_documents_into(
|
||||
// PASS 3: Execute all deferred linking callbacks
|
||||
let final_entity_map = linking_ctx.into_inner().execute_callbacks(world);
|
||||
|
||||
info!("DEBUG (build_world_from_documents_into): Spawned {} entities", spawned_entities.len());
|
||||
info!("DEBUG (build_world_from_documents_into): final_entity_map size: {}, entity_map size before extend: {}", final_entity_map.len(), entity_map.len());
|
||||
|
||||
// Update caller's entity_map with new mappings
|
||||
entity_map.extend(final_entity_map);
|
||||
|
||||
info!("DEBUG (build_world_from_documents_into): entity_map size after extend: {}", entity_map.len());
|
||||
|
||||
Ok(spawned_entities)
|
||||
}
|
||||
|
||||
@@ -374,17 +369,7 @@ fn attach_component(
|
||||
.copied();
|
||||
|
||||
match entity_opt {
|
||||
Some(e) => {
|
||||
// Debug logging for Interactable_Resource MonoBehaviours
|
||||
if doc.class_name == "MonoBehaviour" {
|
||||
if let Some(script_ref) = yaml_helpers::get_external_ref(yaml, "m_Script") {
|
||||
if script_ref.guid.as_str() == "d39ddbf1c2c3d1a4baa070e5e76548bd" {
|
||||
info!("DEBUG: Found GameObject entity {:?} for Interactable_Resource MonoBehaviour (FileID: {})", e, doc.file_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
e
|
||||
}
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return Err(Error::reference_error(format!("Unknown GameObject: {}", r.file_id)));
|
||||
}
|
||||
@@ -447,11 +432,6 @@ fn attach_component(
|
||||
if let Some(script_ref) = yaml_helpers::get_external_ref(yaml, "m_Script") {
|
||||
// Resolve GUID to class name
|
||||
if let Some(class_name) = resolver.resolve_class_name(script_ref.guid.as_str()) {
|
||||
// Debug logging for specific GUID
|
||||
if script_ref.guid.as_str() == "d39ddbf1c2c3d1a4baa070e5e76548bd" {
|
||||
info!("DEBUG: Found Interactable_Resource GUID! Resolved to class name: {}", class_name);
|
||||
}
|
||||
|
||||
// Try to find a registered custom component with this class name
|
||||
let mut found_custom = false;
|
||||
for reg in inventory::iter::<crate::types::ComponentRegistration> {
|
||||
@@ -461,7 +441,6 @@ fn attach_component(
|
||||
if (reg.parse_and_insert)(yaml, &ctx, world, entity) {
|
||||
// Successfully parsed and inserted
|
||||
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
|
||||
info!("DEBUG: Successfully inserted component: {}", class_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub mod log;
|
||||
pub mod macros;
|
||||
pub mod model;
|
||||
pub mod parser;
|
||||
pub mod post_processing;
|
||||
// TODO: Update project module to work with new UnityFile enum architecture
|
||||
// pub mod project;
|
||||
pub mod property;
|
||||
@@ -43,6 +44,7 @@ pub use parser::{
|
||||
find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered,
|
||||
GuidResolver, PrefabGuidResolver,
|
||||
};
|
||||
pub use post_processing::{compute_world_transforms, WorldTransform};
|
||||
pub use property::PropertyValue;
|
||||
pub use types::{
|
||||
get_class_name, get_type_id, Color, ComponentContext, ComponentRegistration, EcsInsertable,
|
||||
|
||||
8
unity-parser/src/post_processing/mod.rs
Normal file
8
unity-parser/src/post_processing/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Post-processing features for Unity scenes
|
||||
//!
|
||||
//! This module provides various post-processing operations that can be applied
|
||||
//! to parsed Unity scenes to enhance or compute additional data.
|
||||
|
||||
mod world_transform;
|
||||
|
||||
pub use world_transform::{compute_world_transforms, WorldTransform};
|
||||
252
unity-parser/src/post_processing/world_transform.rs
Normal file
252
unity-parser/src/post_processing/world_transform.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
//! World Transform Computation
|
||||
//!
|
||||
//! This module provides functionality to compute world-space (global) transforms
|
||||
//! from local transforms in a Unity scene hierarchy.
|
||||
|
||||
use glam::Mat4;
|
||||
use sparsey::{Entity, World};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::types::{Transform, RectTransform};
|
||||
use crate::FileID;
|
||||
|
||||
/// A component that stores the world-space transform matrix
|
||||
///
|
||||
/// This is computed from the local transform hierarchy and represents
|
||||
/// the final global position, rotation, and scale of an entity.
|
||||
///
|
||||
/// Note: This is a computed component added during post-processing,
|
||||
/// not a Unity component parsed from YAML.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorldTransform {
|
||||
/// 4x4 transformation matrix in world space
|
||||
pub matrix: Mat4,
|
||||
}
|
||||
|
||||
impl WorldTransform {
|
||||
/// Create a new WorldTransform from a matrix
|
||||
pub fn new(matrix: Mat4) -> Self {
|
||||
Self { matrix }
|
||||
}
|
||||
|
||||
/// Create an identity WorldTransform
|
||||
pub fn identity() -> Self {
|
||||
Self {
|
||||
matrix: Mat4::IDENTITY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the world position from the matrix
|
||||
pub fn position(&self) -> glam::Vec3 {
|
||||
self.matrix.col(3).truncate()
|
||||
}
|
||||
|
||||
/// Get the world rotation (as a quaternion) from the matrix
|
||||
pub fn rotation(&self) -> glam::Quat {
|
||||
glam::Quat::from_mat4(&self.matrix)
|
||||
}
|
||||
|
||||
/// Get the world scale from the matrix
|
||||
pub fn scale(&self) -> glam::Vec3 {
|
||||
glam::Vec3::new(
|
||||
self.matrix.col(0).truncate().length(),
|
||||
self.matrix.col(1).truncate().length(),
|
||||
self.matrix.col(2).truncate().length(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute world transforms for all entities in the scene
|
||||
///
|
||||
/// This function traverses the transform hierarchy and computes the world-space
|
||||
/// transform matrix for each entity by combining local transforms with their
|
||||
/// parent's world transform.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `world` - The ECS world containing Transform components
|
||||
/// * `entity_map` - Mapping from Unity FileID to ECS Entity
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use unity_parser::UnityProject;
|
||||
/// use unity_parser::post_processing::compute_world_transforms;
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let project = UnityProject::from_path("/path/to/project")?;
|
||||
/// let mut scene = project.parse_scene("scene.unity")?;
|
||||
///
|
||||
/// // Compute world transforms as a post-processing step
|
||||
/// compute_world_transforms(&mut scene.world, &scene.entity_map);
|
||||
///
|
||||
/// // Now you can query WorldTransform components
|
||||
/// let world_transforms = scene.world.borrow::<WorldTransform>();
|
||||
/// # Ok::<(), unity_parser::Error>(())
|
||||
/// ```
|
||||
pub fn compute_world_transforms(world: &mut World, entity_map: &HashMap<FileID, Entity>) {
|
||||
// Find all root transforms (transforms with no parent)
|
||||
let mut root_entities = Vec::new();
|
||||
|
||||
{
|
||||
let transform_view = world.borrow::<Transform>();
|
||||
for &entity in entity_map.values() {
|
||||
if let Some(transform) = transform_view.get(entity) {
|
||||
if transform.parent().is_none() {
|
||||
root_entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively compute world transforms starting from roots
|
||||
for root_entity in root_entities {
|
||||
compute_world_transform_recursive(world, root_entity, Mat4::IDENTITY);
|
||||
}
|
||||
|
||||
// Handle RectTransforms separately (they might have their own hierarchy)
|
||||
let mut rect_root_entities = Vec::new();
|
||||
|
||||
{
|
||||
let rect_transform_view = world.borrow::<RectTransform>();
|
||||
for &entity in entity_map.values() {
|
||||
if let Some(rect_transform) = rect_transform_view.get(entity) {
|
||||
if rect_transform.transform().parent().is_none() {
|
||||
rect_root_entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for root_entity in rect_root_entities {
|
||||
compute_rect_world_transform_recursive(world, root_entity, Mat4::IDENTITY);
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively compute world transform for a Transform entity and its children
|
||||
fn compute_world_transform_recursive(world: &mut World, entity: Entity, parent_matrix: Mat4) {
|
||||
// Get the local transform
|
||||
let local_matrix = {
|
||||
let transform_view = world.borrow::<Transform>();
|
||||
if let Some(transform) = transform_view.get(entity) {
|
||||
compute_local_matrix(
|
||||
transform.local_position(),
|
||||
transform.local_rotation(),
|
||||
transform.local_scale(),
|
||||
)
|
||||
} else {
|
||||
// No transform component, skip this entity
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Compute world matrix = parent * local
|
||||
let world_matrix = parent_matrix * local_matrix;
|
||||
|
||||
// Get children before inserting component
|
||||
let children = {
|
||||
let transform_view = world.borrow::<Transform>();
|
||||
transform_view
|
||||
.get(entity)
|
||||
.map(|t| t.children().to_vec())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
// Insert the WorldTransform component (will auto-register on first insert)
|
||||
world.insert(entity, (WorldTransform::new(world_matrix),));
|
||||
|
||||
// Recursively process children
|
||||
for child in children {
|
||||
compute_world_transform_recursive(world, child, world_matrix);
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively compute world transform for a RectTransform entity and its children
|
||||
fn compute_rect_world_transform_recursive(world: &mut World, entity: Entity, parent_matrix: Mat4) {
|
||||
// Get the local transform from RectTransform
|
||||
let (local_matrix, children) = {
|
||||
let rect_transform_view = world.borrow::<RectTransform>();
|
||||
if let Some(rect_transform) = rect_transform_view.get(entity) {
|
||||
let transform = rect_transform.transform();
|
||||
let local = compute_local_matrix(
|
||||
transform.local_position(),
|
||||
transform.local_rotation(),
|
||||
transform.local_scale(),
|
||||
);
|
||||
let children = transform.children().to_vec();
|
||||
(local, children)
|
||||
} else {
|
||||
// No rect transform component, skip this entity
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Compute world matrix = parent * local
|
||||
let world_matrix = parent_matrix * local_matrix;
|
||||
|
||||
// Insert the WorldTransform component (will auto-register on first insert)
|
||||
world.insert(entity, (WorldTransform::new(world_matrix),));
|
||||
|
||||
// Recursively process children
|
||||
for child in children {
|
||||
// Children might be either Transform or RectTransform
|
||||
// Try RectTransform first, then Transform
|
||||
let has_rect_transform = world.borrow::<RectTransform>().contains(child);
|
||||
if has_rect_transform {
|
||||
compute_rect_world_transform_recursive(world, child, world_matrix);
|
||||
} else {
|
||||
compute_world_transform_recursive(world, child, world_matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a local transformation matrix from position, rotation, and scale
|
||||
fn compute_local_matrix(
|
||||
position: Option<&crate::Vector3>,
|
||||
rotation: Option<&crate::Quaternion>,
|
||||
scale: Option<&crate::Vector3>,
|
||||
) -> Mat4 {
|
||||
// Default values if not present
|
||||
let pos = position
|
||||
.map(|p| glam::Vec3::new(p.x, p.y, p.z))
|
||||
.unwrap_or(glam::Vec3::ZERO);
|
||||
|
||||
let rot = rotation
|
||||
.map(|q| glam::Quat::from_xyzw(q.x, q.y, q.z, q.w))
|
||||
.unwrap_or(glam::Quat::IDENTITY);
|
||||
|
||||
let scl = scale
|
||||
.map(|s| glam::Vec3::new(s.x, s.y, s.z))
|
||||
.unwrap_or(glam::Vec3::ONE);
|
||||
|
||||
// Compute TRS matrix
|
||||
Mat4::from_scale_rotation_translation(scl, rot, pos)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use glam::Vec3;
|
||||
|
||||
#[test]
|
||||
fn test_local_matrix_identity() {
|
||||
let matrix = compute_local_matrix(None, None, None);
|
||||
assert_eq!(matrix, Mat4::IDENTITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_matrix_translation() {
|
||||
let pos = crate::Vector3::new(1.0, 2.0, 3.0);
|
||||
let matrix = compute_local_matrix(Some(&pos), None, None);
|
||||
let expected = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(matrix, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_world_transform_position() {
|
||||
let matrix = Mat4::from_translation(Vec3::new(5.0, 10.0, 15.0));
|
||||
let wt = WorldTransform::new(matrix);
|
||||
let pos = wt.position();
|
||||
assert_eq!(pos, Vec3::new(5.0, 10.0, 15.0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user