use crate::types::mesh::MeshData;
pub(super) const SITE_LOCAL_MESH_COORDINATE_SPACE: &str = "site_local";
pub(super) const MODEL_RTC_MESH_COORDINATE_SPACE: &str = "model_rtc";
pub(super) const RAW_IFC_MESH_COORDINATE_SPACE: &str = "raw_ifc";
const PLACEMENT_IDENTITY_EPSILON: f64 = 1e-9;
#[inline]
pub(super) fn translation_is_nonidentity(t: (f64, f64, f64)) -> bool {
t.0.abs() > PLACEMENT_IDENTITY_EPSILON
|| t.1.abs() > PLACEMENT_IDENTITY_EPSILON
|| t.2.abs() > PLACEMENT_IDENTITY_EPSILON
}
#[inline]
pub(super) fn rotation_is_identity(column_major_matrix: &[f64]) -> bool {
if column_major_matrix.len() < 16 {
return false;
}
let r00 = column_major_matrix[0];
let r10 = column_major_matrix[1];
let r20 = column_major_matrix[2];
let r01 = column_major_matrix[4];
let r11 = column_major_matrix[5];
let r21 = column_major_matrix[6];
let r02 = column_major_matrix[8];
let r12 = column_major_matrix[9];
let r22 = column_major_matrix[10];
(r00 - 1.0).abs() < PLACEMENT_IDENTITY_EPSILON
&& r10.abs() < PLACEMENT_IDENTITY_EPSILON
&& r20.abs() < PLACEMENT_IDENTITY_EPSILON
&& r01.abs() < PLACEMENT_IDENTITY_EPSILON
&& (r11 - 1.0).abs() < PLACEMENT_IDENTITY_EPSILON
&& r21.abs() < PLACEMENT_IDENTITY_EPSILON
&& r02.abs() < PLACEMENT_IDENTITY_EPSILON
&& r12.abs() < PLACEMENT_IDENTITY_EPSILON
&& (r22 - 1.0).abs() < PLACEMENT_IDENTITY_EPSILON
}
#[inline]
pub(crate) fn site_local_rotation_invalidates_captured_transforms(
site_local_rotation: Option<&Vec<f64>>,
) -> bool {
site_local_rotation.is_some_and(|m| !rotation_is_identity(m))
}
fn apply_inverse_rotation_in_place(values: &mut [f32], column_major_matrix: &[f64]) {
if values.len() < 3 || column_major_matrix.len() < 16 {
return;
}
if rotation_is_identity(column_major_matrix) {
return;
}
let r00 = column_major_matrix[0];
let r10 = column_major_matrix[1];
let r20 = column_major_matrix[2];
let r01 = column_major_matrix[4];
let r11 = column_major_matrix[5];
let r21 = column_major_matrix[6];
let r02 = column_major_matrix[8];
let r12 = column_major_matrix[9];
let r22 = column_major_matrix[10];
for chunk in values.chunks_exact_mut(3) {
let x = chunk[0] as f64;
let y = chunk[1] as f64;
let z = chunk[2] as f64;
chunk[0] = (r00 * x + r10 * y + r20 * z) as f32;
chunk[1] = (r01 * x + r11 * y + r21 * z) as f32;
chunk[2] = (r02 * x + r12 * y + r22 * z) as f32;
}
}
pub fn convert_mesh_to_site_local(mesh: &mut MeshData, site_transform: Option<&Vec<f64>>) {
let Some(site_transform) = site_transform else {
return;
};
apply_inverse_rotation_in_place(&mut mesh.positions, site_transform);
apply_inverse_rotation_in_place(&mut mesh.normals, site_transform);
apply_inverse_rotation_point_f64(&mut mesh.origin, site_transform);
}
fn apply_inverse_rotation_point_f64(p: &mut [f64; 3], column_major_matrix: &[f64]) {
if column_major_matrix.len() < 16
|| rotation_is_identity(column_major_matrix)
|| (p[0] == 0.0 && p[1] == 0.0 && p[2] == 0.0)
{
return;
}
let (r00, r10, r20) = (
column_major_matrix[0],
column_major_matrix[1],
column_major_matrix[2],
);
let (r01, r11, r21) = (
column_major_matrix[4],
column_major_matrix[5],
column_major_matrix[6],
);
let (r02, r12, r22) = (
column_major_matrix[8],
column_major_matrix[9],
column_major_matrix[10],
);
let (x, y, z) = (p[0], p[1], p[2]);
p[0] = r00 * x + r10 * y + r20 * z;
p[1] = r01 * x + r11 * y + r21 * z;
p[2] = r02 * x + r12 * y + r22 * z;
}
#[cfg(test)]
mod tests {
use super::*;
fn translation_only_matrix(t: (f64, f64, f64)) -> Vec<f64> {
#[rustfmt::skip]
let m = vec![
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
t.0, t.1, t.2, 1.0,
];
m
}
fn yawed_matrix(t: (f64, f64, f64)) -> Vec<f64> {
let c = 30f64.to_radians().cos();
let s = 30f64.to_radians().sin();
#[rustfmt::skip]
let m = vec![
c, s, 0.0, 0.0,
-s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
t.0, t.1, t.2, 1.0,
];
m
}
#[test]
fn identity_matrix_is_identity() {
assert!(rotation_is_identity(&translation_only_matrix((0.0, 0.0, 0.0))));
}
#[test]
fn translation_only_matrix_is_identity_rotation() {
assert!(rotation_is_identity(&translation_only_matrix((10.0, 20.0, 0.0))));
assert!(rotation_is_identity(&translation_only_matrix((-500.5, 12345.6, 7.0))));
}
#[test]
fn yawed_matrix_is_not_identity_rotation() {
assert!(!rotation_is_identity(&yawed_matrix((10.0, 20.0, 0.0))));
assert!(!rotation_is_identity(&yawed_matrix((0.0, 0.0, 0.0))));
}
#[test]
fn short_matrix_is_conservatively_not_identity() {
assert!(!rotation_is_identity(&[1.0, 0.0, 0.0]));
assert!(!rotation_is_identity(&[]));
}
#[test]
fn near_identity_matrix_does_not_perturb_origin() {
let yaw = 1e-10_f64;
let c = yaw.cos();
let s = yaw.sin();
#[rustfmt::skip]
let m = vec![
c, s, 0.0, 0.0,
-s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
];
assert!(rotation_is_identity(&m), "fixture must classify as identity");
let mut origin = [10_000_000.0_f64, 0.0, 0.0];
apply_inverse_rotation_point_f64(&mut origin, &m);
assert_eq!(
origin,
[10_000_000.0, 0.0, 0.0],
"an identity-classified rotation must not move the origin at all — \
it moved by {:?}",
[origin[0] - 10_000_000.0, origin[1], origin[2]],
);
}
}