session_rust/
vector.rs

1use crate::tolerance::{Tolerance, SCALE, TO_DEGREES, TO_RADIANS};
2use serde::{ser::Serialize as SerTrait, Deserialize, Serialize};
3use std::fmt;
4use std::ops::{
5    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
6};
7use uuid::Uuid;
8
9/// A 3D vector with visual properties and JSON serialization support.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(tag = "type", rename = "Vector")]
12pub struct Vector {
13    pub guid: String,
14    pub name: String,
15    #[serde(rename = "x")]
16    _x: f32,
17    #[serde(rename = "y")]
18    _y: f32,
19    #[serde(rename = "z")]
20    _z: f32,
21    #[serde(skip)]
22    _length: f32,
23    #[serde(skip)]
24    _has_length: bool,
25}
26
27impl Vector {
28    /// Creates a new Vector with specified coordinates.
29    pub fn new(x: f32, y: f32, z: f32) -> Self {
30        Self {
31            _x: x,
32            _y: y,
33            _z: z,
34            guid: Uuid::new_v4().to_string(),
35            name: "my_vector".to_string(),
36            _length: 0.0,
37            _has_length: false,
38        }
39    }
40
41    /// Creates a zero vector.
42    pub fn zero() -> Self {
43        Self::new(0.0, 0.0, 0.0)
44    }
45
46    /// Creates a unit vector along X-axis.
47    ///
48    /// Returns
49    /// -------
50    /// Vector
51    ///     Unit vector (1, 0, 0).
52    pub fn x_axis() -> Self {
53        Self::new(1.0, 0.0, 0.0)
54    }
55
56    /// Creates a unit vector along Y-axis.
57    ///
58    /// Returns
59    /// -------
60    /// Vector
61    ///     Unit vector (0, 1, 0).
62    pub fn y_axis() -> Self {
63        Self::new(0.0, 1.0, 0.0)
64    }
65
66    /// Getters for coordinates
67    pub fn x(&self) -> f32 {
68        self._x
69    }
70    pub fn y(&self) -> f32 {
71        self._y
72    }
73    pub fn z(&self) -> f32 {
74        self._z
75    }
76
77    /// Setters for coordinates (invalidate cached length)
78    pub fn set_x(&mut self, v: f32) {
79        self._x = v;
80        self.invalidate_length_cache();
81    }
82    pub fn set_y(&mut self, v: f32) {
83        self._y = v;
84        self.invalidate_length_cache();
85    }
86    pub fn set_z(&mut self, v: f32) {
87        self._z = v;
88        self.invalidate_length_cache();
89    }
90
91    /// Creates a unit vector along Z-axis.
92    ///
93    /// Returns
94    /// -------
95    /// Vector
96    ///     Unit vector (0, 0, 1).
97    pub fn z_axis() -> Self {
98        Self::new(0.0, 0.0, 1.0)
99    }
100
101    /// Creates a vector from start point to end point.
102    ///
103    /// Parameters
104    /// ----------
105    /// start : &Vector
106    ///     The starting point.
107    /// end : &Vector
108    ///     The ending point.
109    ///
110    /// Returns
111    /// -------
112    /// Vector
113    ///     The vector from start to end (end - start).
114    pub fn from_start_and_end(start: &Vector, end: &Vector) -> Self {
115        Self::new(end._x - start._x, end._y - start._y, end._z - start._z)
116    }
117
118    ///////////////////////////////////////////////////////////////////////////////////////////
119    // Vector Operations
120    ///////////////////////////////////////////////////////////////////////////////////////////
121
122    /// Invalidates the cached length when coordinates change.
123    fn invalidate_length_cache(&mut self) {
124        self._has_length = false;
125    }
126
127    /// Computes the length (magnitude) of the vector without caching.
128    ///
129    /// Returns
130    /// -------
131    /// f32
132    ///     The length of the vector.
133    pub fn compute_length(&self) -> f32 {
134        (self._x * self._x + self._y * self._y + self._z * self._z).sqrt()
135    }
136
137    /// Gets the (cached) magnitude. Avoids recalculating if unchanged.
138    ///
139    /// Returns
140    /// -------
141    /// f32
142    ///     The magnitude (length) of the vector.
143    pub fn magnitude(&mut self) -> f32 {
144        if !self._has_length {
145            self._length = self.compute_length();
146            self._has_length = true;
147        }
148        self._length
149    }
150
151    /// Computes the squared length of the vector (avoids sqrt for performance).
152    pub fn length_squared(&self) -> f32 {
153        self._x * self._x + self._y * self._y + self._z * self._z
154    }
155
156    /// Normalizes the vector in place.
157    pub fn normalize_self(&mut self) {
158        let len = self.magnitude();
159        if len > Tolerance::ZERO_TOLERANCE as f32 {
160            self._x /= len;
161            self._y /= len;
162            self._z /= len;
163            self.invalidate_length_cache();
164        }
165    }
166
167    /// Returns a normalized copy of the vector.
168    pub fn normalize(&self) -> Self {
169        let mut result = self.clone();
170        result.normalize_self();
171        result
172    }
173
174    /// Reverses the vector direction in place.
175    pub fn reverse(&mut self) {
176        self._x = -self._x;
177        self._y = -self._y;
178        self._z = -self._z;
179        // Length magnitude stays the same, no need to invalidate cache
180    }
181
182    /// Scales the vector by a factor.
183    pub fn scale(&mut self, factor: f32) {
184        self._x *= factor;
185        self._y *= factor;
186        self._z *= factor;
187        self.invalidate_length_cache();
188    }
189
190    /// Scales the vector up by the global scale factor.
191    pub fn scale_up(&mut self) {
192        self.scale(SCALE as f32);
193    }
194
195    /// Scales the vector down by the global scale factor.
196    pub fn scale_down(&mut self) {
197        self.scale(1.0 / SCALE as f32);
198    }
199
200    /// Computes the dot product with another vector.
201    ///
202    /// Parameters
203    /// ----------
204    /// other : &Vector
205    ///     Other vector.
206    ///
207    /// Returns
208    /// -------
209    /// f32
210    ///     Dot product value.
211    pub fn dot(&self, other: &Vector) -> f32 {
212        self._x * other._x + self._y * other._y + self._z * other._z
213    }
214
215    /// Computes the cross product with another vector.
216    ///
217    /// Parameters
218    /// ----------
219    /// other : &Vector
220    ///     Other vector.
221    ///
222    /// Returns
223    /// -------
224    /// Vector
225    ///     Cross product vector (orthogonal to inputs).
226    pub fn cross(&self, other: &Vector) -> Vector {
227        Vector::new(
228            self._y * other._z - self._z * other._y,
229            self._z * other._x - self._x * other._z,
230            self._x * other._y - self._y * other._x,
231        )
232    }
233
234    /// Computes the angle between this vector and another in degrees.
235    pub fn angle(&self, other: &Vector, sign_by_cross_product: bool) -> f32 {
236        let dotp = self.dot(other);
237        let len_product = self.compute_length() * other.compute_length();
238
239        if len_product < Tolerance::ZERO_TOLERANCE as f32 {
240            return 0.0;
241        }
242
243        let cos_angle = (dotp / len_product).clamp(-1.0, 1.0);
244        let mut angle = cos_angle.acos() * TO_DEGREES as f32;
245
246        if sign_by_cross_product {
247            let cp = self.cross(other);
248            if cp.z() < 0.0 {
249                angle = -angle;
250            }
251        }
252
253        angle
254    }
255
256    /// Projects this vector onto another vector and returns detailed results.
257    ///
258    /// Returns a tuple of:
259    /// - projection vector of `self` onto `onto`
260    /// - projected length (scalar projection)
261    /// - perpendicular projected vector (self - projection)
262    /// - perpendicular projected vector length
263    pub fn projection(&self, onto: &Vector) -> (Vector, f32, Vector, f32) {
264        self.projection_with(onto, Tolerance::ZERO_TOLERANCE as f32)
265    }
266
267    /// Same as `projection` but allows specifying a tolerance.
268    pub fn projection_with(&self, onto: &Vector, tolerance: f32) -> (Vector, f32, Vector, f32) {
269        let onto_len_sq = onto.length_squared();
270
271        if onto_len_sq < tolerance {
272            return (Vector::zero(), 0.0, Vector::zero(), 0.0);
273        }
274
275        // Unit vector along 'onto'
276        let onto_len = onto_len_sq.sqrt();
277        let onto_unit = Vector::new(onto._x / onto_len, onto._y / onto_len, onto._z / onto_len);
278
279        // Scalar projection and projected vector
280        let projected_len = self.dot(&onto_unit);
281        let projection_vec = Vector::new(
282            onto_unit._x * projected_len,
283            onto_unit._y * projected_len,
284            onto_unit._z * projected_len,
285        );
286
287        // Perpendicular component and its length
288        let perp_vec = Vector::new(
289            self._x - projection_vec._x,
290            self._y - projection_vec._y,
291            self._z - projection_vec._z,
292        );
293        let perp_len = perp_vec.compute_length();
294
295        (projection_vec, projected_len, perp_vec, perp_len)
296    }
297
298    /// Checks if this vector is parallel to another vector.
299    /// Returns: 1 for parallel, -1 for antiparallel, 0 for not parallel.
300    pub fn is_parallel_to(&self, other: &Vector) -> i32 {
301        let len_product = self.compute_length() * other.compute_length();
302
303        if len_product <= 0.0 {
304            return 0;
305        }
306
307        let cos_angle = self.dot(other) / len_product;
308        let angle_in_radians = Tolerance::ANGLE_TOLERANCE_DEGREES as f32 * TO_RADIANS as f32;
309        let cos_tolerance = angle_in_radians.cos();
310
311        if cos_angle >= cos_tolerance {
312            1 // Parallel
313        } else if cos_angle <= -cos_tolerance {
314            -1 // Antiparallel
315        } else {
316            0 // Not parallel
317        }
318    }
319
320    /// Gets a leveled vector (replicates statics bug with degrees passed to cos).
321    pub fn get_leveled_vector(&self, vertical_height: f32) -> Vector {
322        let mut copy = self.clone();
323        copy.normalize_self();
324
325        if vertical_height != 0.0 {
326            let reference = Vector::z_axis();
327            let angle = copy.angle(&reference, true); // returns degrees
328                                                      // CRITICAL: statics bug - passes degrees directly to cos (expects radians)
329            let inclined_offset_by_vertical_distance = vertical_height / angle.cos();
330            copy.scale(inclined_offset_by_vertical_distance);
331        }
332
333        copy
334    }
335
336    /// Set this vector to be perpendicular to `v` (matches Python semantics).
337    /// Returns true on success, false otherwise.
338    pub fn perpendicular_to(&mut self, v: &Vector) -> bool {
339        // Ported from Python implementation to ensure identical behavior
340        let i: usize;
341        let j: usize;
342        let k: usize;
343        let a: f32;
344        let b: f32;
345
346        if v.y().abs() > v.x().abs() {
347            if v.z().abs() > v.y().abs() {
348                // |v.z| > |v.y| > |v.x|
349                i = 2;
350                j = 1;
351                k = 0;
352                a = v.z();
353                b = -v.y();
354            } else if v.z().abs() >= v.x().abs() {
355                // |v.y| >= |v.z| >= |v.x|
356                i = 1;
357                j = 2;
358                k = 0;
359                a = v.y();
360                b = -v.z();
361            } else {
362                // |v.y| > |v.x| > |v.z|
363                i = 1;
364                j = 0;
365                k = 2;
366                a = v.y();
367                b = -v.x();
368            }
369        } else if v.z().abs() > v.x().abs() {
370            // |v.z| > |v.x| >= |v.y|
371            i = 2;
372            j = 0;
373            k = 1;
374            a = v.z();
375            b = -v.x();
376        } else if v.z().abs() > v.y().abs() {
377            // |v.x| >= |v.z| > |v.y|
378            i = 0;
379            j = 2;
380            k = 1;
381            a = v.x();
382            b = -v.z();
383        } else {
384            // |v.x| >= |v.y| >= |v.z|
385            i = 0;
386            j = 1;
387            k = 2;
388            a = v.x();
389            b = -v.y();
390        }
391
392        let mut coords = [0.0, 0.0, 0.0];
393        coords[i] = b;
394        coords[j] = a;
395        coords[k] = 0.0;
396
397        self._x = coords[0];
398        self._y = coords[1];
399        self._z = coords[2];
400        self.invalidate_length_cache();
401
402        a != 0.0
403    }
404
405    ///////////////////////////////////////////////////////////////////////////////////////////
406    // Static Methods
407    ///////////////////////////////////////////////////////////////////////////////////////////
408
409    /// Computes the cosine law for triangle edge length.
410    pub fn cosine_law(
411        triangle_edge_length_a: f32,
412        triangle_edge_length_b: f32,
413        angle_in_degrees_between_edges: f32,
414        degrees: bool,
415    ) -> f32 {
416        let angle = if degrees {
417            angle_in_degrees_between_edges * TO_RADIANS as f32
418        } else {
419            angle_in_degrees_between_edges
420        };
421
422        (triangle_edge_length_a.powi(2) + triangle_edge_length_b.powi(2)
423            - 2.0 * triangle_edge_length_a * triangle_edge_length_b * angle.cos())
424        .sqrt()
425    }
426
427    /// Computes the sine law for triangle angle.
428    pub fn sine_law_angle(
429        triangle_edge_length_a: f32,
430        angle_in_degrees_in_front_of_a: f32,
431        triangle_edge_length_b: f32,
432        degrees: bool,
433    ) -> f32 {
434        let angle_a = if degrees {
435            angle_in_degrees_in_front_of_a * TO_RADIANS as f32
436        } else {
437            angle_in_degrees_in_front_of_a
438        };
439
440        let sin_b = (triangle_edge_length_b * angle_a.sin()) / triangle_edge_length_a;
441        let angle_b = sin_b.asin();
442
443        if degrees {
444            angle_b * TO_DEGREES as f32
445        } else {
446            angle_b
447        }
448    }
449
450    /// Computes the sine law for triangle edge length.
451    pub fn sine_law_length(
452        triangle_edge_length_a: f32,
453        angle_in_degrees_in_front_of_a: f32,
454        angle_in_degrees_in_front_of_b: f32,
455        degrees: bool,
456    ) -> f32 {
457        let angle_a = if degrees {
458            angle_in_degrees_in_front_of_a * TO_RADIANS as f32
459        } else {
460            angle_in_degrees_in_front_of_a
461        };
462
463        let angle_b = if degrees {
464            angle_in_degrees_in_front_of_b * TO_RADIANS as f32
465        } else {
466            angle_in_degrees_in_front_of_b
467        };
468
469        (triangle_edge_length_a * angle_b.sin()) / angle_a.sin()
470    }
471
472    /// Computes the angle between vector XY components in degrees.
473    pub fn angle_between_vector_xy_components(vector: &Vector) -> f32 {
474        vector._y.atan2(vector._x) * TO_DEGREES as f32
475    }
476
477    /// Deprecated: use `angle_between_vector_xy_components`.
478    #[allow(dead_code)]
479    pub fn angle_between_vector_xy_components_degrees(vector: &Vector) -> f32 {
480        Self::angle_between_vector_xy_components(vector)
481    }
482
483    /// Sums a collection of vectors.
484    pub fn sum_of_vectors(vectors: &[Vector]) -> Vector {
485        let mut result = Vector::zero();
486        for vector in vectors {
487            result._x += vector._x;
488            result._y += vector._y;
489            result._z += vector._z;
490        }
491        result
492    }
493
494    /// Computes coordinate direction angles (alpha, beta, gamma) in degrees.
495    pub fn coordinate_direction_3angles(&self, degrees: bool) -> [f32; 3] {
496        let length = self.compute_length();
497        if length < Tolerance::ZERO_TOLERANCE as f32 {
498            return [0.0, 0.0, 0.0];
499        }
500
501        let cos_alpha = self._x / length;
502        let cos_beta = self._y / length;
503        let cos_gamma = self._z / length;
504
505        let alpha = cos_alpha.acos();
506        let beta = cos_beta.acos();
507        let gamma = cos_gamma.acos();
508
509        if degrees {
510            [
511                alpha * TO_DEGREES as f32,
512                beta * TO_DEGREES as f32,
513                gamma * TO_DEGREES as f32,
514            ]
515        } else {
516            [alpha, beta, gamma]
517        }
518    }
519
520    /// Computes coordinate direction angles (phi, theta) in degrees.
521    pub fn coordinate_direction_2angles(&self, degrees: bool) -> [f32; 2] {
522        let length_xy = (self._x * self._x + self._y * self._y).sqrt();
523        let length = self.compute_length();
524
525        if length < Tolerance::ZERO_TOLERANCE as f32 {
526            return [0.0, 0.0];
527        }
528
529        let phi = self._y.atan2(self._x);
530        let theta = length_xy.atan2(self._z);
531
532        if degrees {
533            [phi * TO_DEGREES as f32, theta * TO_DEGREES as f32]
534        } else {
535            [phi, theta]
536        }
537    }
538
539    ///////////////////////////////////////////////////////////////////////////////////////////
540    // JSON
541    ///////////////////////////////////////////////////////////////////////////////////////////
542
543    /// Serializes the Vector to a JSON string.
544    pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
545        let mut buf = Vec::new();
546        let formatter = serde_json::ser::PrettyFormatter::with_indent(b"    ");
547        let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
548        SerTrait::serialize(self, &mut ser)?;
549        Ok(String::from_utf8(buf)?)
550    }
551
552    /// Deserializes a Vector from a JSON string.
553    pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
554        Ok(serde_json::from_str(json_data)?)
555    }
556
557    /// Serializes the Vector to a JSON file.
558    pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
559        let json = self.jsondump()?;
560        std::fs::write(filepath, json)?;
561        Ok(())
562    }
563
564    /// Deserializes a Vector from a JSON file.
565    pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
566        let json = std::fs::read_to_string(filepath)?;
567        Self::jsonload(&json)
568    }
569}
570
571impl Default for Vector {
572    fn default() -> Self {
573        Self::new(0.0, 0.0, 0.0)
574    }
575}
576
577// Index trait for array-like access
578impl Index<usize> for Vector {
579    type Output = f32;
580
581    fn index(&self, index: usize) -> &Self::Output {
582        match index {
583            0 => &self._x,
584            1 => &self._y,
585            2 => &self._z,
586            _ => panic!("Index out of bounds for Vector"),
587        }
588    }
589}
590
591impl IndexMut<usize> for Vector {
592    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
593        self.invalidate_length_cache();
594        match index {
595            0 => &mut self._x,
596            1 => &mut self._y,
597            2 => &mut self._z,
598            _ => panic!("Index out of bounds for Vector"),
599        }
600    }
601}
602
603// Arithmetic operators
604impl Add for Vector {
605    type Output = Vector;
606
607    fn add(self, other: Vector) -> Vector {
608        Vector::new(
609            self.x() + other.x(),
610            self.y() + other.y(),
611            self.z() + other.z(),
612        )
613    }
614}
615
616impl Add for &Vector {
617    type Output = Vector;
618
619    fn add(self, other: &Vector) -> Vector {
620        Vector::new(
621            self.x() + other.x(),
622            self.y() + other.y(),
623            self.z() + other.z(),
624        )
625    }
626}
627
628impl Sub for Vector {
629    type Output = Vector;
630
631    fn sub(self, other: Vector) -> Vector {
632        Vector::new(
633            self.x() - other.x(),
634            self.y() - other.y(),
635            self.z() - other.z(),
636        )
637    }
638}
639
640impl Sub for &Vector {
641    type Output = Vector;
642
643    fn sub(self, other: &Vector) -> Vector {
644        Vector::new(
645            self.x() - other.x(),
646            self.y() - other.y(),
647            self.z() - other.z(),
648        )
649    }
650}
651
652impl Mul<f32> for Vector {
653    type Output = Vector;
654
655    fn mul(self, scalar: f32) -> Vector {
656        Vector::new(self.x() * scalar, self.y() * scalar, self.z() * scalar)
657    }
658}
659
660impl Mul<f32> for &Vector {
661    type Output = Vector;
662
663    fn mul(self, scalar: f32) -> Vector {
664        Vector::new(self.x() * scalar, self.y() * scalar, self.z() * scalar)
665    }
666}
667
668impl Div<f32> for Vector {
669    type Output = Vector;
670
671    fn div(self, scalar: f32) -> Vector {
672        Vector::new(self.x() / scalar, self.y() / scalar, self.z() / scalar)
673    }
674}
675
676impl Div<f32> for &Vector {
677    type Output = Vector;
678
679    fn div(self, scalar: f32) -> Vector {
680        Vector::new(self.x() / scalar, self.y() / scalar, self.z() / scalar)
681    }
682}
683
684impl Neg for Vector {
685    type Output = Vector;
686
687    fn neg(self) -> Vector {
688        Vector::new(-self.x(), -self.y(), -self.z())
689    }
690}
691
692impl Neg for &Vector {
693    type Output = Vector;
694
695    fn neg(self) -> Vector {
696        Vector::new(-self.x(), -self.y(), -self.z())
697    }
698}
699
700// Compound assignment operators
701impl AddAssign for Vector {
702    fn add_assign(&mut self, other: Vector) {
703        self.set_x(self.x() + other.x());
704        self.set_y(self.y() + other.y());
705        self.set_z(self.z() + other.z());
706    }
707}
708
709impl AddAssign<&Vector> for Vector {
710    fn add_assign(&mut self, other: &Vector) {
711        self.set_x(self.x() + other.x());
712        self.set_y(self.y() + other.y());
713        self.set_z(self.z() + other.z());
714    }
715}
716
717impl SubAssign for Vector {
718    fn sub_assign(&mut self, other: Vector) {
719        self.set_x(self.x() - other.x());
720        self.set_y(self.y() - other.y());
721        self.set_z(self.z() - other.z());
722    }
723}
724
725impl SubAssign<&Vector> for Vector {
726    fn sub_assign(&mut self, other: &Vector) {
727        self.set_x(self.x() - other.x());
728        self.set_y(self.y() - other.y());
729        self.set_z(self.z() - other.z());
730    }
731}
732
733impl MulAssign<f32> for Vector {
734    fn mul_assign(&mut self, scalar: f32) {
735        self.set_x(self.x() * scalar);
736        self.set_y(self.y() * scalar);
737        self.set_z(self.z() * scalar);
738    }
739}
740
741impl DivAssign<f32> for Vector {
742    fn div_assign(&mut self, scalar: f32) {
743        self.set_x(self.x() / scalar);
744        self.set_y(self.y() / scalar);
745        self.set_z(self.z() / scalar);
746    }
747}
748
749impl fmt::Display for Vector {
750    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
751        write!(
752            f,
753            "Vector({}, {}, {}, {}, {})",
754            self.x(),
755            self.y(),
756            self.z(),
757            self.guid,
758            self.name
759        )
760    }
761}
762
763#[cfg(test)]
764#[path = "vector_test.rs"]
765mod tests;