Merge pull request #1240 from kevin9327/fix/step-export-large-coordinates

This commit is contained in:
Hakan Seven committed 2026-09-14 09:29:08 +03:00
commit 3fe91fa6e1
1 file changed
+75 -7
+75 -7
View File
@@ -21,7 +21,7 @@ use std::fmt::Write as FmtWrite;
pub fn build_step(meshes: &[&MeshModel]) -> Option<String> {
// Collect all triangles as (v0, v1, v2, normal).
struct Tri {
v: [[f32; 3]; 3],
v: [[f64; 3]; 3],
n: [f32; 3],
}
@@ -30,6 +30,13 @@ pub fn build_step(meshes: &[&MeshModel]) -> Option<String> {
let verts = &mesh.verts;
let normals = &mesh.normals;
let idx = &mesh.indices;
// Positions are stored as an f32 half plus a low residual so they stay
// precise at survey coordinates; write the sum, as the renderer uses.
let point = |i: usize| {
let high = verts[i];
let low = mesh.verts_low.get(i).copied().unwrap_or([0.0; 3]);
[0, 1, 2].map(|k| high[k] as f64 + low[k] as f64)
};
let n_tri = idx.len() / 3;
for t in 0..n_tri {
let i0 = idx[t * 3] as usize;
@@ -38,9 +45,9 @@ pub fn build_step(meshes: &[&MeshModel]) -> Option<String> {
if i0 >= verts.len() || i1 >= verts.len() || i2 >= verts.len() {
continue;
}
let a = verts[i0];
let b = verts[i1];
let c = verts[i2];
let a = point(i0);
let b = point(i1);
let c = point(i2);
let n = if !normals.is_empty() && i0 < normals.len() {
normals[i0]
} else {
@@ -49,8 +56,8 @@ pub fn build_step(meshes: &[&MeshModel]) -> Option<String> {
let nx = ab[1] * ac[2] - ab[2] * ac[1];
let ny = ab[2] * ac[0] - ab[0] * ac[2];
let nz = ab[0] * ac[1] - ab[1] * ac[0];
let len = (nx * nx + ny * ny + nz * nz).sqrt().max(f32::EPSILON);
[nx / len, ny / len, nz / len]
let len = (nx * nx + ny * ny + nz * nz).sqrt().max(f64::EPSILON);
[(nx / len) as f32, (ny / len) as f32, (nz / len) as f32]
};
tris.push(Tri { v: [a, b, c], n });
}
@@ -128,7 +135,7 @@ pub fn build_step(meshes: &[&MeshModel]) -> Option<String> {
tri.v[k1][1] - tri.v[k][1],
tri.v[k1][2] - tri.v[k][2],
];
let len = (dx * dx + dy * dy + dz * dz).sqrt().max(f32::EPSILON);
let len = (dx * dx + dy * dy + dz * dz).sqrt().max(f64::EPSILON);
writeln!(
data,
"#{} = DIRECTION('',({:.6},{:.6},{:.6}));",
@@ -263,3 +270,64 @@ fn chrono_timestamp() -> String {
let day = doy % 30 + 1;
format!("{year:04}-{month:02}-{day:02}T{hh:02}:{mm:02}:{ss:02}")
}
#[cfg(test)]
mod tests {
use super::build_step;
use crate::scene::model::mesh_model::MeshModel;
fn triangle(verts: Vec<[f32; 3]>, verts_low: Vec<[f32; 3]>) -> MeshModel {
MeshModel {
name: String::new(),
verts,
verts_low,
normals: Vec::new(),
indices: vec![0, 1, 2],
triangle_material_handles: Vec::new(),
triangle_colors: Vec::new(),
color: [1.0; 4],
selected: false,
}
}
/// Split an absolute position into the f32 pair the tessellator stores.
fn split(point: [f64; 3]) -> ([f32; 3], [f32; 3]) {
let high = point.map(|v| v as f32);
let low = [0, 1, 2].map(|i| (point[i] - high[i] as f64) as f32);
(high, low)
}
/// At survey coordinates the f32 half alone is 6 cm coarse: 500000.123
/// is stored as 500000.125 plus a -0.002 residual. The export has to add
/// the residual back, as the renderer does.
#[test]
fn large_coordinates_keep_their_low_residual() {
let (verts, verts_low): (Vec<_>, Vec<_>) = [
[500000.123, 500010.456, 0.0],
[500001.123, 500010.456, 0.0],
[500000.123, 500011.456, 0.0],
]
.into_iter()
.map(split)
.unzip();
let step = build_step(&[&triangle(verts, verts_low)]).expect("step");
assert!(
step.contains("CARTESIAN_POINT('',(500000.123000,500010.456000,0.000000));"),
"first vertex lost its residual:\n{}",
step.lines()
.find(|line| line.contains("CARTESIAN_POINT"))
.unwrap_or("")
);
}
/// Meshes without residuals (imported OBJ, legacy meshes) export as before.
#[test]
fn a_mesh_without_low_residuals_exports_its_positions() {
let mesh = triangle(
vec![[1.5, 2.0, 0.0], [2.5, 2.0, 0.0], [1.5, 3.0, 0.0]],
Vec::new(),
);
let step = build_step(&[&mesh]).expect("step");
assert!(step.contains("CARTESIAN_POINT('',(1.500000,2.000000,0.000000));"));
}
}