1use axiolid_core::Point3;
20use axiolid_guarantees::{Certified, Precision, Sign};
21
22use crate::arithmetic::{
23 expansion_product, expansion_sign, expansion_sum, grow_expansion, negate_expansion,
24 scale_expansion,
25};
26use crate::expansion::{two_diff, two_product};
27use crate::orient3_dyadic::orient3d_exact_dyadic;
28
29const EPSILON: f64 = f64::EPSILON / 2.0;
31
32const ORIENT3D_ERROR_FACTOR: f64 = (7.0 + 56.0 * EPSILON) * EPSILON;
47
48#[must_use]
56pub fn orient3d(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
57 match orient3d_filter(a, b, c, d) {
58 Certified::Certain { sign, .. } => Certified::exact_sign(sign),
59 _ => orient3d_exact(a, b, c, d)
61 .or_else(|| orient3d_exact_dyadic(a, b, c, d))
62 .map_or(
63 Certified::Uncertain {
64 attempted: Precision::Exact,
65 },
66 Certified::exact_sign,
67 ),
68 }
69}
70
71#[must_use]
73pub fn orient3d_filter(a: Point3, b: Point3, c: Point3, d: Point3) -> Certified {
74 let (adx, ady, adz) = (a.x - d.x, a.y - d.y, a.z - d.z);
75 let (bdx, bdy, bdz) = (b.x - d.x, b.y - d.y, b.z - d.z);
76 let (cdx, cdy, cdz) = (c.x - d.x, c.y - d.y, c.z - d.z);
77
78 let differences = [adx, ady, adz, bdx, bdy, bdz, cdx, cdy, cdz];
79 if !differences.iter().all(|value| {
80 value.is_finite() && (*value == 0.0 || (value.abs() >= 1.0e-90 && value.abs() <= 1.0e90))
81 }) {
82 return Certified::Uncertain {
83 attempted: Precision::F64,
84 };
85 }
86
87 let bdxcdy = bdx * cdy;
88 let cdxbdy = cdx * bdy;
89 let cdxady = cdx * ady;
90 let adxcdy = adx * cdy;
91 let adxbdy = adx * bdy;
92 let bdxady = bdx * ady;
93
94 let determinant = adz * (bdxcdy - cdxbdy) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady);
95
96 let permanent = (bdxcdy.abs() + cdxbdy.abs()) * adz.abs()
99 + (cdxady.abs() + adxcdy.abs()) * bdz.abs()
100 + (adxbdy.abs() + bdxady.abs()) * cdz.abs();
101
102 Certified::from_filter(
103 determinant,
104 ORIENT3D_ERROR_FACTOR * permanent,
105 Precision::F64,
106 )
107}
108
109#[must_use]
116fn orient3d_exact(a: Point3, b: Point3, c: Point3, d: Point3) -> Option<Sign> {
117 let (adx, adx_error) = two_diff(a.x, d.x);
118 let (ady, ady_error) = two_diff(a.y, d.y);
119 let (adz, adz_error) = two_diff(a.z, d.z);
120 let (bdx, bdx_error) = two_diff(b.x, d.x);
121 let (bdy, bdy_error) = two_diff(b.y, d.y);
122 let (bdz, bdz_error) = two_diff(b.z, d.z);
123 let (cdx, cdx_error) = two_diff(c.x, d.x);
124 let (cdy, cdy_error) = two_diff(c.y, d.y);
125 let (cdz, cdz_error) = two_diff(c.z, d.z);
126
127 let differences = [[adx, ady, adz], [bdx, bdy, bdz], [cdx, cdy, cdz]];
128 let errors = [
129 [adx_error, ady_error, adz_error],
130 [bdx_error, bdy_error, bdz_error],
131 [cdx_error, cdy_error, cdz_error],
132 ];
133 if !exact_components_are_representable(&differences, &errors) {
134 return None;
135 }
136
137 if errors.iter().flatten().all(|error| *error == 0.0) {
138 return Some(orient3d_exact_differences(
139 differences[0],
140 differences[1],
141 differences[2],
142 ));
143 }
144
145 let [[adx, ady, adz], [bdx, bdy, bdz], [cdx, cdy, cdz]] = differences;
146 let [[adx_error, ady_error, adz_error], [bdx_error, bdy_error, bdz_error], [cdx_error, cdy_error, cdz_error]] =
147 errors;
148
149 let adx = difference_expansion(adx, adx_error);
150 let ady = difference_expansion(ady, ady_error);
151 let adz = difference_expansion(adz, adz_error);
152 let bdx = difference_expansion(bdx, bdx_error);
153 let bdy = difference_expansion(bdy, bdy_error);
154 let bdz = difference_expansion(bdz, bdz_error);
155 let cdx = difference_expansion(cdx, cdx_error);
156 let cdy = difference_expansion(cdy, cdy_error);
157 let cdz = difference_expansion(cdz, cdz_error);
158
159 let bc = orient3d_expansion_cofactor(&bdx, &cdy, &cdx, &bdy);
160 let ca = orient3d_expansion_cofactor(&cdx, &ady, &adx, &cdy);
161 let ab = orient3d_expansion_cofactor(&adx, &bdy, &bdx, &ady);
162
163 let total = expansion_sum(
164 &expansion_sum(&expansion_product(&bc, &adz), &expansion_product(&ca, &bdz)),
165 &expansion_product(&ab, &cdz),
166 );
167 Some(expansion_sign(&total))
168}
169
170#[must_use]
176fn exact_components_are_representable(differences: &[[f64; 3]; 3], errors: &[[f64; 3]; 3]) -> bool {
177 differences
178 .iter()
179 .flatten()
180 .chain(errors.iter().flatten())
181 .all(|value| value.is_finite() && (*value == 0.0 || highest_bit_exponent(*value) <= 300))
182 && determinant_terms_are_representable(differences, errors)
183}
184
185#[must_use]
186fn determinant_terms_are_representable(
187 differences: &[[f64; 3]; 3],
188 errors: &[[f64; 3]; 3],
189) -> bool {
190 let mut least_bits = [[None; 3]; 3];
191 for row in 0..3 {
192 for column in 0..3 {
193 least_bits[row][column] = [differences[row][column], errors[row][column]]
194 .into_iter()
195 .filter(|value| *value != 0.0)
196 .map(least_significant_bit_exponent)
197 .min();
198 }
199 }
200
201 [
202 [(1, 0), (2, 1), (0, 2)],
203 [(2, 0), (1, 1), (0, 2)],
204 [(2, 0), (0, 1), (1, 2)],
205 [(0, 0), (2, 1), (1, 2)],
206 [(0, 0), (1, 1), (2, 2)],
207 [(1, 0), (0, 1), (2, 2)],
208 ]
209 .into_iter()
210 .all(|term| {
211 let exponents = term.map(|(row, column)| least_bits[row][column]);
212 let [Some(first), Some(second), Some(third)] = exponents else {
213 return true;
214 };
215 first + second >= -1074 && first + second + third >= -1074
216 })
217}
218
219#[must_use]
220fn highest_bit_exponent(value: f64) -> i32 {
221 let bits = value.abs().to_bits();
222 let encoded_exponent = ((bits >> 52) & 0x7ff) as i32;
223 if encoded_exponent == 0 {
224 let fraction = bits & ((1_u64 << 52) - 1);
225 -1074 + (63 - fraction.leading_zeros() as i32)
226 } else {
227 encoded_exponent - 1023
228 }
229}
230
231#[must_use]
232fn least_significant_bit_exponent(value: f64) -> i32 {
233 let bits = value.abs().to_bits();
234 let encoded_exponent = ((bits >> 52) & 0x7ff) as i32;
235 let fraction = bits & ((1_u64 << 52) - 1);
236 if encoded_exponent == 0 {
237 -1074 + fraction.trailing_zeros() as i32
238 } else {
239 let significand = (1_u64 << 52) | fraction;
240 encoded_exponent - 1023 - 52 + significand.trailing_zeros() as i32
241 }
242}
243
244#[must_use]
246fn orient3d_exact_differences(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> Sign {
247 let bc = orient3d_cofactor(b[0], c[1], c[0], b[1]);
248 let ca = orient3d_cofactor(c[0], a[1], a[0], c[1]);
249 let ab = orient3d_cofactor(a[0], b[1], b[0], a[1]);
250 let total = expansion_sum(
251 &expansion_sum(&scale_expansion(&bc, a[2]), &scale_expansion(&ca, b[2])),
252 &scale_expansion(&ab, c[2]),
253 );
254 expansion_sign(&total)
255}
256
257#[must_use]
258fn difference_expansion(difference: f64, error: f64) -> Vec<f64> {
259 let mut expansion = Vec::new();
260 if error != 0.0 {
261 expansion.push(error);
262 }
263 if difference != 0.0 || expansion.is_empty() {
264 expansion.push(difference);
265 }
266 expansion
267}
268
269#[must_use]
270fn orient3d_expansion_cofactor(p: &[f64], q: &[f64], r: &[f64], s: &[f64]) -> Vec<f64> {
271 expansion_sum(
272 &expansion_product(p, q),
273 &negate_expansion(&expansion_product(r, s)),
274 )
275}
276
277#[must_use]
281pub(crate) fn orient3d_cofactor(p: f64, q: f64, r: f64, s: f64) -> Vec<f64> {
282 let (pq, pq_err) = two_product(p, q);
283 let (rs, rs_err) = two_product(r, s);
284 let e = grow_expansion(&[pq_err], -rs_err);
287 let e = grow_expansion(&e, pq);
288 grow_expansion(&e, -rs)
289}