sim_lib_interference_core/
sampling.rs1use crate::{InterferenceError, Point3M, PositiveMetres, UnitVector3};
4
5pub const SAMPLING_AXIS_ORTHOGONALITY_TOLERANCE: f64 = 1.0e-12;
11
12#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct SamplingPlane {
25 origin: Point3M,
26 u_axis: UnitVector3,
27 v_axis: UnitVector3,
28 normal: UnitVector3,
29 extent_u: PositiveMetres,
30 extent_v: PositiveMetres,
31 rows: usize,
32 columns: usize,
33 cell_count: usize,
34 cell_size_u_m: f64,
35 cell_size_v_m: f64,
36}
37
38impl SamplingPlane {
39 #[allow(clippy::too_many_arguments)]
41 pub fn new(
42 origin: Point3M,
43 u_axis: UnitVector3,
44 v_axis: UnitVector3,
45 extent_u: PositiveMetres,
46 extent_v: PositiveMetres,
47 rows: usize,
48 columns: usize,
49 ) -> Result<Self, InterferenceError> {
50 if rows == 0 {
51 return Err(InterferenceError::ZeroSamplingDimension { name: "rows" });
52 }
53 if columns == 0 {
54 return Err(InterferenceError::ZeroSamplingDimension { name: "columns" });
55 }
56 let cell_count = rows
57 .checked_mul(columns)
58 .ok_or(InterferenceError::SamplingCellCountOverflow { rows, columns })?;
59
60 let [ux, uy, uz] = u_axis.components();
61 let [vx, vy, vz] = v_axis.components();
62 let dot_product = ux * vx + uy * vy + uz * vz;
63 if dot_product.abs() > SAMPLING_AXIS_ORTHOGONALITY_TOLERANCE {
64 return Err(InterferenceError::NonOrthogonalSamplingAxes {
65 dot_product,
66 max_abs_dot_product: SAMPLING_AXIS_ORTHOGONALITY_TOLERANCE,
67 });
68 }
69
70 let normal = UnitVector3::new(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx)?;
71 let cell_size_u_m = extent_u.get() / columns as f64;
72 let cell_size_v_m = extent_v.get() / rows as f64;
73 require_cell_size("u", cell_size_u_m)?;
74 require_cell_size("v", cell_size_v_m)?;
75
76 Ok(Self {
77 origin,
78 u_axis,
79 v_axis,
80 normal,
81 extent_u,
82 extent_v,
83 rows,
84 columns,
85 cell_count,
86 cell_size_u_m,
87 cell_size_v_m,
88 })
89 }
90
91 pub fn origin(self) -> Point3M {
93 self.origin
94 }
95
96 pub fn u_axis(self) -> UnitVector3 {
98 self.u_axis
99 }
100
101 pub fn v_axis(self) -> UnitVector3 {
103 self.v_axis
104 }
105
106 pub fn normal(self) -> UnitVector3 {
108 self.normal
109 }
110
111 pub fn extent_u(self) -> PositiveMetres {
113 self.extent_u
114 }
115
116 pub fn extent_v(self) -> PositiveMetres {
118 self.extent_v
119 }
120
121 pub fn rows(self) -> usize {
123 self.rows
124 }
125
126 pub fn columns(self) -> usize {
128 self.columns
129 }
130
131 pub fn cell_count(self) -> usize {
133 self.cell_count
134 }
135
136 pub fn cell_size_u_m(self) -> f64 {
138 self.cell_size_u_m
139 }
140
141 pub fn cell_size_v_m(self) -> f64 {
143 self.cell_size_v_m
144 }
145
146 pub fn point_at(self, row: usize, column: usize) -> Result<Point3M, InterferenceError> {
148 if row >= self.rows || column >= self.columns {
149 return Err(InterferenceError::SamplingCellOutOfBounds {
150 row,
151 column,
152 rows: self.rows,
153 columns: self.columns,
154 });
155 }
156
157 let offset_u = (column as f64 + 0.5) * self.cell_size_u_m;
158 let offset_v = (row as f64 + 0.5) * self.cell_size_v_m;
159 let [origin_x, origin_y, origin_z] = self.origin.coordinates_metres();
160 let [ux, uy, uz] = self.u_axis.components();
161 let [vx, vy, vz] = self.v_axis.components();
162
163 Point3M::from_metres(
164 origin_x + offset_u * ux + offset_v * vx,
165 origin_y + offset_u * uy + offset_v * vy,
166 origin_z + offset_u * uz + offset_v * vz,
167 )
168 }
169}
170
171fn require_cell_size(axis: &'static str, value: f64) -> Result<(), InterferenceError> {
172 if value.is_finite() && value > 0.0 {
173 Ok(())
174 } else {
175 Err(InterferenceError::InvalidSamplingCellSize { axis, value })
176 }
177}