wow_m2/
coordinate.rs

1//! Coordinate system transformations for World of Warcraft models
2//!
3//! This module provides utilities for transforming WoW's coordinate system to common
4//! 3D application coordinate systems like Blender, Unity, and Unreal Engine.
5//!
6//! # WoW Coordinate System
7//!
8//! World of Warcraft uses a right-handed coordinate system:
9//! - X-axis: North (positive X = north)
10//! - Y-axis: West (positive Y = west)
11//! - Z-axis: Up (positive Z = up, 0 = sea level)
12//!
13//! # Examples
14//!
15//! ## Basic Coordinate Transformation
16//!
17//! ```rust
18//! use wow_m2::coordinate::{CoordinateSystem, transform_position, transform_quaternion};
19//! use wow_m2::common::{C3Vector, Quaternion};
20//!
21//! // Transform a position from WoW to Blender coordinates
22//! let wow_pos = C3Vector { x: 100.0, y: 200.0, z: 50.0 };
23//! let blender_pos = transform_position(wow_pos, CoordinateSystem::Blender);
24//!
25//! // Transform a rotation from WoW to Unity coordinates
26//! let wow_rot = Quaternion { x: 0.0, y: 0.707, z: 0.0, w: 0.707 };
27//! let unity_rot = transform_quaternion(wow_rot, CoordinateSystem::Unity);
28//! ```
29//!
30//! ## Batch Transformations
31//!
32//! ```rust,no_run
33//! use wow_m2::coordinate::{CoordinateTransformer, CoordinateSystem};
34//!
35//! let transformer = CoordinateTransformer::new(CoordinateSystem::Blender);
36//!
37//! // Transform vertices from a model
38//! # let model_vertices = Vec::new(); // This would come from a loaded model
39//! let transformed_vertices = transformer.transform_positions(&model_vertices);
40//! ```
41
42use crate::common::{C2Vector, C3Vector, Quaternion};
43use glam::Vec3;
44
45/// Target coordinate systems for transformation
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum CoordinateSystem {
48    /// Blender coordinate system (right-handed): Right=X+, Forward=Y+, Up=Z+
49    Blender,
50    /// Unity coordinate system (left-handed): Right=X+, Up=Y+, Forward=Z+
51    Unity,
52    /// Unreal Engine coordinate system (left-handed): Forward=X+, Right=Y+, Up=Z+
53    UnrealEngine,
54}
55
56/// Transform a 3D position from WoW coordinates to the target coordinate system
57///
58/// # Examples
59///
60/// ```rust
61/// use wow_m2::coordinate::{CoordinateSystem, transform_position};
62/// use wow_m2::common::C3Vector;
63///
64/// let wow_pos = C3Vector { x: 100.0, y: 200.0, z: 50.0 }; // 100N, 200W, 50Up
65/// let blender_pos = transform_position(wow_pos, CoordinateSystem::Blender);
66///
67/// // In Blender: Y=forward, X=right, Z=up
68/// // So WoW (100N, 200W, 50Up) becomes Blender (200Forward, -100Right, 50Up)
69/// assert_eq!(blender_pos.x, 200.0); // Y becomes X (forward becomes right... wait, this is wrong)
70/// ```
71pub fn transform_position(wow_pos: C3Vector, target: CoordinateSystem) -> C3Vector {
72    match target {
73        CoordinateSystem::Blender => C3Vector {
74            x: wow_pos.y,  // WoW Y (west) → Blender X (right)
75            y: -wow_pos.x, // WoW X (north) → Blender -Y (backward)
76            z: wow_pos.z,  // WoW Z (up) → Blender Z (up)
77        },
78        CoordinateSystem::Unity => C3Vector {
79            x: -wow_pos.y, // WoW Y (west) → Unity -X (left)
80            y: wow_pos.z,  // WoW Z (up) → Unity Y (up)
81            z: wow_pos.x,  // WoW X (north) → Unity Z (forward)
82        },
83        CoordinateSystem::UnrealEngine => C3Vector {
84            x: wow_pos.x,  // WoW X (north) → Unreal X (forward)
85            y: -wow_pos.y, // WoW Y (west) → Unreal -Y (left)
86            z: wow_pos.z,  // WoW Z (up) → Unreal Z (up)
87        },
88    }
89}
90
91/// Transform a quaternion rotation from WoW coordinates to the target coordinate system
92///
93/// # Examples
94///
95/// ```rust
96/// use wow_m2::coordinate::{CoordinateSystem, transform_quaternion};
97/// use wow_m2::common::Quaternion;
98///
99/// let wow_rot = Quaternion { x: 0.0, y: 0.707, z: 0.0, w: 0.707 };
100/// let blender_rot = transform_quaternion(wow_rot, CoordinateSystem::Blender);
101/// ```
102pub fn transform_quaternion(wow_quat: Quaternion, target: CoordinateSystem) -> Quaternion {
103    match target {
104        CoordinateSystem::Blender => Quaternion {
105            x: wow_quat.y,  // WoW Y → Blender X
106            y: -wow_quat.x, // WoW X → Blender -Y
107            z: wow_quat.z,  // WoW Z → Blender Z
108            w: wow_quat.w,  // W component unchanged
109        },
110        CoordinateSystem::Unity => Quaternion {
111            x: wow_quat.y,  // WoW Y → Unity X
112            y: -wow_quat.z, // WoW Z → Unity -Y
113            z: -wow_quat.x, // WoW X → Unity -Z
114            w: wow_quat.w,  // W component unchanged
115        },
116        CoordinateSystem::UnrealEngine => Quaternion {
117            x: -wow_quat.x, // WoW X → Unreal -X
118            y: wow_quat.y,  // WoW Y → Unreal Y
119            z: -wow_quat.z, // WoW Z → Unreal -Z
120            w: wow_quat.w,  // W component unchanged
121        },
122    }
123}
124
125/// Transform a 2D vector (typically texture coordinates)
126///
127/// Note: In most cases, texture coordinates don't need coordinate system transformation
128/// since they represent UV mapping rather than 3D spatial coordinates.
129pub fn transform_vector2(wow_vec: C2Vector, _target: CoordinateSystem) -> C2Vector {
130    // Texture coordinates typically don't need transformation
131    wow_vec
132}
133
134/// A coordinate transformer that can efficiently transform multiple related coordinates
135/// while maintaining consistency across a model or scene.
136pub struct CoordinateTransformer {
137    target: CoordinateSystem,
138}
139
140impl CoordinateTransformer {
141    /// Create a new coordinate transformer for the target system
142    pub fn new(target: CoordinateSystem) -> Self {
143        Self { target }
144    }
145
146    /// Transform a single position
147    pub fn transform_position(&self, wow_pos: C3Vector) -> C3Vector {
148        transform_position(wow_pos, self.target)
149    }
150
151    /// Transform a single quaternion
152    pub fn transform_quaternion(&self, wow_quat: Quaternion) -> Quaternion {
153        transform_quaternion(wow_quat, self.target)
154    }
155
156    /// Transform multiple positions efficiently
157    pub fn transform_positions(&self, positions: &[C3Vector]) -> Vec<C3Vector> {
158        positions
159            .iter()
160            .map(|&pos| self.transform_position(pos))
161            .collect()
162    }
163
164    /// Transform multiple quaternions efficiently
165    pub fn transform_quaternions(&self, quaternions: &[Quaternion]) -> Vec<Quaternion> {
166        quaternions
167            .iter()
168            .map(|&quat| self.transform_quaternion(quat))
169            .collect()
170    }
171
172    /// Transform positions using SIMD operations for better performance with large datasets
173    pub fn transform_positions_simd(&self, positions: &[C3Vector]) -> Vec<C3Vector> {
174        positions
175            .iter()
176            .map(|pos| {
177                let glam_pos = pos.to_glam();
178                let transformed = self.transform_glam_position(glam_pos);
179                C3Vector::from_glam(transformed)
180            })
181            .collect()
182    }
183
184    /// Transform a glam Vec3 directly (internal helper for SIMD operations)
185    fn transform_glam_position(&self, wow_pos: Vec3) -> Vec3 {
186        match self.target {
187            CoordinateSystem::Blender => Vec3::new(wow_pos.y, -wow_pos.x, wow_pos.z),
188            CoordinateSystem::Unity => Vec3::new(-wow_pos.y, wow_pos.z, wow_pos.x),
189            CoordinateSystem::UnrealEngine => Vec3::new(wow_pos.x, -wow_pos.y, wow_pos.z),
190        }
191    }
192}
193
194/// Utility functions for working with transformation matrices
195pub mod matrix {
196    use super::CoordinateSystem;
197    use glam::{Mat4, Vec3};
198
199    /// Get the transformation matrix for converting from WoW coordinates to the target system
200    pub fn get_transform_matrix(target: CoordinateSystem) -> Mat4 {
201        match target {
202            CoordinateSystem::Blender => Mat4::from_cols(
203                Vec3::new(0.0, 1.0, 0.0).extend(0.0),  // X column: Y → X
204                Vec3::new(-1.0, 0.0, 0.0).extend(0.0), // Y column: -X → Y
205                Vec3::new(0.0, 0.0, 1.0).extend(0.0),  // Z column: Z → Z
206                Vec3::new(0.0, 0.0, 0.0).extend(1.0),  // W column
207            ),
208            CoordinateSystem::Unity => Mat4::from_cols(
209                Vec3::new(0.0, -1.0, 0.0).extend(0.0), // X column: -Y → X
210                Vec3::new(0.0, 0.0, 1.0).extend(0.0),  // Y column: Z → Y
211                Vec3::new(1.0, 0.0, 0.0).extend(0.0),  // Z column: X → Z
212                Vec3::new(0.0, 0.0, 0.0).extend(1.0),  // W column
213            ),
214            CoordinateSystem::UnrealEngine => Mat4::from_cols(
215                Vec3::new(1.0, 0.0, 0.0).extend(0.0),  // X column: X → X
216                Vec3::new(0.0, -1.0, 0.0).extend(0.0), // Y column: -Y → Y
217                Vec3::new(0.0, 0.0, 1.0).extend(0.0),  // Z column: Z → Z
218                Vec3::new(0.0, 0.0, 0.0).extend(1.0),  // W column
219            ),
220        }
221    }
222
223    /// Transform a 4x4 transformation matrix from WoW coordinate system to target
224    pub fn transform_matrix(wow_matrix: Mat4, target: CoordinateSystem) -> Mat4 {
225        let transform = get_transform_matrix(target);
226        let inverse_transform = transform.transpose(); // For orthogonal matrices, transpose = inverse
227
228        // Apply transformation: T * M * T^-1
229        transform * wow_matrix * inverse_transform
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_blender_position_transform() {
239        let wow_pos = C3Vector {
240            x: 100.0,
241            y: 200.0,
242            z: 50.0,
243        };
244        let blender_pos = transform_position(wow_pos, CoordinateSystem::Blender);
245
246        // WoW (100N, 200W, 50Up) → Blender (200Right, -100Backward, 50Up)
247        assert_eq!(blender_pos.x, 200.0);
248        assert_eq!(blender_pos.y, -100.0);
249        assert_eq!(blender_pos.z, 50.0);
250    }
251
252    #[test]
253    fn test_unity_position_transform() {
254        let wow_pos = C3Vector {
255            x: 100.0,
256            y: 200.0,
257            z: 50.0,
258        };
259        let unity_pos = transform_position(wow_pos, CoordinateSystem::Unity);
260
261        // WoW (100N, 200W, 50Up) → Unity (-200Left, 50Up, 100Forward)
262        assert_eq!(unity_pos.x, -200.0);
263        assert_eq!(unity_pos.y, 50.0);
264        assert_eq!(unity_pos.z, 100.0);
265    }
266
267    #[test]
268    fn test_unreal_position_transform() {
269        let wow_pos = C3Vector {
270            x: 100.0,
271            y: 200.0,
272            z: 50.0,
273        };
274        let unreal_pos = transform_position(wow_pos, CoordinateSystem::UnrealEngine);
275
276        // WoW (100N, 200W, 50Up) → Unreal (100Forward, -200Left, 50Up)
277        assert_eq!(unreal_pos.x, 100.0);
278        assert_eq!(unreal_pos.y, -200.0);
279        assert_eq!(unreal_pos.z, 50.0);
280    }
281
282    #[test]
283    fn test_identity_quaternion_transforms() {
284        let identity = Quaternion {
285            x: 0.0,
286            y: 0.0,
287            z: 0.0,
288            w: 1.0,
289        };
290
291        let blender_quat = transform_quaternion(identity, CoordinateSystem::Blender);
292        let unity_quat = transform_quaternion(identity, CoordinateSystem::Unity);
293        let unreal_quat = transform_quaternion(identity, CoordinateSystem::UnrealEngine);
294
295        // Identity quaternion should remain identity in all systems
296        assert_eq!(blender_quat, identity);
297        assert_eq!(unity_quat, identity);
298        assert_eq!(unreal_quat, identity);
299    }
300
301    #[test]
302    fn test_coordinate_transformer() {
303        let transformer = CoordinateTransformer::new(CoordinateSystem::Blender);
304
305        let positions = vec![
306            C3Vector {
307                x: 0.0,
308                y: 0.0,
309                z: 0.0,
310            },
311            C3Vector {
312                x: 100.0,
313                y: 200.0,
314                z: 50.0,
315            },
316        ];
317
318        let transformed = transformer.transform_positions(&positions);
319
320        assert_eq!(
321            transformed[0],
322            C3Vector {
323                x: 0.0,
324                y: 0.0,
325                z: 0.0
326            }
327        );
328        assert_eq!(
329            transformed[1],
330            C3Vector {
331                x: 200.0,
332                y: -100.0,
333                z: 50.0
334            }
335        );
336    }
337
338    #[test]
339    fn test_simd_transform_consistency() {
340        let transformer = CoordinateTransformer::new(CoordinateSystem::Blender);
341
342        let positions = vec![
343            C3Vector {
344                x: 100.0,
345                y: 200.0,
346                z: 50.0,
347            },
348            C3Vector {
349                x: -50.0,
350                y: 75.0,
351                z: 25.0,
352            },
353        ];
354
355        let regular_transform = transformer.transform_positions(&positions);
356        let simd_transform = transformer.transform_positions_simd(&positions);
357
358        // Results should be identical
359        for (regular, simd) in regular_transform.iter().zip(simd_transform.iter()) {
360            assert!((regular.x - simd.x).abs() < f32::EPSILON);
361            assert!((regular.y - simd.y).abs() < f32::EPSILON);
362            assert!((regular.z - simd.z).abs() < f32::EPSILON);
363        }
364    }
365
366    #[test]
367    fn test_transform_matrix_construction() {
368        use crate::coordinate::matrix::get_transform_matrix;
369
370        let blender_matrix = get_transform_matrix(CoordinateSystem::Blender);
371        let unity_matrix = get_transform_matrix(CoordinateSystem::Unity);
372        let unreal_matrix = get_transform_matrix(CoordinateSystem::UnrealEngine);
373
374        // Matrices should be proper transformation matrices (determinant ≠ 0)
375        assert!(blender_matrix.determinant().abs() > f32::EPSILON);
376        assert!(unity_matrix.determinant().abs() > f32::EPSILON);
377        assert!(unreal_matrix.determinant().abs() > f32::EPSILON);
378    }
379
380    #[test]
381    fn test_cardinal_directions() {
382        // Test that cardinal directions transform correctly
383
384        // North in WoW
385        let north = C3Vector {
386            x: 1.0,
387            y: 0.0,
388            z: 0.0,
389        };
390        let blender_north = transform_position(north, CoordinateSystem::Blender);
391        assert_eq!(
392            blender_north,
393            C3Vector {
394                x: 0.0,
395                y: -1.0,
396                z: 0.0
397            }
398        ); // Backward in Blender
399
400        // West in WoW
401        let west = C3Vector {
402            x: 0.0,
403            y: 1.0,
404            z: 0.0,
405        };
406        let blender_west = transform_position(west, CoordinateSystem::Blender);
407        assert_eq!(
408            blender_west,
409            C3Vector {
410                x: 1.0,
411                y: 0.0,
412                z: 0.0
413            }
414        ); // Right in Blender
415
416        // Up in WoW
417        let up = C3Vector {
418            x: 0.0,
419            y: 0.0,
420            z: 1.0,
421        };
422        let blender_up = transform_position(up, CoordinateSystem::Blender);
423        assert_eq!(
424            blender_up,
425            C3Vector {
426                x: 0.0,
427                y: 0.0,
428                z: 1.0
429            }
430        ); // Up in Blender (unchanged)
431    }
432}