world positions

This commit is contained in:
2026-01-06 03:52:58 +00:00
parent 0f2cb68fa4
commit 768188134a
5 changed files with 300 additions and 36 deletions

View File

@@ -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)?;
}

View 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;
}

View File

@@ -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,

View 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};

View 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));
}
}