Skip to main content

cadmpeg_ir/
math.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Small geometric value types shared by geometry and topology.
3//!
4//! These are plain data carriers (no invariants enforced at construction); the
5//! validation pass is responsible for sanity checks such as "a direction is
6//! non-degenerate" where the IR is expected to hold geometry.
7
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11/// A point in 3D model space, in the document's length unit.
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
13pub struct Point3 {
14    /// X coordinate.
15    pub x: f64,
16    /// Y coordinate.
17    pub y: f64,
18    /// Z coordinate.
19    pub z: f64,
20}
21
22impl Point3 {
23    /// Construct a point.
24    pub fn new(x: f64, y: f64, z: f64) -> Self {
25        Point3 { x, y, z }
26    }
27
28    /// Euclidean distance to another point.
29    pub fn distance(self, other: Point3) -> f64 {
30        self.distance_squared(other).sqrt()
31    }
32
33    /// Squared Euclidean distance to another point (no square root).
34    pub fn distance_squared(self, other: Point3) -> f64 {
35        (self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)
36    }
37
38    /// Displacement from `origin` to `self`, i.e. `self - origin`.
39    pub fn vector_from(self, origin: Point3) -> Vector3 {
40        Vector3::new(self.x - origin.x, self.y - origin.y, self.z - origin.z)
41    }
42
43    /// Point translated by `scale * vector`.
44    #[must_use]
45    pub fn translated(self, vector: Vector3, scale: f64) -> Point3 {
46        Point3::new(
47            self.x + scale * vector.x,
48            self.y + scale * vector.y,
49            self.z + scale * vector.z,
50        )
51    }
52}
53
54/// A 3D vector. Depending on context this may be a direction (often but not
55/// always unit length) or a length-bearing displacement.
56#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
57pub struct Vector3 {
58    /// X component.
59    pub x: f64,
60    /// Y component.
61    pub y: f64,
62    /// Z component.
63    pub z: f64,
64}
65
66impl Vector3 {
67    /// Construct a vector.
68    pub fn new(x: f64, y: f64, z: f64) -> Self {
69        Vector3 { x, y, z }
70    }
71
72    /// Euclidean length.
73    pub fn norm(&self) -> f64 {
74        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
75    }
76
77    /// Dot product with another vector.
78    pub fn dot(self, other: Vector3) -> f64 {
79        self.x * other.x + self.y * other.y + self.z * other.z
80    }
81
82    /// Cross product with another vector.
83    #[must_use]
84    pub fn cross(self, other: Vector3) -> Vector3 {
85        Vector3::new(
86            self.y * other.z - self.z * other.y,
87            self.z * other.x - self.x * other.z,
88            self.x * other.y - self.y * other.x,
89        )
90    }
91
92    /// Vector scaled by a factor.
93    #[must_use]
94    pub fn scale(self, factor: f64) -> Vector3 {
95        Vector3::new(self.x * factor, self.y * factor, self.z * factor)
96    }
97
98    /// Unit vector in the same direction, or `None` when the length is
99    /// within [`f64::EPSILON`] of zero (degenerate direction).
100    #[must_use]
101    pub fn unit(self) -> Option<Vector3> {
102        let length = self.norm();
103        (length > f64::EPSILON)
104            .then(|| Vector3::new(self.x / length, self.y / length, self.z / length))
105    }
106}
107
108impl std::ops::Add for Vector3 {
109    type Output = Vector3;
110
111    fn add(self, other: Vector3) -> Vector3 {
112        Vector3::new(self.x + other.x, self.y + other.y, self.z + other.z)
113    }
114}
115
116impl std::ops::Sub for Vector3 {
117    type Output = Vector3;
118
119    fn sub(self, other: Vector3) -> Vector3 {
120        Vector3::new(self.x - other.x, self.y - other.y, self.z - other.z)
121    }
122}
123
124/// A point in 2D surface parameter (u, v) space.
125#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
126pub struct Point2 {
127    /// U parameter.
128    pub u: f64,
129    /// V parameter.
130    pub v: f64,
131}
132
133impl Point2 {
134    /// Construct a 2D parameter point.
135    pub fn new(u: f64, v: f64) -> Self {
136        Point2 { u, v }
137    }
138}
139
140/// An axis-aligned bounding box, in the document's length unit.
141#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
142pub struct Aabb {
143    /// Minimum corner.
144    pub min: Point3,
145    /// Maximum corner.
146    pub max: Point3,
147}