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}