eryon_surface/points/
point.rs

1/*
2    Appellation: critical_point <module>
3    Contrib: @FL03
4*/
5use super::PointKind;
6use num_traits::{FromPrimitive, Num};
7use rstmt::Octave;
8use rstmt::nrt::{Triad, Triads};
9
10/// Adjust coordinates based on triad class
11fn adjust_coordinates<T>(coords: [T; 3], triad: &Triad) -> [T; 3]
12where
13    T: Copy + PartialOrd + FromPrimitive + Num,
14{
15    // TODO: ensure the coordinates are weighted accordingly; not just based on the class
16    let class = triad.class();
17    // Define adjustment factors for different triad classes
18    match class {
19        Triads::Major => {
20            // Standard coordinates for major triads
21            coords
22        }
23        Triads::Minor => {
24            // For minor triads, shift some weight from the 3rd to the 5th
25            let third_weight_reduction = T::from_f32(0.1).unwrap();
26            [
27                coords[0],
28                coords[1] - third_weight_reduction,
29                coords[2] + third_weight_reduction,
30            ]
31        }
32        Triads::Diminished => {
33            // For diminished, reduce 5th, increase 3rd
34            let fifth_weight_reduction = T::from_f32(0.15).unwrap();
35            [
36                coords[0],
37                coords[1] + fifth_weight_reduction,
38                coords[2] - fifth_weight_reduction,
39            ]
40        }
41        Triads::Augmented => {
42            // For augmented, increase 5th
43            let fifth_weight_increase = T::from_f32(0.1).unwrap();
44            let total = T::one() - fifth_weight_increase;
45            let factor = total / (coords[0] + coords[1]);
46            [
47                coords[0] * factor,
48                coords[1] * factor,
49                coords[2] + fifth_weight_increase,
50            ]
51        } // Add handling for other classes as needed
52    }
53}
54
55/// Critical point within the surface of a triadic space
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
57#[cfg_attr(
58    feature = "serde",
59    derive(serde_derive::Deserialize, serde_derive::Serialize)
60)]
61pub struct Point<T = f32> {
62    /// Barycentric coordinates within the triad (u,v,w where u+v+w=1)
63    pub(crate) coordinates: [T; 3],
64    /// the kind of point
65    pub(crate) kind: PointKind,
66    /// the modulus of the triad
67    pub(crate) modulus: Octave,
68    /// Importance weight of this critical point
69    pub(crate) weight: T,
70}
71
72impl<T> Point<T> {
73    pub fn new(mut u: T, mut v: T, modulus: Octave<isize>, kind: PointKind) -> Self
74    where
75        T: Copy + PartialOrd + num_traits::Num,
76    {
77        // Ensure coordinates sum to 1.0
78        let sum = u + v;
79        if sum > T::one() {
80            // Normalize if sum exceeds 1
81            u = u / sum;
82            v = v / sum;
83        }
84        let w = T::one() - u - v;
85
86        Self {
87            coordinates: [u, v, w],
88            kind,
89            modulus,
90            weight: T::one(),
91        }
92    }
93    /// Create a new critical point with coordinates adjusted for the triad class
94    pub fn from_triad(kind: PointKind, triad: &Triad) -> Self
95    where
96        T: num_traits::Float + num_traits::FromPrimitive,
97    {
98        let base_coords = kind.default_coordinates::<T>();
99        let adjusted_coords = adjust_coordinates(base_coords, triad);
100
101        Self {
102            coordinates: adjusted_coords,
103            modulus: triad.octave(),
104            kind,
105            weight: T::one(),
106        }
107    }
108    /// returns an immutable reference to the coordinates of the critical point
109    pub const fn coordinates(&self) -> &[T; 3] {
110        &self.coordinates
111    }
112    /// returns an mutable reference to the coordinates of the critical point
113    pub fn coordinates_mut(&mut self) -> &mut [T; 3] {
114        &mut self.coordinates
115    }
116    /// returns an immutable reference to the critical points octave
117    pub const fn modulus(&self) -> &Octave {
118        &self.modulus
119    }
120    /// returns a mutable reference to the critical points octave
121    pub fn modulus_mut(&mut self) -> &mut Octave {
122        &mut self.modulus
123    }
124    /// returns the name of the critical point
125    pub fn kind(&self) -> PointKind {
126        self.kind
127    }
128    /// returns an immutable reference to the weight of the critical point
129    pub const fn weight(&self) -> &T {
130        &self.weight
131    }
132    /// returns a mutable reference to the weight of the critical point
133    pub fn weight_mut(&mut self) -> &mut T {
134        &mut self.weight
135    }
136    /// set the coordinates of the critical point
137    pub fn set_coordinates(&mut self, u: T, v: T)
138    where
139        T: Copy + PartialOrd + num_traits::Num,
140    {
141        let sum = u + v;
142        if sum > T::one() {
143            self.coordinates[0] = u / sum;
144            self.coordinates[1] = v / sum;
145        } else {
146            self.coordinates[0] = u;
147            self.coordinates[1] = v;
148        }
149        self.coordinates[2] = T::one() - self.coordinates[0] - self.coordinates[1];
150    }
151    /// set the name of the critical point
152    pub fn set_kind(&mut self, kind: PointKind) {
153        self.kind = kind;
154    }
155    /// set the weight of the critical point
156    pub fn set_weight(&mut self, weight: T) {
157        self.weight = weight;
158    }
159    /// consumes the current instance to create another with the given coordinates
160    pub fn with_coordinates(self, u: T, v: T) -> Self
161    where
162        T: Copy + PartialOrd + num_traits::NumOps<T> + num_traits::One,
163    {
164        let sum = u + v;
165        let (u, v) = if sum > T::one() {
166            (u / sum, v / sum)
167        } else {
168            (u, v)
169        };
170        let w = T::one() - u - v;
171        Self {
172            coordinates: [u, v, w],
173            ..self
174        }
175    }
176    /// consumes the current instance to create another with the given name
177    pub fn with_kind(self, kind: PointKind) -> Self {
178        Self { kind, ..self }
179    }
180    /// consumes the current instance to create another with the given modulus
181    pub fn with_modulus(self, modulus: Octave) -> Self {
182        Self { modulus, ..self }
183    }
184    /// consumes the current instance to create another with the given weight
185    pub fn with_weight(self, weight: T) -> Self {
186        Self { weight, ..self }
187    }
188    /// returns true if the critical point is valid
189    pub fn validate(&self) -> bool
190    where
191        T: num_traits::Float,
192    {
193        let sum = self.coordinates.iter().fold(T::zero(), |a, &b| a + b);
194        (sum - T::one()).abs() < T::from(1e-6).unwrap()
195    }
196}
197
198impl<T> core::ops::Index<usize> for Point<T> {
199    type Output = T;
200
201    fn index(&self, index: usize) -> &Self::Output {
202        &self.coordinates[index % 3]
203    }
204}
205
206impl<T> core::ops::IndexMut<usize> for Point<T> {
207    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
208        &mut self.coordinates[index % 3]
209    }
210}