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