forked from 1b-agent/opencadstudio-upstream
refactor(tess): move solid sampling to kernel
Remove local face, edge, spline, sweep, loft, and silhouette samplers. Meshes and overlays share the kernel tolerance and edge schedule.
This commit is contained in:
1 parent
60f5bca0d4
commit
dc998fe46b
15 files changed
+410
-3938
No files matched your search
@@ -42,76 +42,10 @@ pub struct MeshModel {
|
||||
///
|
||||
/// `lods` holds up to one MeshModel per LOD level (high → low). Empty
|
||||
/// slots fall back to the nearest available LOD at render time.
|
||||
/// A curved face's generator, kept so a view-dependent silhouette (DISPSILH)
|
||||
/// can be computed per frame — the silhouette is where the surface turns away
|
||||
/// from the eye, which no baked edge can capture. World-space, post body
|
||||
/// transform; base/centre points carry a double-single low half so they stay
|
||||
/// precise at UTM scale like the mesh verts. Each variant also carries the
|
||||
/// face's parametric extent so the silhouette is clipped to the actual face
|
||||
/// rather than drawn across the whole (possibly partial) surface.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CurvedGen {
|
||||
/// Cone / cylinder: two edge-on lines up the surface.
|
||||
Cone {
|
||||
base: [f32; 3],
|
||||
base_low: [f32; 3],
|
||||
axis: [f32; 3],
|
||||
/// Radial frame: `u` is the θ=0 direction, `v = axis × u`.
|
||||
u_dir: [f32; 3],
|
||||
v_dir: [f32; 3],
|
||||
/// Radius at the base (`h = 0`).
|
||||
radius: f32,
|
||||
/// `tan(half-angle)`: radius at height `h` is `radius + h * tan_a`.
|
||||
tan_a: f32,
|
||||
/// Height span along the axis the face covers (base is `h = 0`).
|
||||
h_max: f32,
|
||||
theta_min: f32,
|
||||
theta_span: f32,
|
||||
full: bool,
|
||||
},
|
||||
/// Sphere: the great circle perpendicular to the view, clipped to the
|
||||
/// face's longitude/colatitude window.
|
||||
Sphere {
|
||||
center: [f32; 3],
|
||||
center_low: [f32; 3],
|
||||
pole: [f32; 3],
|
||||
u_dir: [f32; 3],
|
||||
v_dir: [f32; 3],
|
||||
radius: f32,
|
||||
theta_min: f32,
|
||||
theta_span: f32,
|
||||
full: bool,
|
||||
phi_min: f32,
|
||||
phi_max: f32,
|
||||
},
|
||||
/// Torus: view-dependent tube silhouette, clipped to both parametric
|
||||
/// windows the face covers.
|
||||
Torus {
|
||||
center: [f32; 3],
|
||||
center_low: [f32; 3],
|
||||
axis: [f32; 3],
|
||||
u_dir: [f32; 3],
|
||||
v_dir: [f32; 3],
|
||||
major: f32,
|
||||
minor: f32,
|
||||
phi_min: f32,
|
||||
phi_span: f32,
|
||||
full: bool,
|
||||
theta_min: f32,
|
||||
theta_span: f32,
|
||||
theta_full: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Kernel-owned source for a view-dependent silhouette.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StoredSilhouette {
|
||||
pub viewport_id: i64,
|
||||
pub view_direction: [f32; 3],
|
||||
pub up_vector: [f32; 3],
|
||||
pub target: [f32; 3],
|
||||
pub is_perspective: bool,
|
||||
pub edge_verts: Vec<[f32; 3]>,
|
||||
pub edge_verts_low: Vec<[f32; 3]>,
|
||||
pub struct CurvedGen {
|
||||
pub source: acadrust::kernel::brep::mesh::SilhouetteSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
@@ -149,13 +83,8 @@ pub struct MeshLodSet {
|
||||
pub edge_verts: Vec<[f32; 3]>,
|
||||
/// Low residual paired with `edge_verts`.
|
||||
pub edge_verts_low: Vec<[f32; 3]>,
|
||||
/// Curved-face generators for per-frame silhouette (DISPSILH). Empty for a
|
||||
/// solid with no curved faces, or when silhouettes aren't wanted.
|
||||
/// Kernel sources for per-frame silhouettes.
|
||||
pub curved_gens: Vec<CurvedGen>,
|
||||
/// View-specific silhouette caches stored in COMMON_3DSOLID. They are used
|
||||
/// when the decoded surface family cannot provide a live analytic
|
||||
/// silhouette for the current view.
|
||||
pub stored_silhouettes: Vec<StoredSilhouette>,
|
||||
/// Geometry measurements calculated once from the highest available LOD.
|
||||
/// Properties can read these without re-parsing or re-tessellating ACIS on
|
||||
/// the UI thread.
|
||||
@@ -291,7 +220,6 @@ impl MeshLodSet {
|
||||
edge_verts: Vec::new(),
|
||||
edge_verts_low: Vec::new(),
|
||||
curved_gens: Vec::new(),
|
||||
stored_silhouettes: Vec::new(),
|
||||
metrics,
|
||||
world_aabb,
|
||||
z_aabb,
|
||||
|
||||
@@ -25,6 +25,10 @@ const SAG: f64 = 0.05;
|
||||
/// What counts as the same point when the kernel checks a body over.
|
||||
const TOL: f64 = 1e-9;
|
||||
|
||||
fn tessellation(body: &Body) -> brep::mesh::BodyMesh {
|
||||
brep::mesh::tessellate(body, brep::mesh::TessellationTolerance::new(SAG, TOL))
|
||||
}
|
||||
|
||||
/// Axis-aligned box from its center and full extents.
|
||||
pub fn box_solid(center: [f64; 3], length: f64, width: f64, height: f64) -> Option<Body> {
|
||||
brep::make::cuboid(
|
||||
@@ -143,7 +147,7 @@ fn about_origin(x: [f64; 3], y: [f64; 3], z: [f64; 3], about: [f64; 3]) -> [f64;
|
||||
|
||||
/// The box a body occupies, from its mesh.
|
||||
pub fn extent(body: &Body) -> Option<([f64; 3], [f64; 3])> {
|
||||
let mesh = brep::mesh::body(body, SAG, TOL);
|
||||
let mesh = tessellation(body).mesh;
|
||||
if mesh.positions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -165,7 +169,7 @@ pub fn extent(body: &Body) -> Option<([f64; 3], [f64; 3])> {
|
||||
/// is a set of Line entities either way. Each triangle the plane crosses
|
||||
/// contributes the one segment where it does.
|
||||
pub fn section(body: &Body, axis: usize, value: f64) -> Vec<([f64; 3], [f64; 3])> {
|
||||
let mesh = brep::mesh::body(body, SAG, TOL);
|
||||
let mesh = tessellation(body).mesh;
|
||||
let mut out = Vec::new();
|
||||
for triangle in &mesh.triangles {
|
||||
let corners: Vec<[f64; 3]> = triangle.iter().map(|i| mesh.positions[*i]).collect();
|
||||
@@ -201,16 +205,15 @@ pub fn section(body: &Body, axis: usize, value: f64) -> Vec<([f64; 3], [f64; 3])
|
||||
// ── Edge extraction (pick geometry + wireframe overlay) ─────────────────────
|
||||
|
||||
/// Tessellate the solid's B-rep edges into acadrust `Wire`s. Stored on the
|
||||
/// `Solid3D`/result entity so it is click-pickable (the renderer's wire
|
||||
/// fallback draws these as a wireframe over the shaded mesh, and hit-testing
|
||||
/// uses their points).
|
||||
/// `Solid3D`/result entity for picking.
|
||||
pub fn edge_wires(body: &Body) -> Vec<acadrust::entities::Wire> {
|
||||
use acadrust::types::Vector3;
|
||||
brep::edge_polylines(body, SAG)
|
||||
tessellation(body)
|
||||
.edges
|
||||
.into_iter()
|
||||
.map(|points| {
|
||||
.map(|edge| {
|
||||
acadrust::entities::Wire::from_points(
|
||||
points
|
||||
edge.positions
|
||||
.into_iter()
|
||||
.map(|p| Vector3::new(p[0], p[1], p[2]))
|
||||
.collect(),
|
||||
@@ -250,7 +253,9 @@ pub fn boolean(op: Bool, a: &Body, b: &Body) -> Option<Body> {
|
||||
/// Tessellate a `Body` into a single-LOD `MeshLodSet` (world-space, before
|
||||
/// world_offset is applied by the caller).
|
||||
pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
let mesh = brep::mesh::body(body, SAG, TOL);
|
||||
let tessellation = tessellation(body);
|
||||
let silhouette = tessellation.silhouette_source();
|
||||
let mesh = tessellation.mesh;
|
||||
if mesh.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -278,7 +283,7 @@ pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
.iter()
|
||||
.flat_map(|t| [t[0] as u32, t[1] as u32, t[2] as u32])
|
||||
.collect();
|
||||
Some(MeshLodSet::from_single(MeshModel {
|
||||
let mut set = MeshLodSet::from_single(MeshModel {
|
||||
name: String::new(),
|
||||
verts,
|
||||
verts_low,
|
||||
@@ -288,7 +293,23 @@ pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
triangle_colors: Vec::new(),
|
||||
color,
|
||||
selected: false,
|
||||
}))
|
||||
});
|
||||
for edge in tessellation.edges {
|
||||
for segment in edge.positions.windows(2) {
|
||||
for point in segment {
|
||||
let high = [point[0] as f32, point[1] as f32, point[2] as f32];
|
||||
set.edge_verts.push(high);
|
||||
set.edge_verts_low.push([
|
||||
(point[0] - high[0] as f64) as f32,
|
||||
(point[1] - high[1] as f64) as f32,
|
||||
(point[2] - high[2] as f64) as f32,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
set.complete = tessellation.missing_faces.is_empty();
|
||||
set.curved_gens.push(super::mesh_model::CurvedGen { source: silhouette });
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// The middle of a body, for a caller needing a point to turn or scale about.
|
||||
@@ -296,7 +317,7 @@ pub fn mesh_from_solid(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
/// Read off the mesh rather than `body_bounds`, which refuses a face that
|
||||
/// wraps a closed surface — a sphere is one such face and has no box at all.
|
||||
pub fn centre(body: &Body) -> Option<[f64; 3]> {
|
||||
let mesh = brep::mesh::body(body, SAG, TOL);
|
||||
let mesh = tessellation(body).mesh;
|
||||
if mesh.positions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -325,7 +346,7 @@ pub fn centre(body: &Body) -> Option<[f64; 3]> {
|
||||
#[cfg(test)]
|
||||
pub fn volume(body: &Body) -> f64 {
|
||||
use acadrust::kernel::space::Vec3;
|
||||
let mesh = brep::mesh::body(body, SAG, TOL);
|
||||
let mesh = tessellation(body).mesh;
|
||||
let Some(middle) = centre(body) else {
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
+68
-224
@@ -14,7 +14,7 @@
|
||||
|
||||
use acadrust::kernel::brep::{self, Body};
|
||||
use acadrust::kernel::geom2d::Curve;
|
||||
use acadrust::kernel::space::{PlanarCurve, Plane, Vec3};
|
||||
use acadrust::kernel::space::{PlanarCurve, Plane};
|
||||
use acadrust::EntityType;
|
||||
|
||||
use crate::entities::curve::entity_curve;
|
||||
@@ -47,7 +47,7 @@ pub fn profile_of(entity: &EntityType) -> Option<Profile> {
|
||||
// both the fewest pieces a chain may have and the fewest that leave
|
||||
// each one unambiguous about which way round it goes.
|
||||
Curve::Circle(circle) => quarters(circle.centre, circle.radius),
|
||||
other => split_evenly(other, 4),
|
||||
_ => return None,
|
||||
};
|
||||
(pieces.len() >= 3).then_some(Profile {
|
||||
plane: planar.plane,
|
||||
@@ -72,21 +72,6 @@ fn quarters(centre: [f64; 2], radius: f64) -> Vec<Curve> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Any other closed curve as `count` straight pieces between points on it.
|
||||
///
|
||||
/// The honest fallback: an ellipse or a spline has no analytic sweep, so the
|
||||
/// kernel would refuse the exact form anyway. Chords at least say plainly
|
||||
/// what they are.
|
||||
fn split_evenly(curve: &Curve, count: usize) -> Vec<Curve> {
|
||||
use acadrust::kernel::geom2d::Line;
|
||||
(0..count)
|
||||
.map(|step| Curve::Line(Line {
|
||||
start: curve.point_at(step as f64 / count as f64),
|
||||
end: curve.point_at((step + 1) as f64 / count as f64),
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// EXTRUDE: drag the profile `height` along its own plane's normal.
|
||||
///
|
||||
/// `None` for a profile that does not close, encloses nothing, or holds a
|
||||
@@ -123,226 +108,85 @@ pub fn revolved(
|
||||
)
|
||||
}
|
||||
|
||||
// ── SWEEP and LOFT ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// Neither keeps a B-rep — both have only ever produced a mesh — so both are
|
||||
// built from point lists rather than from topology. A profile becomes the
|
||||
// points its own curve tessellates to, which is the same source EXTRUDE and
|
||||
// REVOLVE read, so a circle stays round here too.
|
||||
|
||||
use crate::entities::curve::curve_points;
|
||||
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||
|
||||
/// The points a profile entity traces, and whether it closes.
|
||||
fn outline(entity: &EntityType) -> Option<(Vec<[f64; 3]>, bool)> {
|
||||
let planar = entity_curve(entity)?;
|
||||
let closed = planar.curve.is_closed();
|
||||
let mut points = curve_points(&planar);
|
||||
// A closed curve tessellates back to its own start. Carrying the repeat
|
||||
// would put a zero-width quad in every strip below.
|
||||
if closed && points.len() > 1 {
|
||||
let first = points[0];
|
||||
let last = points[points.len() - 1];
|
||||
if Vec3::from(first).distance(Vec3::from(last)) < 1e-9 {
|
||||
points.pop();
|
||||
}
|
||||
}
|
||||
(points.len() >= 2).then_some((points, closed))
|
||||
}
|
||||
|
||||
/// SWEEP: drag a profile along a path.
|
||||
///
|
||||
/// The path contributes its direction and length, not its shape — which is
|
||||
/// what SWEEP has always done here, and what makes it an extrusion along an
|
||||
/// arbitrary vector rather than along a curve.
|
||||
/// SWEEP through the kernel's tolerance-driven mesh API.
|
||||
pub fn swept(profile: &EntityType, path: &EntityType, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
let (points, closed) = outline(profile)?;
|
||||
let along = {
|
||||
let track = curve_points(&entity_curve(path)?);
|
||||
let (from, to) = (Vec3::from(*track.first()?), Vec3::from(*track.last()?));
|
||||
to - from
|
||||
};
|
||||
if along.length() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
let moved: Vec<[f64; 3]> = points
|
||||
.iter()
|
||||
.map(|point| (Vec3::from(*point) + along).to_array())
|
||||
.collect();
|
||||
let mut mesh = Ribbon::default();
|
||||
mesh.band(&points, &moved, closed);
|
||||
if closed {
|
||||
// An open profile sweeps into a sheet with nothing to cap.
|
||||
mesh.cap(&points, true);
|
||||
mesh.cap(&moved, false);
|
||||
}
|
||||
mesh.finish(color)
|
||||
let tolerance = crate::scene::convert::curve_tol::current_curve_tol();
|
||||
let surface = brep::mesh::sweep_surface(
|
||||
&entity_curve(profile)?,
|
||||
&entity_curve(path)?,
|
||||
tolerance,
|
||||
)?;
|
||||
mesh_set(surface, color, tolerance)
|
||||
}
|
||||
|
||||
/// LOFT: rule a surface through a run of profiles.
|
||||
///
|
||||
/// Consecutive profiles are joined by a band each, and the two ends are
|
||||
/// capped when they close. Profiles with different point counts are resampled
|
||||
/// onto the finer of the two, so a circle lofted to a square does not twist.
|
||||
/// LOFT through the kernel's tolerance-driven mesh API.
|
||||
pub fn lofted(profiles: &[EntityType], color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
let sections: Vec<(Vec<[f64; 3]>, bool)> = profiles.iter().filter_map(outline).collect();
|
||||
if sections.len() < 2 {
|
||||
let curves: Vec<PlanarCurve> = profiles.iter().filter_map(entity_curve).collect();
|
||||
let tolerance = crate::scene::convert::curve_tol::current_curve_tol();
|
||||
mesh_set(brep::mesh::loft_surface(&curves, tolerance)?, color, tolerance)
|
||||
}
|
||||
|
||||
fn mesh_set(
|
||||
surface: brep::mesh::SurfaceMesh,
|
||||
color: [f32; 4],
|
||||
tolerance: f64,
|
||||
) -> Option<MeshLodSet> {
|
||||
if surface.mesh.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut mesh = Ribbon::default();
|
||||
for pair in sections.windows(2) {
|
||||
let count = pair[0].0.len().max(pair[1].0.len());
|
||||
let closed = pair[0].1 && pair[1].1;
|
||||
let lower = resampled(&pair[0].0, count, pair[0].1);
|
||||
let upper = resampled(&pair[1].0, count, pair[1].1);
|
||||
mesh.band(&lower, &upper, closed);
|
||||
let mut verts = Vec::with_capacity(surface.mesh.positions.len());
|
||||
let mut verts_low = Vec::with_capacity(surface.mesh.positions.len());
|
||||
for point in &surface.mesh.positions {
|
||||
push_point(&mut verts, &mut verts_low, *point);
|
||||
}
|
||||
if sections.first()?.1 {
|
||||
mesh.cap(§ions.first()?.0, true);
|
||||
}
|
||||
if sections.last()?.1 {
|
||||
mesh.cap(§ions.last()?.0, false);
|
||||
}
|
||||
mesh.finish(color)
|
||||
}
|
||||
|
||||
/// A ring walked in `count` even steps along its own length.
|
||||
///
|
||||
/// Even by distance rather than by index: two profiles given at different
|
||||
/// densities line up where they are, so a band between them does not twist
|
||||
/// wherever one of them happened to be sampled more finely.
|
||||
fn resampled(points: &[[f64; 3]], count: usize, closed: bool) -> Vec<[f64; 3]> {
|
||||
let mut ring: Vec<Vec3> = points.iter().map(|point| Vec3::from(*point)).collect();
|
||||
if closed {
|
||||
ring.push(ring[0]);
|
||||
}
|
||||
let mut walked = vec![0.0];
|
||||
for pair in ring.windows(2) {
|
||||
walked.push(walked[walked.len() - 1] + pair[0].distance(pair[1]));
|
||||
}
|
||||
let total = *walked.last().unwrap_or(&0.0);
|
||||
if total <= 0.0 {
|
||||
return points.to_vec();
|
||||
}
|
||||
let steps = if closed { count } else { count.max(2) - 1 };
|
||||
(0..if closed { count } else { count.max(2) })
|
||||
.map(|step| {
|
||||
let want = total * step as f64 / steps as f64;
|
||||
let at = walked
|
||||
.iter()
|
||||
.rposition(|reached| *reached <= want)
|
||||
.unwrap_or(0)
|
||||
.min(ring.len() - 2);
|
||||
let span = walked[at + 1] - walked[at];
|
||||
let along = if span > 0.0 { (want - walked[at]) / span } else { 0.0 };
|
||||
ring[at].lerp(ring[at + 1], along).to_array()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Triangles being gathered from bands and caps.
|
||||
#[derive(Default)]
|
||||
struct Ribbon {
|
||||
positions: Vec<[f64; 3]>,
|
||||
normals: Vec<[f64; 3]>,
|
||||
triangles: Vec<[u32; 3]>,
|
||||
}
|
||||
|
||||
impl Ribbon {
|
||||
/// A strip of quads between two rings of the same length.
|
||||
fn band(&mut self, lower: &[[f64; 3]], upper: &[[f64; 3]], closed: bool) {
|
||||
let count = lower.len().min(upper.len());
|
||||
if count < 2 {
|
||||
return;
|
||||
}
|
||||
let spans = if closed { count } else { count - 1 };
|
||||
for step in 0..spans {
|
||||
let next = (step + 1) % count;
|
||||
self.quad(lower[step], lower[next], upper[next], upper[step]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A flat lid over a closed ring, fanned from its middle.
|
||||
///
|
||||
/// A fan rather than a proper triangulation: a lofted section can be
|
||||
/// concave and a fan would then cover ground outside it, but every
|
||||
/// profile these commands accept is a single closed curve, and the middle
|
||||
/// of one is inside it.
|
||||
fn cap(&mut self, ring: &[[f64; 3]], downward: bool) {
|
||||
if ring.len() < 3 {
|
||||
return;
|
||||
}
|
||||
let mut middle = Vec3::new(0.0, 0.0, 0.0);
|
||||
for point in ring {
|
||||
middle = middle + Vec3::from(*point);
|
||||
}
|
||||
let middle = (middle / ring.len() as f64).to_array();
|
||||
for step in 0..ring.len() {
|
||||
let next = (step + 1) % ring.len();
|
||||
if downward {
|
||||
self.triangle(middle, ring[next], ring[step]);
|
||||
} else {
|
||||
self.triangle(middle, ring[step], ring[next]);
|
||||
let silhouette = surface.silhouette_source(tolerance);
|
||||
let mut set = MeshLodSet::from_single(MeshModel {
|
||||
name: String::new(),
|
||||
verts,
|
||||
verts_low,
|
||||
normals: surface
|
||||
.mesh
|
||||
.normals
|
||||
.iter()
|
||||
.map(|normal| [normal[0] as f32, normal[1] as f32, normal[2] as f32])
|
||||
.collect(),
|
||||
indices: surface
|
||||
.mesh
|
||||
.triangles
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|index| *index as u32)
|
||||
.collect(),
|
||||
triangle_material_handles: Vec::new(),
|
||||
triangle_colors: Vec::new(),
|
||||
color,
|
||||
selected: false,
|
||||
});
|
||||
{
|
||||
let (high, low) = (&mut set.edge_verts, &mut set.edge_verts_low);
|
||||
for edge in surface.edges {
|
||||
for segment in edge.windows(2) {
|
||||
for point in segment {
|
||||
push_point(high, low, *point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set.curved_gens
|
||||
.push(super::mesh_model::CurvedGen { source: silhouette });
|
||||
Some(set)
|
||||
}
|
||||
|
||||
fn quad(&mut self, a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3]) {
|
||||
self.triangle(a, b, c);
|
||||
self.triangle(a, c, d);
|
||||
}
|
||||
|
||||
fn triangle(&mut self, a: [f64; 3], b: [f64; 3], c: [f64; 3]) {
|
||||
let Some(normal) = (Vec3::from(b) - Vec3::from(a))
|
||||
.cross(Vec3::from(c) - Vec3::from(a))
|
||||
.normalize()
|
||||
else {
|
||||
// Collapsed: no normal, and nothing to draw.
|
||||
return;
|
||||
};
|
||||
let base = self.positions.len() as u32;
|
||||
for corner in [a, b, c] {
|
||||
self.positions.push(corner);
|
||||
self.normals.push(normal.to_array());
|
||||
}
|
||||
self.triangles.push([base, base + 1, base + 2]);
|
||||
}
|
||||
|
||||
/// The gathered triangles as the renderer's mesh, or `None` for none.
|
||||
fn finish(self, color: [f32; 4]) -> Option<MeshLodSet> {
|
||||
if self.triangles.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// The renderer holds each position as a coarse float plus a fine
|
||||
// correction, so a profile at survey coordinates keeps its last
|
||||
// millimetres instead of losing them to f32.
|
||||
let mut verts = Vec::with_capacity(self.positions.len());
|
||||
let mut verts_low = Vec::with_capacity(self.positions.len());
|
||||
for point in &self.positions {
|
||||
let high = [point[0] as f32, point[1] as f32, point[2] as f32];
|
||||
verts.push(high);
|
||||
verts_low.push([
|
||||
(point[0] - high[0] as f64) as f32,
|
||||
(point[1] - high[1] as f64) as f32,
|
||||
(point[2] - high[2] as f64) as f32,
|
||||
]);
|
||||
}
|
||||
Some(MeshLodSet::from_single(MeshModel {
|
||||
name: String::new(),
|
||||
verts,
|
||||
verts_low,
|
||||
normals: self
|
||||
.normals
|
||||
.iter()
|
||||
.map(|n| [n[0] as f32, n[1] as f32, n[2] as f32])
|
||||
.collect(),
|
||||
indices: self.triangles.iter().flatten().copied().collect(),
|
||||
triangle_material_handles: Vec::new(),
|
||||
triangle_colors: Vec::new(),
|
||||
color,
|
||||
selected: false,
|
||||
}))
|
||||
}
|
||||
fn push_point(high: &mut Vec<[f32; 3]>, low: &mut Vec<[f32; 3]>, point: [f64; 3]) {
|
||||
let coarse = [point[0] as f32, point[1] as f32, point[2] as f32];
|
||||
high.push(coarse);
|
||||
low.push([
|
||||
(point[0] - coarse[0] as f64) as f32,
|
||||
(point[1] - coarse[1] as f64) as f32,
|
||||
(point[2] - coarse[2] as f64) as f32,
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user