rpfm_lib/files/bmd/common/mod.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Common data structures shared across BMD format files.
12//!
13//! This module provides reusable geometric and transformation primitives used throughout
14//! BMD (Battle Map Definition) files and related formats. These structures are public to
15//! allow reuse in other file format modules.
16//!
17//! # Geometric Primitives
18//!
19//! ## Points
20//! - [`Point2d`] - 2D point (x, y)
21//! - [`Point3d`] - 3D point (x, y, z)
22//!
23//! ## Shapes
24//! - [`Rectangle`] - 2D axis-aligned bounding box
25//! - [`Cube`] - 3D axis-aligned bounding box
26//! - [`Outline2d`] - 2D polyline outline
27//! - [`Outline3d`] - 3D polyline outline
28//! - [`Polygon2d`] - 2D polygon with arbitrary vertices
29//!
30//! ## Colors
31//! - [`ColourRGB`] - RGB color (floating-point components)
32//! - [`ColourRGBA`] - RGBA color (8-bit components)
33//!
34//! ## Transformations
35//! - [`Transform3x4`] - 3x4 transformation matrix (rotation + translation)
36//! - [`Transform4x4`] - 4x4 transformation matrix (full affine transform)
37//! - [`Quaternion`] - Rotation quaternion (i, j, k, w)
38//! - [`Matrix`] - Trait for matrix operations and conversions
39//!
40//! # Matrix Trait
41//!
42//! The [`Matrix`] trait provides common operations for transformation matrices:
43//! - Element accessors (`m00()`, `m01()`, etc.)
44//! - Rotation matrix extraction
45//! - Scale extraction and application
46//! - Euler angle conversion
47//! - Identity matrix creation
48//!
49//! # Usage
50//!
51//! ```ignore
52//! use rpfm_lib::files::bmd::common::{Point3d, Transform4x4, Matrix};
53//!
54//! // Create a 3D point
55//! let point = Point3d::new(10.0, 20.0, 30.0);
56//!
57//! // Create an identity transform
58//! let transform = Transform4x4::identity();
59//!
60//! // Extract rotation angles
61//! let rotation_matrix = transform.rotation_matrix();
62//! let (x, y, z) = Transform4x4::rotation_matrix_to_euler_angles(rotation_matrix, true);
63//! ```
64//!
65//! # Submodules
66//!
67//! - [`building_link`] - Building linkage data structures
68//! - [`building_reference`] - Building reference data structures
69//! - [`flags`] - Flag definitions
70//! - [`properties`] - Property data structures
71
72use serde_derive::{Serialize, Deserialize};
73
74use std::ops::Sub;
75
76use crate::binary::{ReadBytes, WriteBytes};
77use crate::error::Result;
78use crate::files::{Decodeable, EncodeableExtraData, Encodeable};
79
80use super::*;
81
82pub mod building_link;
83pub mod building_reference;
84pub mod flags;
85pub mod properties;
86
87//---------------------------------------------------------------------------//
88// Enum & Structs
89//---------------------------------------------------------------------------//
90
91/// RGB color with floating-point components.
92///
93/// Used for lighting and material colors in BMD files. Each component is a 32-bit
94/// floating-point value typically in the range [0.0, 1.0], though values outside
95/// this range are supported for HDR lighting.
96///
97/// # Fields
98///
99/// - `r`: Red component
100/// - `g`: Green component
101/// - `b`: Blue component
102///
103/// # Example
104///
105/// ```ignore
106/// use rpfm_lib::files::bmd::common::ColourRGB;
107///
108/// let mut color = ColourRGB::default();
109/// color.set_r(1.0); // Full red
110/// color.set_g(0.5); // Half green
111/// color.set_b(0.0); // No blue
112/// ```
113#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
114#[getset(get = "pub", get_mut = "pub", set = "pub")]
115pub struct ColourRGB {
116 /// Red component (typically 0.0-1.0).
117 r: f32,
118
119 /// Green component (typically 0.0-1.0).
120 g: f32,
121
122 /// Blue component (typically 0.0-1.0).
123 b: f32,
124}
125
126/// RGBA color with 8-bit components.
127///
128/// Used for color data requiring alpha (transparency) channel. Each component is
129/// an unsigned 8-bit integer in the range [0, 255].
130///
131/// # Fields
132///
133/// - `r`: Red component (0-255)
134/// - `g`: Green component (0-255)
135/// - `b`: Blue component (0-255)
136/// - `a`: Alpha (opacity) component (0-255, where 255 is fully opaque)
137///
138/// # Example
139///
140/// ```ignore
141/// use rpfm_lib::files::bmd::common::ColourRGBA;
142///
143/// let mut color = ColourRGBA::default();
144/// color.set_r(255); // Full red
145/// color.set_g(128); // Half green
146/// color.set_b(0); // No blue
147/// color.set_a(255); // Fully opaque
148/// ```
149#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
150#[getset(get = "pub", get_mut = "pub", set = "pub")]
151pub struct ColourRGBA {
152 /// Red component (0-255).
153 r: u8,
154
155 /// Green component (0-255).
156 g: u8,
157
158 /// Blue component (0-255).
159 b: u8,
160
161 /// Alpha (opacity) component (0-255, where 255 is fully opaque).
162 a: u8,
163}
164
165/// 3D axis-aligned bounding box (AABB).
166///
167/// Represents a rectangular volume aligned with coordinate axes, defined by minimum
168/// and maximum corners. Used for spatial bounds, collision volumes, and culling.
169///
170/// # Fields
171///
172/// - `min_x`, `min_y`, `min_z`: Minimum corner coordinates
173/// - `max_x`, `max_y`, `max_z`: Maximum corner coordinates
174///
175/// # Example
176///
177/// ```ignore
178/// use rpfm_lib::files::bmd::common::Cube;
179///
180/// let mut cube = Cube::default();
181/// cube.set_min_x(-10.0);
182/// cube.set_max_x(10.0);
183/// // Creates a 20x20x20 cube centered at origin
184/// ```
185#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
186#[getset(get = "pub", get_mut = "pub", set = "pub")]
187pub struct Cube {
188 /// Minimum X coordinate.
189 min_x: f32,
190
191 /// Minimum Y coordinate.
192 min_y: f32,
193
194 /// Minimum Z coordinate.
195 min_z: f32,
196
197 /// Maximum X coordinate.
198 max_x: f32,
199
200 /// Maximum Y coordinate.
201 max_y: f32,
202
203 /// Maximum Z coordinate.
204 max_z: f32,
205}
206
207/// 2D polyline outline.
208///
209/// Represents a sequence of connected 2D points forming an open or closed outline.
210/// Used for area boundaries, deployment zones, and other 2D regions.
211///
212/// # Fields
213///
214/// - `outline`: Ordered list of 2D points
215#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
216#[getset(get = "pub", get_mut = "pub", set = "pub")]
217pub struct Outline2d {
218 /// Ordered list of 2D points forming the outline.
219 outline: Vec<Point2d>,
220}
221
222/// 3D polyline outline.
223///
224/// Represents a sequence of connected 3D points forming an open or closed outline.
225/// Used for 3D boundaries, paths, and spatial regions.
226///
227/// # Fields
228///
229/// - `outline`: Ordered list of 3D points
230#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
231#[getset(get = "pub", get_mut = "pub", set = "pub")]
232pub struct Outline3d {
233 /// Ordered list of 3D points forming the outline.
234 outline: Vec<Point3d>,
235}
236
237/// 2D point in Cartesian coordinates.
238///
239/// Represents a position in 2D space. Used for map coordinates, UI positions,
240/// and texture coordinates.
241///
242/// # Fields
243///
244/// - `x`: Horizontal coordinate
245/// - `y`: Vertical coordinate
246#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
247#[getset(get = "pub", get_mut = "pub", set = "pub")]
248pub struct Point2d {
249 /// X (horizontal) coordinate.
250 x: f32,
251
252 /// Y (vertical) coordinate.
253 y: f32,
254}
255
256/// 3D point in Cartesian coordinates.
257///
258/// Represents a position in 3D space.
259///
260/// # Fields
261///
262/// - `x`: X-axis coordinate
263/// - `y`: Y-axis coordinate
264/// - `z`: Z-axis coordinate
265///
266/// # Example
267///
268/// ```ignore
269/// use rpfm_lib::files::bmd::common::Point3d;
270///
271/// let p1 = Point3d::new(10.0, 20.0, 30.0);
272/// let p2 = Point3d::new(5.0, 5.0, 5.0);
273/// let diff = p1 - p2; // Vector from p2 to p1
274/// ```
275#[derive(Default, PartialEq, Copy, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
276#[getset(get = "pub", get_mut = "pub", set = "pub")]
277pub struct Point3d {
278 /// X-axis coordinate.
279 x: f32,
280
281 /// Y-axis coordinate.
282 y: f32,
283
284 /// Z-axis coordinate.
285 z: f32,
286}
287
288/// 2D polygon with arbitrary vertices.
289///
290/// Represents a closed 2D polygon defined by an ordered list of vertices.
291/// Used for complex area definitions and spatial regions.
292///
293/// # Fields
294///
295/// - `points`: Ordered list of polygon vertices
296#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
297#[getset(get = "pub", get_mut = "pub", set = "pub")]
298pub struct Polygon2d {
299 /// Ordered list of polygon vertices.
300 points: Vec<Point2d>
301}
302
303/// Rotation quaternion.
304///
305/// Represents a 3D rotation using quaternion representation (i, j, k, w).
306/// Quaternions provide smooth interpolation and avoid gimbal lock.
307///
308/// # Fields
309///
310/// - `i`, `j`, `k`: Imaginary components
311/// - `w`: Real (scalar) component
312///
313/// # Quaternion Format
314///
315/// Standard quaternion format: `q = w + xi + yj + zk`
316///
317/// # Example
318///
319/// ```ignore
320/// use rpfm_lib::files::bmd::common::Quaternion;
321///
322/// let mut quat = Quaternion::default();
323/// quat.set_w(1.0); // Identity rotation
324/// ```
325#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
326#[getset(get = "pub", get_mut = "pub", set = "pub")]
327pub struct Quaternion {
328 /// Imaginary i component.
329 i: f32,
330
331 /// Imaginary j component.
332 j: f32,
333
334 /// Imaginary k component.
335 k: f32,
336
337 /// Real (scalar) w component.
338 w: f32,
339}
340
341/// 2D axis-aligned rectangle.
342///
343/// Represents a rectangular area aligned with coordinate axes, defined by
344/// minimum and maximum corner coordinates. Used for 2D bounds and regions.
345///
346/// # Fields
347///
348/// - `min_x`, `min_y`: Minimum corner coordinates
349/// - `max_x`, `max_y`: Maximum corner coordinates
350#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
351#[getset(get = "pub", get_mut = "pub", set = "pub")]
352pub struct Rectangle {
353 /// Minimum X coordinate.
354 min_x: f32,
355
356 /// Minimum Y coordinate.
357 min_y: f32,
358
359 /// Maximum X coordinate.
360 max_x: f32,
361
362 /// Maximum Y coordinate.
363 max_y: f32,
364}
365
366/// 3x4 transformation matrix.
367///
368/// Represents a 3D affine transformation with rotation and translation but no
369/// perspective. The matrix is stored in column-major order and contains:
370/// - 3x3 rotation/scale submatrix (top-left)
371/// - 3x1 translation vector (bottom row)
372///
373/// # Matrix Layout
374///
375/// ```text
376/// [ m00 m01 m02 ]
377/// [ m10 m11 m12 ]
378/// [ m20 m21 m22 ]
379/// [ m30 m31 m32 ]
380/// ```
381///
382/// # Note
383///
384/// This struct does not have automatic getters for matrix elements. Use the
385/// [`Matrix`] trait methods (m00(), m01(), etc.) to access elements.
386///
387/// # Example
388///
389/// ```ignore
390/// use rpfm_lib::files::bmd::common::{Transform3x4, Matrix};
391///
392/// let transform = Transform3x4::identity();
393/// let m00 = transform.m00(); // Access via Matrix trait
394/// ```
395#[derive(Default, PartialEq, Clone, Debug, MutGetters, Setters, Serialize, Deserialize)]
396#[getset(get_mut = "pub", set = "pub")]
397pub struct Transform3x4{
398 /// Matrix element at row 0, column 0.
399 m00: f32,
400 /// Matrix element at row 0, column 1.
401 m01: f32,
402 /// Matrix element at row 0, column 2.
403 m02: f32,
404 /// Matrix element at row 1, column 0.
405 m10: f32,
406 /// Matrix element at row 1, column 1.
407 m11: f32,
408 /// Matrix element at row 1, column 2.
409 m12: f32,
410 /// Matrix element at row 2, column 0.
411 m20: f32,
412 /// Matrix element at row 2, column 1.
413 m21: f32,
414 /// Matrix element at row 2, column 2.
415 m22: f32,
416 /// Matrix element at row 3, column 0 (translation X).
417 m30: f32,
418 /// Matrix element at row 3, column 1 (translation Y).
419 m31: f32,
420 /// Matrix element at row 3, column 2 (translation Z).
421 m32: f32,
422}
423
424/// 4x4 transformation matrix.
425///
426/// Represents a full 3D affine transformation including rotation, scale,
427/// translation, and perspective. The matrix is stored in column-major order.
428///
429/// # Matrix Layout
430///
431/// ```text
432/// [ m00 m01 m02 m03 ]
433/// [ m10 m11 m12 m13 ]
434/// [ m20 m21 m22 m23 ]
435/// [ m30 m31 m32 m33 ]
436/// ```
437///
438/// # Note
439///
440/// This struct does not have automatic getters for matrix elements. Use the
441/// [`Matrix`] trait methods (m00(), m01(), etc.) to access elements.
442///
443/// # Conversions
444///
445/// - Can be converted from/to [`Cube`] for bounding box storage
446///
447/// # Example
448///
449/// ```ignore
450/// use rpfm_lib::files::bmd::common::{Transform4x4, Matrix};
451///
452/// let transform = Transform4x4::identity();
453/// let rotation = transform.rotation_matrix();
454/// let (rx, ry, rz) = Transform4x4::rotation_matrix_to_euler_angles(rotation, true);
455/// ```
456#[derive(Default, PartialEq, Clone, Debug, MutGetters, Setters, Serialize, Deserialize)]
457#[getset(get_mut = "pub", set = "pub")]
458pub struct Transform4x4 {
459 /// Matrix element at row 0, column 0.
460 m00: f32,
461 /// Matrix element at row 0, column 1.
462 m01: f32,
463 /// Matrix element at row 0, column 2.
464 m02: f32,
465 /// Matrix element at row 0, column 3.
466 m03: f32,
467 /// Matrix element at row 1, column 0.
468 m10: f32,
469 /// Matrix element at row 1, column 1.
470 m11: f32,
471 /// Matrix element at row 1, column 2.
472 m12: f32,
473 /// Matrix element at row 1, column 3.
474 m13: f32,
475 /// Matrix element at row 2, column 0.
476 m20: f32,
477 /// Matrix element at row 2, column 1.
478 m21: f32,
479 /// Matrix element at row 2, column 2.
480 m22: f32,
481 /// Matrix element at row 2, column 3.
482 m23: f32,
483 /// Matrix element at row 3, column 0 (translation X).
484 m30: f32,
485 /// Matrix element at row 3, column 1 (translation Y).
486 m31: f32,
487 /// Matrix element at row 3, column 2 (translation Z).
488 m32: f32,
489 /// Matrix element at row 3, column 3 (homogeneous coordinate).
490 m33: f32,
491}
492
493//---------------------------------------------------------------------------//
494// Implementations
495//---------------------------------------------------------------------------//
496
497impl Decodeable for ColourRGB {
498
499 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
500 Ok(Self {
501 r: data.read_f32()?,
502 g: data.read_f32()?,
503 b: data.read_f32()?,
504 })
505 }
506}
507
508impl Encodeable for ColourRGB {
509
510 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
511 buffer.write_f32(self.r)?;
512 buffer.write_f32(self.g)?;
513 buffer.write_f32(self.b)?;
514
515 Ok(())
516 }
517}
518
519impl Decodeable for ColourRGBA {
520
521 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
522 Ok(Self {
523 r: data.read_u8()?,
524 g: data.read_u8()?,
525 b: data.read_u8()?,
526 a: data.read_u8()?,
527 })
528 }
529}
530
531impl Encodeable for ColourRGBA {
532
533 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
534 buffer.write_u8(self.r)?;
535 buffer.write_u8(self.g)?;
536 buffer.write_u8(self.b)?;
537 buffer.write_u8(self.a)?;
538
539 Ok(())
540 }
541}
542
543impl Decodeable for Cube {
544
545 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
546 Ok(Self {
547 min_x: data.read_f32()?,
548 min_y: data.read_f32()?,
549 min_z: data.read_f32()?,
550 max_x: data.read_f32()?,
551 max_y: data.read_f32()?,
552 max_z: data.read_f32()?,
553 })
554 }
555}
556
557impl Encodeable for Cube {
558
559 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
560 buffer.write_f32(self.min_x)?;
561 buffer.write_f32(self.min_y)?;
562 buffer.write_f32(self.min_z)?;
563 buffer.write_f32(self.max_x)?;
564 buffer.write_f32(self.max_y)?;
565 buffer.write_f32(self.max_z)?;
566
567 Ok(())
568 }
569}
570
571impl Decodeable for Outline2d {
572
573 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
574 let mut decoded = Self::default();
575
576 for _ in 0..data.read_u32()? {
577 decoded.outline.push(Point2d::decode(data, extra_data)?);
578 }
579
580 Ok(decoded)
581 }
582}
583
584impl Encodeable for Outline2d {
585
586 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
587 buffer.write_u32(self.outline.len() as u32)?;
588
589 for point in &mut self.outline {
590 point.encode(buffer, extra_data)?;
591 }
592
593 Ok(())
594 }
595}
596
597impl Decodeable for Outline3d {
598
599 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
600 let mut decoded = Self::default();
601
602 for _ in 0..data.read_u32()? {
603 decoded.outline.push(Point3d::decode(data, extra_data)?);
604 }
605
606 Ok(decoded)
607 }
608}
609
610impl Encodeable for Outline3d {
611
612 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
613 buffer.write_u32(self.outline.len() as u32)?;
614
615 for point in &mut self.outline {
616 point.encode(buffer, extra_data)?;
617 }
618
619 Ok(())
620 }
621}
622
623impl Decodeable for Point2d {
624
625 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
626 Ok(Self {
627 x: data.read_f32()?,
628 y: data.read_f32()?,
629 })
630 }
631}
632
633impl Encodeable for Point2d {
634
635 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
636 buffer.write_f32(self.x)?;
637 buffer.write_f32(self.y)?;
638
639 Ok(())
640 }
641}
642
643impl Decodeable for Point3d {
644
645 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
646 Ok(Self {
647 x: data.read_f32()?,
648 y: data.read_f32()?,
649 z: data.read_f32()?,
650 })
651 }
652}
653
654impl Encodeable for Point3d {
655
656 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
657 buffer.write_f32(self.x)?;
658 buffer.write_f32(self.y)?;
659 buffer.write_f32(self.z)?;
660
661 Ok(())
662 }
663}
664
665impl Decodeable for Polygon2d {
666
667 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
668 let mut decoded = Self::default();
669
670 for _ in 0..data.read_u32()? {
671 decoded.points.push(Point2d::decode(data, extra_data)?);
672 }
673
674 Ok(decoded)
675 }
676}
677
678impl Encodeable for Polygon2d {
679
680 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
681 buffer.write_u32(self.points.len() as u32)?;
682 for point in &mut self.points {
683 point.encode(buffer, extra_data)?;
684 }
685
686 Ok(())
687 }
688}
689
690impl Decodeable for Quaternion {
691
692 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
693 Ok(Self {
694 i: data.read_f32()?,
695 j: data.read_f32()?,
696 k: data.read_f32()?,
697 w: data.read_f32()?,
698 })
699 }
700}
701
702impl Encodeable for Quaternion {
703
704 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
705 buffer.write_f32(self.i)?;
706 buffer.write_f32(self.j)?;
707 buffer.write_f32(self.k)?;
708 buffer.write_f32(self.w)?;
709
710 Ok(())
711 }
712}
713
714impl Decodeable for Rectangle {
715
716 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
717 Ok(Self {
718 min_x: data.read_f32()?,
719 min_y: data.read_f32()?,
720 max_x: data.read_f32()?,
721 max_y: data.read_f32()?,
722 })
723 }
724}
725
726impl Encodeable for Rectangle {
727
728 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
729 buffer.write_f32(self.min_x)?;
730 buffer.write_f32(self.min_y)?;
731 buffer.write_f32(self.max_x)?;
732 buffer.write_f32(self.max_y)?;
733
734 Ok(())
735 }
736}
737
738impl Decodeable for Transform3x4 {
739
740 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
741 Ok(Self {
742 m00: data.read_f32()?,
743 m01: data.read_f32()?,
744 m02: data.read_f32()?,
745 m10: data.read_f32()?,
746 m11: data.read_f32()?,
747 m12: data.read_f32()?,
748 m20: data.read_f32()?,
749 m21: data.read_f32()?,
750 m22: data.read_f32()?,
751 m30: data.read_f32()?,
752 m31: data.read_f32()?,
753 m32: data.read_f32()?,
754 })
755 }
756}
757
758impl Encodeable for Transform3x4 {
759
760 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
761 buffer.write_f32(self.m00)?;
762 buffer.write_f32(self.m01)?;
763 buffer.write_f32(self.m02)?;
764 buffer.write_f32(self.m10)?;
765 buffer.write_f32(self.m11)?;
766 buffer.write_f32(self.m12)?;
767 buffer.write_f32(self.m20)?;
768 buffer.write_f32(self.m21)?;
769 buffer.write_f32(self.m22)?;
770 buffer.write_f32(self.m30)?;
771 buffer.write_f32(self.m31)?;
772 buffer.write_f32(self.m32)?;
773
774 Ok(())
775 }
776}
777
778impl Decodeable for Transform4x4 {
779
780 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
781 Ok(Self {
782 m00: data.read_f32()?,
783 m01: data.read_f32()?,
784 m02: data.read_f32()?,
785 m03: data.read_f32()?,
786 m10: data.read_f32()?,
787 m11: data.read_f32()?,
788 m12: data.read_f32()?,
789 m13: data.read_f32()?,
790 m20: data.read_f32()?,
791 m21: data.read_f32()?,
792 m22: data.read_f32()?,
793 m23: data.read_f32()?,
794 m30: data.read_f32()?,
795 m31: data.read_f32()?,
796 m32: data.read_f32()?,
797 m33: data.read_f32()?,
798 })
799 }
800}
801
802impl Encodeable for Transform4x4 {
803
804 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
805 buffer.write_f32(self.m00)?;
806 buffer.write_f32(self.m01)?;
807 buffer.write_f32(self.m02)?;
808 buffer.write_f32(self.m03)?;
809 buffer.write_f32(self.m10)?;
810 buffer.write_f32(self.m11)?;
811 buffer.write_f32(self.m12)?;
812 buffer.write_f32(self.m13)?;
813 buffer.write_f32(self.m20)?;
814 buffer.write_f32(self.m21)?;
815 buffer.write_f32(self.m22)?;
816 buffer.write_f32(self.m23)?;
817 buffer.write_f32(self.m30)?;
818 buffer.write_f32(self.m31)?;
819 buffer.write_f32(self.m32)?;
820 buffer.write_f32(self.m33)?;
821
822 Ok(())
823 }
824}
825
826/// Common operations for transformation matrices.
827///
828/// This trait abstracts behavior shared between [`Transform3x4`] and [`Transform4x4`],
829/// providing matrix element access and transformation utilities.
830///
831/// # Provided Methods
832///
833/// - **Element Access**: m00() through m33() - Access individual matrix elements
834/// - **Rotation**: `rotation_matrix()` - Extract 3x3 rotation submatrix
835/// - **Scaling**: `extract_scales()`, `apply_scales()`, `normalize_rotation_matrix()`
836/// - **Euler Angles**: `rotation_matrix_to_euler_angles()`, `euler_angles_to_rotation_matrix()`
837/// - **Identity**: `identity()` - Create identity transform
838///
839/// # Rotation Order
840///
841/// Euler angle conversions use 'xyz' extrinsic rotation order (roll-pitch-yaw).
842///
843/// # Example
844///
845/// ```ignore
846/// use rpfm_lib::files::bmd::common::{Transform4x4, Matrix};
847///
848/// let transform = Transform4x4::identity();
849///
850/// // Extract rotation
851/// let rotation = transform.rotation_matrix();
852/// let scales = Transform4x4::extract_scales(rotation);
853///
854/// // Convert to Euler angles (in degrees)
855/// let (x, y, z) = Transform4x4::rotation_matrix_to_euler_angles(rotation, true);
856/// println!("Rotation: X={}, Y={}, Z={}", x, y, z);
857/// ```
858pub trait Matrix {
859 /// Returns matrix element at row 0, column 0.
860 fn m00(&self) -> f32;
861 /// Returns matrix element at row 0, column 1.
862 fn m01(&self) -> f32;
863 /// Returns matrix element at row 0, column 2.
864 fn m02(&self) -> f32;
865 /// Returns matrix element at row 0, column 3 (0.0 for 3x4 matrices).
866 fn m03(&self) -> f32;
867 /// Returns matrix element at row 1, column 0.
868 fn m10(&self) -> f32;
869 /// Returns matrix element at row 1, column 1.
870 fn m11(&self) -> f32;
871 /// Returns matrix element at row 1, column 2.
872 fn m12(&self) -> f32;
873 /// Returns matrix element at row 1, column 3 (0.0 for 3x4 matrices).
874 fn m13(&self) -> f32;
875 /// Returns matrix element at row 2, column 0.
876 fn m20(&self) -> f32;
877 /// Returns matrix element at row 2, column 1.
878 fn m21(&self) -> f32;
879 /// Returns matrix element at row 2, column 2.
880 fn m22(&self) -> f32;
881 /// Returns matrix element at row 2, column 3 (0.0 for 3x4 matrices).
882 fn m23(&self) -> f32;
883 /// Returns matrix element at row 3, column 0 (translation X).
884 fn m30(&self) -> f32;
885 /// Returns matrix element at row 3, column 1 (translation Y).
886 fn m31(&self) -> f32;
887 /// Returns matrix element at row 3, column 2 (translation Z).
888 fn m32(&self) -> f32;
889 /// Returns matrix element at row 3, column 3 (1.0 for 3x4 matrices).
890 fn m33(&self) -> f32;
891
892 /// Extracts the 3x3 rotation submatrix.
893 ///
894 /// Converts from CA's column-major serialization to standard row-major
895 /// rotation matrix representation.
896 ///
897 /// # Returns
898 ///
899 /// 3x3 rotation matrix as nalgebra `Matrix3<f64>`.
900 ///
901 /// # Reference
902 ///
903 /// See: <https://developer.unigine.com/forum/uploads/monthly_2020_05/image.png.674c8b961433f2a7a62c54bc55cb599c.png>
904 fn rotation_matrix(&self) -> Matrix3<f64> {
905
906 // Fix order of the elements here
907 Matrix3::new(
908 self.m00() as f64, self.m10() as f64, self.m20() as f64,
909 self.m01() as f64, self.m11() as f64, self.m21() as f64,
910 self.m02() as f64, self.m12() as f64, self.m22() as f64
911 )
912 }
913
914 /// Extracts scale factors from a rotation matrix.
915 ///
916 /// Computes the scale of each axis by taking the norm of each column vector.
917 ///
918 /// # Parameters
919 ///
920 /// - `matrix`: 3x3 rotation/scale matrix
921 ///
922 /// # Returns
923 ///
924 /// Tuple of (scale_x, scale_y, scale_z)
925 ///
926 /// # Note
927 ///
928 /// **Does not support negative scales.** Negative scales will be treated as positive.
929 ///
930 /// # Reference
931 ///
932 /// See: <https://math.stackexchange.com/a/1463487>
933 fn extract_scales(matrix: Matrix3<f64>) -> (f64, f64, f64) {
934 let scale = (
935 matrix.column(0).norm(),
936 matrix.column(1).norm(),
937 matrix.column(2).norm()
938 );
939 scale
940 }
941
942 /// Applies scale factors to a rotation matrix.
943 ///
944 /// Scales each column of the matrix by the corresponding scale factor.
945 ///
946 /// # Parameters
947 ///
948 /// - `matrix`: 3x3 rotation matrix (should be normalized)
949 /// - `scales`: Tuple of (scale_x, scale_y, scale_z)
950 ///
951 /// # Returns
952 ///
953 /// Scaled rotation matrix
954 fn apply_scales(matrix: Matrix3<f64>, scales: (f64, f64, f64)) -> Matrix3<f64> {
955 Matrix3::new(
956 matrix[(0, 0)] * scales.0, matrix[(0, 1)] * scales.1, matrix[(0, 2)] * scales.2,
957 matrix[(1, 0)] * scales.0, matrix[(1, 1)] * scales.1, matrix[(1, 2)] * scales.2,
958 matrix[(2, 0)] * scales.0, matrix[(2, 1)] * scales.1, matrix[(2, 2)] * scales.2,
959 )
960 }
961
962 /// Normalizes a rotation matrix by removing scale factors.
963 ///
964 /// Divides each column by the corresponding scale factor to produce a pure
965 /// rotation matrix.
966 ///
967 /// # Parameters
968 ///
969 /// - `matrix`: 3x3 rotation/scale matrix
970 /// - `scales`: Tuple of (scale_x, scale_y, scale_z) to remove
971 ///
972 /// # Returns
973 ///
974 /// Normalized rotation matrix (orthonormal)
975 fn normalize_rotation_matrix(matrix: Matrix3<f64>, scales: (f64, f64, f64)) -> Matrix3<f64> {
976 Matrix3::new(
977 matrix[(0, 0)] / scales.0, matrix[(0, 1)] / scales.1, matrix[(0, 2)] / scales.2,
978 matrix[(1, 0)] / scales.0, matrix[(1, 1)] / scales.1, matrix[(1, 2)] / scales.2,
979 matrix[(2, 0)] / scales.0, matrix[(2, 1)] / scales.1, matrix[(2, 2)] / scales.2,
980 )
981 }
982
983 /// Converts a rotation matrix to Euler angles.
984 ///
985 /// Uses 'xyz' extrinsic rotation order (roll-pitch-yaw).
986 ///
987 /// # Parameters
988 ///
989 /// - `matrix`: 3x3 rotation matrix
990 /// - `degrees`: If true, return angles in degrees; if false, in radians
991 ///
992 /// # Returns
993 ///
994 /// Tuple of (x_rotation, y_rotation, z_rotation) in specified units
995 ///
996 /// # Example (Python equivalent using scipy)
997 ///
998 /// ```python
999 /// from scipy.spatial.transform import Rotation as R
1000 /// r = R.from_euler("xyz", [-130.0, 80.0, -30.0], degrees=True)
1001 /// m = r.as_matrix()
1002 /// r = R.from_matrix(m)
1003 /// angles = r.as_euler("xyz", degrees=True)
1004 /// ```
1005 fn rotation_matrix_to_euler_angles(matrix: Matrix3<f64>, degrees: bool) -> (f64, f64, f64) {
1006 let rotation = Rotation3::from_matrix_unchecked(matrix);
1007 let euler = rotation.euler_angles();
1008 if degrees {
1009 (
1010 euler.0.to_degrees(),
1011 euler.1.to_degrees(),
1012 euler.2.to_degrees(),
1013 )
1014 } else {
1015 (euler.0, euler.1, euler.2)
1016 }
1017 }
1018
1019 /// Converts Euler angles to a rotation matrix.
1020 ///
1021 /// Uses 'xyz' extrinsic rotation order (roll-pitch-yaw).
1022 ///
1023 /// # Parameters
1024 ///
1025 /// - `angles`: Tuple of (x_rotation, y_rotation, z_rotation)
1026 /// - `degrees`: If true, angles are in degrees; if false, in radians
1027 ///
1028 /// # Returns
1029 ///
1030 /// 3x3 rotation matrix with values near zero cleaned up (< 1e-5 set to 0.0)
1031 fn euler_angles_to_rotation_matrix(angles: (f64, f64, f64), degrees: bool) -> Matrix3<f64> {
1032 let _angles = if degrees {
1033 (
1034 angles.0.to_radians(),
1035 angles.1.to_radians(),
1036 angles.2.to_radians(),
1037 )
1038 } else {
1039 angles
1040 };
1041 let rotation = Rotation3::from_euler_angles(_angles.0, _angles.1, _angles.2);
1042 let mut matrix : Matrix3<f64> = rotation.into();
1043
1044 // Clean up near-zero values for prettier output
1045 matrix.iter_mut().for_each(|element| {
1046 if element.abs() < 1e-5 {
1047 *element = 0.0;
1048 }
1049 });
1050 matrix
1051 }
1052
1053 /// Creates an identity transformation matrix.
1054 ///
1055 /// # Returns
1056 ///
1057 /// Identity matrix (no rotation, no translation, unit scale)
1058 fn identity() -> Self;
1059}
1060
1061impl Matrix for Transform3x4 {
1062 fn m00(&self) -> f32 {
1063 self.m00
1064 }
1065 fn m01(&self) -> f32 {
1066 self.m01
1067 }
1068 fn m02(&self) -> f32 {
1069 self.m02
1070 }
1071 fn m03(&self) -> f32 {
1072 0.0
1073 }
1074 fn m10(&self) -> f32 {
1075 self.m10
1076 }
1077 fn m11(&self) -> f32 {
1078 self.m11
1079 }
1080 fn m12(&self) -> f32 {
1081 self.m12
1082 }
1083 fn m13(&self) -> f32 {
1084 0.0
1085 }
1086 fn m20(&self) -> f32 {
1087 self.m20
1088 }
1089 fn m21(&self) -> f32 {
1090 self.m21
1091 }
1092 fn m22(&self) -> f32 {
1093 self.m22
1094 }
1095 fn m23(&self) -> f32 {
1096 0.0
1097 }
1098 fn m30(&self) -> f32 {
1099 self.m30
1100 }
1101 fn m31(&self) -> f32 {
1102 self.m31
1103 }
1104 fn m32(&self) -> f32 {
1105 self.m32
1106 }
1107 fn m33(&self) -> f32 {
1108 1.0
1109 }
1110
1111 fn identity() -> Self {
1112 Self {
1113 m00: 1.0,
1114 m01: 0.0,
1115 m02: 0.0,
1116 m10: 0.0,
1117 m11: 1.0,
1118 m12: 0.0,
1119 m20: 0.0,
1120 m21: 0.0,
1121 m22: 1.0,
1122 m30: 0.0,
1123 m31: 0.0,
1124 m32: 0.0,
1125 }
1126 }
1127}
1128
1129impl Matrix for Transform4x4 {
1130 fn m00(&self) -> f32 {
1131 self.m00
1132 }
1133 fn m01(&self) -> f32 {
1134 self.m01
1135 }
1136 fn m02(&self) -> f32 {
1137 self.m02
1138 }
1139 fn m03(&self) -> f32 {
1140 self.m03
1141 }
1142 fn m10(&self) -> f32 {
1143 self.m10
1144 }
1145 fn m11(&self) -> f32 {
1146 self.m11
1147 }
1148 fn m12(&self) -> f32 {
1149 self.m12
1150 }
1151 fn m13(&self) -> f32 {
1152 self.m13
1153 }
1154 fn m20(&self) -> f32 {
1155 self.m20
1156 }
1157 fn m21(&self) -> f32 {
1158 self.m21
1159 }
1160 fn m22(&self) -> f32 {
1161 self.m22
1162 }
1163 fn m23(&self) -> f32 {
1164 self.m23
1165 }
1166 fn m30(&self) -> f32 {
1167 self.m30
1168 }
1169 fn m31(&self) -> f32 {
1170 self.m31
1171 }
1172 fn m32(&self) -> f32 {
1173 self.m32
1174 }
1175 fn m33(&self) -> f32 {
1176 self.m33
1177 }
1178
1179 fn identity() -> Self {
1180 Self {
1181 m00: 1.0,
1182 m01: 0.0,
1183 m02: 0.0,
1184 m03: 0.0,
1185 m10: 0.0,
1186 m11: 1.0,
1187 m12: 0.0,
1188 m13: 0.0,
1189 m20: 0.0,
1190 m21: 0.0,
1191 m22: 1.0,
1192 m23: 0.0,
1193 m30: 0.0,
1194 m31: 0.0,
1195 m32: 0.0,
1196 m33: 1.0,
1197 }
1198 }
1199}
1200
1201impl Point3d {
1202 /// Creates a new 3D point with the specified coordinates.
1203 ///
1204 /// # Parameters
1205 ///
1206 /// - `x`: X-axis coordinate
1207 /// - `y`: Y-axis coordinate
1208 /// - `z`: Z-axis coordinate
1209 ///
1210 /// # Returns
1211 ///
1212 /// New `Point3d` instance
1213 ///
1214 /// # Example
1215 ///
1216 /// ```ignore
1217 /// use rpfm_lib::files::bmd::common::Point3d;
1218 ///
1219 /// let point = Point3d::new(10.0, 20.0, 30.0);
1220 /// assert_eq!(*point.x(), 10.0);
1221 /// ```
1222 pub fn new(x: f32, y: f32, z: f32) -> Self {
1223 Self { x, y, z }
1224 }
1225}
1226
1227impl Sub for Point3d {
1228 type Output = Self;
1229
1230 /// Subtracts two 3D points to produce a vector.
1231 ///
1232 /// # Example
1233 ///
1234 /// ```ignore
1235 /// use rpfm_lib::files::bmd::common::Point3d;
1236 ///
1237 /// let p1 = Point3d::new(10.0, 20.0, 30.0);
1238 /// let p2 = Point3d::new(5.0, 10.0, 15.0);
1239 /// let diff = p1 - p2; // Results in (5.0, 10.0, 15.0)
1240 /// ```
1241 fn sub(self, rhs: Self) -> Self::Output {
1242 Self {
1243 x: self.x - rhs.x,
1244 y: self.y - rhs.y,
1245 z: self.z - rhs.z,
1246 }
1247 }
1248}
1249
1250impl From<Cube> for Transform4x4 {
1251 fn from(value: Cube) -> Self {
1252 Self {
1253 m00: value.min_x,
1254 m01: value.min_y,
1255 m02: value.min_z,
1256 m10: value.max_x,
1257 m11: value.max_y,
1258 m12: value.max_z,
1259 ..Default::default()
1260 }
1261 }
1262}
1263
1264impl From<Transform4x4> for Cube {
1265 fn from(value: Transform4x4) -> Self {
1266 Self {
1267 min_x: value.m00,
1268 min_y: value.m01,
1269 min_z: value.m02,
1270 max_x: value.m10,
1271 max_y: value.m11,
1272 max_z: value.m12
1273 }
1274 }
1275}