use nalgebra::{Point3, Vector3};
#[derive(Debug, Clone, Copy, Default)]
pub struct CoordinateShift {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl CoordinateShift {
#[inline]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[inline]
pub fn from_point(point: Point3<f64>) -> Self {
Self {
x: point.x,
y: point.y,
z: point.z,
}
}
#[inline]
pub fn is_significant(&self) -> bool {
const THRESHOLD: f64 = 10000.0; self.x.abs() > THRESHOLD || self.y.abs() > THRESHOLD || self.z.abs() > THRESHOLD
}
#[inline]
pub fn is_zero(&self) -> bool {
self.x == 0.0 && self.y == 0.0 && self.z == 0.0
}
}
#[derive(Debug, Clone)]
pub struct Mesh {
pub positions: Vec<f32>,
pub normals: Vec<f32>,
pub indices: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct SubMesh {
pub geometry_id: u32,
pub mesh: Mesh,
}
impl SubMesh {
pub fn new(geometry_id: u32, mesh: Mesh) -> Self {
Self { geometry_id, mesh }
}
}
#[derive(Debug, Clone, Default)]
pub struct SubMeshCollection {
pub sub_meshes: Vec<SubMesh>,
}
impl SubMeshCollection {
pub fn new() -> Self {
Self { sub_meshes: Vec::new() }
}
pub fn add(&mut self, geometry_id: u32, mesh: Mesh) {
if !mesh.is_empty() {
self.sub_meshes.push(SubMesh::new(geometry_id, mesh));
}
}
pub fn is_empty(&self) -> bool {
self.sub_meshes.is_empty()
}
pub fn len(&self) -> usize {
self.sub_meshes.len()
}
pub fn into_combined_mesh(self) -> Mesh {
let mut combined = Mesh::new();
for sub in self.sub_meshes {
combined.merge(&sub.mesh);
}
combined
}
pub fn iter(&self) -> impl Iterator<Item = &SubMesh> {
self.sub_meshes.iter()
}
}
impl Mesh {
pub fn new() -> Self {
Self {
positions: Vec::new(),
normals: Vec::new(),
indices: Vec::new(),
}
}
pub fn with_capacity(vertex_count: usize, index_count: usize) -> Self {
Self {
positions: Vec::with_capacity(vertex_count * 3),
normals: Vec::with_capacity(vertex_count * 3),
indices: Vec::with_capacity(index_count),
}
}
pub fn from_triangle(v0: &Point3<f64>, v1: &Point3<f64>, v2: &Point3<f64>, normal: &Vector3<f64>) -> Self {
let mut mesh = Self::with_capacity(3, 3);
mesh.positions = vec![
v0.x as f32, v0.y as f32, v0.z as f32,
v1.x as f32, v1.y as f32, v1.z as f32,
v2.x as f32, v2.y as f32, v2.z as f32,
];
mesh.normals = vec![
normal.x as f32, normal.y as f32, normal.z as f32,
normal.x as f32, normal.y as f32, normal.z as f32,
normal.x as f32, normal.y as f32, normal.z as f32,
];
mesh.indices = vec![0, 1, 2];
mesh
}
#[inline]
pub fn add_vertex(&mut self, position: Point3<f64>, normal: Vector3<f64>) {
self.positions.push(position.x as f32);
self.positions.push(position.y as f32);
self.positions.push(position.z as f32);
self.normals.push(normal.x as f32);
self.normals.push(normal.y as f32);
self.normals.push(normal.z as f32);
}
#[inline]
pub fn add_vertex_with_shift(
&mut self,
position: Point3<f64>,
normal: Vector3<f64>,
shift: &CoordinateShift,
) {
let shifted_x = position.x - shift.x;
let shifted_y = position.y - shift.y;
let shifted_z = position.z - shift.z;
self.positions.push(shifted_x as f32);
self.positions.push(shifted_y as f32);
self.positions.push(shifted_z as f32);
self.normals.push(normal.x as f32);
self.normals.push(normal.y as f32);
self.normals.push(normal.z as f32);
}
#[inline]
pub fn apply_shift(&mut self, shift: &CoordinateShift) {
if shift.is_zero() {
return;
}
for chunk in self.positions.chunks_exact_mut(3) {
chunk[0] = (chunk[0] as f64 - shift.x) as f32;
chunk[1] = (chunk[1] as f64 - shift.y) as f32;
chunk[2] = (chunk[2] as f64 - shift.z) as f32;
}
}
#[inline]
pub fn add_triangle(&mut self, i0: u32, i1: u32, i2: u32) {
self.indices.push(i0);
self.indices.push(i1);
self.indices.push(i2);
}
#[inline]
pub fn merge(&mut self, other: &Mesh) {
if other.is_empty() {
return;
}
let vertex_offset = (self.positions.len() / 3) as u32;
self.positions.reserve(other.positions.len());
self.normals.reserve(other.normals.len());
self.indices.reserve(other.indices.len());
self.positions.extend_from_slice(&other.positions);
self.normals.extend_from_slice(&other.normals);
self.indices
.extend(other.indices.iter().map(|&i| i + vertex_offset));
}
#[inline]
pub fn merge_all(&mut self, meshes: &[Mesh]) {
let total_positions: usize = meshes.iter().map(|m| m.positions.len()).sum();
let total_indices: usize = meshes.iter().map(|m| m.indices.len()).sum();
self.positions.reserve(total_positions);
self.normals.reserve(total_positions);
self.indices.reserve(total_indices);
for mesh in meshes {
if !mesh.is_empty() {
let vertex_offset = (self.positions.len() / 3) as u32;
self.positions.extend_from_slice(&mesh.positions);
self.normals.extend_from_slice(&mesh.normals);
self.indices
.extend(mesh.indices.iter().map(|&i| i + vertex_offset));
}
}
}
#[inline]
pub fn vertex_count(&self) -> usize {
self.positions.len() / 3
}
#[inline]
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
#[inline]
pub fn is_empty(&self) -> bool {
self.positions.is_empty()
}
#[inline]
pub fn bounds(&self) -> (Point3<f32>, Point3<f32>) {
if self.is_empty() {
return (Point3::origin(), Point3::origin());
}
let mut min = Point3::new(f32::MAX, f32::MAX, f32::MAX);
let mut max = Point3::new(f32::MIN, f32::MIN, f32::MIN);
self.positions.chunks_exact(3).for_each(|chunk| {
let (x, y, z) = (chunk[0], chunk[1], chunk[2]);
min.x = min.x.min(x);
min.y = min.y.min(y);
min.z = min.z.min(z);
max.x = max.x.max(x);
max.y = max.y.max(y);
max.z = max.z.max(z);
});
(min, max)
}
#[inline]
pub fn centroid_f64(&self) -> Point3<f64> {
if self.is_empty() {
return Point3::origin();
}
let mut sum = Point3::new(0.0f64, 0.0f64, 0.0f64);
let count = self.positions.len() / 3;
self.positions.chunks_exact(3).for_each(|chunk| {
sum.x += chunk[0] as f64;
sum.y += chunk[1] as f64;
sum.z += chunk[2] as f64;
});
Point3::new(
sum.x / count as f64,
sum.y / count as f64,
sum.z / count as f64,
)
}
#[inline]
pub fn clear(&mut self) {
self.positions.clear();
self.normals.clear();
self.indices.clear();
}
pub fn filter_stretched_triangles(&mut self, max_edge_length: f32) -> usize {
if self.is_empty() {
return 0;
}
let max_edge_sq = max_edge_length * max_edge_length;
let mut valid_indices = Vec::new();
let mut removed_count = 0;
for i in (0..self.indices.len()).step_by(3) {
if i + 2 >= self.indices.len() {
break;
}
let i0 = self.indices[i] as usize;
let i1 = self.indices[i + 1] as usize;
let i2 = self.indices[i + 2] as usize;
if i0 * 3 + 2 >= self.positions.len()
|| i1 * 3 + 2 >= self.positions.len()
|| i2 * 3 + 2 >= self.positions.len()
{
removed_count += 1;
continue;
}
let p0 = (
self.positions[i0 * 3],
self.positions[i0 * 3 + 1],
self.positions[i0 * 3 + 2],
);
let p1 = (
self.positions[i1 * 3],
self.positions[i1 * 3 + 1],
self.positions[i1 * 3 + 2],
);
let p2 = (
self.positions[i2 * 3],
self.positions[i2 * 3 + 1],
self.positions[i2 * 3 + 2],
);
let edge01_sq = (p1.0 - p0.0).powi(2) + (p1.1 - p0.1).powi(2) + (p1.2 - p0.2).powi(2);
let edge12_sq = (p2.0 - p1.0).powi(2) + (p2.1 - p1.1).powi(2) + (p2.2 - p1.2).powi(2);
let edge20_sq = (p0.0 - p2.0).powi(2) + (p0.1 - p2.1).powi(2) + (p0.2 - p2.2).powi(2);
if edge01_sq <= max_edge_sq && edge12_sq <= max_edge_sq && edge20_sq <= max_edge_sq {
valid_indices.push(self.indices[i]);
valid_indices.push(self.indices[i + 1]);
valid_indices.push(self.indices[i + 2]);
} else {
removed_count += 1;
}
}
self.indices = valid_indices;
removed_count
}
}
impl Default for Mesh {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_creation() {
let mesh = Mesh::new();
assert!(mesh.is_empty());
assert_eq!(mesh.vertex_count(), 0);
assert_eq!(mesh.triangle_count(), 0);
}
#[test]
fn test_add_vertex() {
let mut mesh = Mesh::new();
mesh.add_vertex(Point3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
assert_eq!(mesh.vertex_count(), 1);
assert_eq!(mesh.positions, vec![1.0, 2.0, 3.0]);
assert_eq!(mesh.normals, vec![0.0, 0.0, 1.0]);
}
#[test]
fn test_merge() {
let mut mesh1 = Mesh::new();
mesh1.add_vertex(Point3::new(0.0, 0.0, 0.0), Vector3::z());
mesh1.add_triangle(0, 1, 2);
let mut mesh2 = Mesh::new();
mesh2.add_vertex(Point3::new(1.0, 1.0, 1.0), Vector3::y());
mesh2.add_triangle(0, 1, 2);
mesh1.merge(&mesh2);
assert_eq!(mesh1.vertex_count(), 2);
assert_eq!(mesh1.triangle_count(), 2);
}
#[test]
fn test_coordinate_shift_creation() {
let shift = CoordinateShift::new(500000.0, 5000000.0, 100.0);
assert!(shift.is_significant());
assert!(!shift.is_zero());
let zero_shift = CoordinateShift::default();
assert!(!zero_shift.is_significant());
assert!(zero_shift.is_zero());
}
#[test]
fn test_add_vertex_with_shift_preserves_precision() {
let mut mesh = Mesh::new();
let p1 = Point3::new(2679012.123456, 1247892.654321, 432.111);
let p2 = Point3::new(2679012.223456, 1247892.754321, 432.211);
let shift = CoordinateShift::new(2679012.0, 1247892.0, 432.0);
mesh.add_vertex_with_shift(p1, Vector3::z(), &shift);
mesh.add_vertex_with_shift(p2, Vector3::z(), &shift);
assert!((mesh.positions[0] - 0.123456).abs() < 0.0001); assert!((mesh.positions[1] - 0.654321).abs() < 0.0001); assert!((mesh.positions[2] - 0.111).abs() < 0.0001); assert!((mesh.positions[3] - 0.223456).abs() < 0.0001); assert!((mesh.positions[4] - 0.754321).abs() < 0.0001); assert!((mesh.positions[5] - 0.211).abs() < 0.0001);
let dx = mesh.positions[3] - mesh.positions[0];
let dy = mesh.positions[4] - mesh.positions[1];
let dz = mesh.positions[5] - mesh.positions[2];
assert!((dx - 0.1).abs() < 0.0001);
assert!((dy - 0.1).abs() < 0.0001);
assert!((dz - 0.1).abs() < 0.0001);
}
#[test]
fn test_apply_shift_to_existing_mesh() {
let mut mesh = Mesh::new();
mesh.positions = vec![
500000.0, 5000000.0, 0.0,
500010.0, 5000010.0, 10.0,
];
mesh.normals = vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0];
let shift = CoordinateShift::new(500000.0, 5000000.0, 0.0);
mesh.apply_shift(&shift);
assert!((mesh.positions[0] - 0.0).abs() < 0.001);
assert!((mesh.positions[1] - 0.0).abs() < 0.001);
assert!((mesh.positions[3] - 10.0).abs() < 0.001);
assert!((mesh.positions[4] - 10.0).abs() < 0.001);
}
#[test]
fn test_centroid_f64() {
let mut mesh = Mesh::new();
mesh.positions = vec![
0.0, 0.0, 0.0,
10.0, 10.0, 10.0,
20.0, 20.0, 20.0,
];
mesh.normals = vec![0.0; 9];
let centroid = mesh.centroid_f64();
assert!((centroid.x - 10.0).abs() < 0.001);
assert!((centroid.y - 10.0).abs() < 0.001);
assert!((centroid.z - 10.0).abs() < 0.001);
}
#[test]
fn test_precision_comparison_shifted_vs_unshifted() {
let base_x = 2679012.0;
let base_y = 1247892.0;
let offset = 0.001;
let p1 = Point3::new(base_x, base_y, 0.0);
let p2 = Point3::new(base_x + offset, base_y, 0.0);
let p1_f32_direct = (p1.x as f32, p1.y as f32);
let p2_f32_direct = (p2.x as f32, p2.y as f32);
let diff_direct = p2_f32_direct.0 - p1_f32_direct.0;
let shift = CoordinateShift::new(base_x, base_y, 0.0);
let p1_shifted = ((p1.x - shift.x) as f32, (p1.y - shift.y) as f32);
let p2_shifted = ((p2.x - shift.x) as f32, (p2.y - shift.y) as f32);
let diff_shifted = p2_shifted.0 - p1_shifted.0;
println!("Direct f32 difference (should be ~0.001): {}", diff_direct);
println!("Shifted f32 difference (should be ~0.001): {}", diff_shifted);
let error_direct = (diff_direct - offset as f32).abs();
let error_shifted = (diff_shifted - offset as f32).abs();
println!("Error without shift: {}m", error_direct);
println!("Error with shift: {}m", error_shifted);
assert!(
error_shifted < error_direct || error_shifted < 0.0001,
"Shifted precision should be better than direct conversion"
);
}
}