Skip to main content

Mesh

Struct Mesh 

Source
pub struct Mesh {
    pub positions: Vec<f32>,
    pub normals: Vec<f32>,
    pub indices: Vec<u32>,
    pub rtc_applied: bool,
    pub origin: [f64; 3],
    pub instance_meta: Option<InstanceMeta>,
    pub local_bounds: Option<[f32; 6]>,
    pub local_to_world: Option<[f64; 16]>,
}
Expand description

Triangle mesh

Fields§

§positions: Vec<f32>

Vertex positions (x, y, z)

§normals: Vec<f32>

Vertex normals (nx, ny, nz)

§indices: Vec<u32>

Triangle indices (i0, i1, i2)

§rtc_applied: bool

Whether RTC offset has already been subtracted from positions. Set by FacetedBrepProcessor::process_with_rtc to prevent transform_mesh from double-subtracting RTC.

§origin: [f64; 3]

Per-mesh local origin (f64), in the RTC/world frame. When non-zero, positions are stored RELATIVE to this origin (so they stay small and f32-precise regardless of the element’s world placement), and the world position of a vertex is origin + position. Set by transform_mesh_world to the element’s centroid so building-scale coordinates (~hundreds of metres) never collapse adjacent vertices to bit-identical f32. Default [0, 0, 0] means positions are already absolute (legacy/local meshes).

§instance_meta: Option<InstanceMeta>

Instancing side-channel (see InstanceMeta); None on the flat path.

§local_bounds: Option<[f32; 6]>

Local (pre-placement, object-space) AABB — positions bounds as they were BEFORE apply_placement’s transform was baked in. None for an empty mesh or one that never went through transform_mesh_world_framed (e.g. synthetic/test meshes). Unrelated to origin, which is a world-space translation captured AFTER the transform, purely for f32 precision — see issue #1474.

§local_to_world: Option<[f64; 16]>

The resolved IfcLocalPlacement chain applied to this mesh by apply_placement (row-major, same convention as InstanceMeta::transform). None when no placement was applied (synthetic/test meshes) — see issue #1474.

Implementations§

Source§

impl Mesh

Source

pub fn new() -> Self

Create a new empty mesh

Source

pub fn with_capacity(vertex_count: usize, index_count: usize) -> Self

Create a mesh with capacity

Source

pub fn rebuilt_like( &self, positions: Vec<f32>, normals: Vec<f32>, indices: Vec<u32>, ) -> Mesh

Build a mesh with FRESH geometry buffers (positions / normals / indices) that carries THIS mesh’s placement/frame metadata forward: origin (RTC / local-frame translation), rtc_applied, local_bounds and local_to_world (the #1474 placement capture).

This is the correct constructor for an in-place rebuild pass that REPLACES the vertex buffers of an already-placed mesh (sliver refine, subdivide, weld). Constructing a bare Mesh and copying back only a field or two silently resets origin and the #1474 capture to their defaults, which mis-places the rebuilt host at the world origin on local-framed (large / georeferenced) models — see facet_weld’s sliver-refine and this module’s subdivide_once / weld_impl.

instance_meta is intentionally NOT carried. Every such rebuild CHANGES the vertices, so the mesh no longer reproduces its representation’s canonical geometry; carrying the (vertex-invariant) rep_identity forward would let the GPU-instancing collator dedup this changed mesh against an unrefined sibling that shares the same rep_identity and draw the wrong geometry. Dropping it mirrors the void-cut path, which nulls instance_meta for exactly this reason.

§Precondition

The new buffers MUST NOT extend the mesh’s spatial extent beyond the original: the carried local_bounds stays valid only because it remains a superset of the rebuilt vertices’ extent. This holds for every current caller — sliver-refine and subdivide insert edge/interior midpoints (convex combinations that lie inside the existing hull), and weld only merges/moves coincident vertices to a snapped position (a subset extent). A future caller that GROWS the extent (adds vertices outside the original hull) must NOT use this constructor for local_bounds: it has to recompute local_bounds from the new positions or pass through a variant that sets it to None.

Source

pub fn from_triangle( v0: &Point3<f64>, v1: &Point3<f64>, v2: &Point3<f64>, normal: &Vector3<f64>, ) -> Self

Create a mesh from a single triangle

Source

pub fn add_vertex(&mut self, position: Point3<f64>, normal: Vector3<f64>)

Add a vertex with normal

Source

pub fn add_triangle(&mut self, i0: u32, i1: u32, i2: u32)

Add a triangle

Source

pub fn merge(&mut self, other: &Mesh)

Merge another mesh into this one.

Positions are stored relative to origin. The common case is merging local/origin-zero meshes (sub-meshes combined BEFORE the world transform), where origins match and concatenation is exact. If the two meshes carry different non-zero origins, other is rebased into self’s frame so the merged positions stay consistent (correct, though large-coordinate if the origins are far apart — which the pre-transform merge order avoids).

Source

pub fn merge_all(&mut self, meshes: &[Mesh])

Batch merge multiple meshes at once (more efficient than individual merges)

Source

pub fn vertex_count(&self) -> usize

Get vertex count

Source

pub fn triangle_count(&self) -> usize

Get triangle count

Source

pub fn subdivided(&self, levels: usize) -> Mesh

Uniform 1→4 midpoint subdivision applied levels times. Each triangle is split into four by its three edge midpoints; midpoint positions/normals are the f32 average of the edge endpoints (commutative ⇒ a shared edge yields the SAME midpoint from either adjacent triangle, so the result stays watertight once the kernel’s interner welds coincident vertices).

Purpose: a host face that is one or two huge triangles concentrates ALL of a wall’s opening cuts onto it, so the exact arrangement re-triangulates a single triangle carrying dozens of constraint segments — O(k²) and, worse, dense enough that the batched N-ary subtract leaves unrecovered constraints and falls back to the O(N²) sequential path. Spreading the face into many small triangles localises each opening to a few of them (small k), so the batched cut recovers. consolidate_coplanar re-triangulates each coplanar group afterwards, so the extra interior vertices do not survive into the final mesh except where a hole boundary pins them.

Source

pub fn validate_indices(&mut self)

Remove triangle indices that reference vertices beyond the positions array. This prevents panics from malformed IFC data (e.g. Revit exports with invalid indices).

Source

pub fn drop_degenerate_triangles(&mut self)

Drop triangles that collapsed into degenerate needles when the mesh was stored at f32 precision.

At building-scale world coordinates (e.g. ~220 m) an f32 mantissa only resolves ~15 µm, so two genuinely-distinct vertices less than one ULP apart round to the same (or near-same) f32 value. The triangle that joined them becomes a zero-area sliver — and when its third vertex is far away, a long thin “fan” that visibly spans the model (the gross corruption seen on large georeferenced buildings).

These slivers carry effectively no area, so the neighbouring triangles of the same face already cover the surface; removing them is visually lossless while eliminating the fans. The proper fix (local-frame / tiled vertex storage) keeps the vertices distinct in the first place; this is the backstop for meshes that still arrive degenerate.

Conservative by design — only drops triangles that are unambiguously garbage: a bit-identical f32 vertex pair (exact zero area) or an aspect ratio (longest edge / shortest edge) above 1e5. Legitimate thin members (mullions, braces) sit far below that. Only indices change; the vertex buffer and per-vertex data are left intact, so the operation is deterministic and keeps vertex indices stable.

Source

pub fn is_empty(&self) -> bool

Check if mesh is empty

Source

pub fn bounds(&self) -> (Point3<f32>, Point3<f32>)

Calculate bounds (min, max) - optimized with chunk iteration

Source

pub fn centroid_f64(&self) -> Point3<f64>

Calculate centroid in f64 precision (for RTC offset calculation) Returns the average of all vertex positions

Source

pub fn clear(&mut self)

Clear the mesh

Source

pub fn welded_by_position(&self, position_eps: f32) -> Mesh

Weld vertices that share a position, regardless of normal.

Returns a new mesh where vertices at the same position (within position_eps) collapse to one canonical vertex; the welded vertex’s normal is the sum of contributing normals, re-normalized (or a neutral up-Z (0, 0, 1) default if the sum is degenerate, e.g. exactly opposing normals cancelling out). Triangles that collapse to a degenerate edge or point are dropped.

Use this when you need a topologically connected, manifold- candidate mesh — volume queries, CSG operands, watertight checks, mesh repair pipelines. Shading at sharp corners gets averaged.

position_eps is the bucket size in metres (1 µm is a safe default for IFC).

Source

pub fn drop_thin_triangles(&mut self, h_eps: f64)

Drop triangles whose perpendicular height (= 2·area / longest edge) is below h_eps metres — i.e. genuinely-degenerate collinear slivers (three distinct but near-collinear vertices, zero area). These come from redundant collinear vertices in source brep faces / extrusion profiles triangulated as-is; vertex welding can’t merge them (the vertices are distinct), so this catches them. At h_eps ≈ 15 µm — far below any real architectural feature — the dropped triangles carry no area, so the surrounding triangulation still covers the face (visually lossless, watertight-preserving). Only indices change.

Source

pub fn clean_degenerate(&mut self)

Mesh hygiene applied to every element mesh before it leaves the router.

Restores the cleanup the pure-Rust pipeline lost when #1024 removed Manifold (which implicitly dropped degenerate output). Without it, redundant/near-collinear source vertices in faceted breps and extrusion profiles get triangulated into visible needle “spikes” and jagged silhouettes (the regression reported on large breps); BIMcollab and other viewers don’t show them because they clean degenerates on import.

Deliberately does not weld vertices. The pipeline emits per-face flat-shaded facet soup on purpose (each facet keeps its own vertices + normal so creases stay sharp — see issue #846); welding would share vertices across facets and re-smooth every crease. Instead we drop only the genuinely-degenerate triangles via drop_thin_triangles below the kernel’s reconcile grid (1/65536 ≈ 15.3 µm): coincident-pair needles (area 0) and collinear slivers (three distinct near-collinear vertices). The grid is the kernel’s own representable resolution, so sub-grid triangles are degenerate by definition; measured triangle counts are flat from 10–50 µm and only start touching real geometry at ~100 µm (6.5× higher), confirming nothing real lives in that band. Positions/normals are left untouched, so it is visually lossless and bit-deterministic.

The 15.3 µm threshold is most precise when applied in a small-magnitude (element-local) frame, where f32 positions resolve well below it — which the tessellation chokepoints honour (they clean before world placement). The void-cut output is cleaned in world coordinates (the cut runs there), so on a model georeferenced a few hundred metres to ~10 km from origin — below the RTC re-basing threshold — the f32 grid at that magnitude approaches the threshold and the margin near opening seams erodes slightly; the longest <= 0 guard still catches full collapse at extreme scale. NaN/Inf triangles are kept (the comparison is false), i.e. non-finite geometry is left for upstream to handle, never dropped.

Source

pub fn clip_triangles_to_aabb( &mut self, min: [f32; 3], max: [f32; 3], pad: f32, ) -> usize

Drop triangles with ANY vertex outside [min - pad, max + pad], then compact away the now-unreferenced vertices. Returns the count dropped.

Boolean subtraction can only REMOVE material, so the cut of a host whose pre-cut AABB is [min, max] is mathematically contained in that AABB. A malformed cutter — self-intersecting, or carrying garbage vertices metres from the real opening (e.g. an exporter that welds stray points into a tessellated void, the multi-body-cutter case) — can make the exact mesh-arrangement leak a spurious far-flung “flap” triangle into the output: a visible spike poking metres out of the wall. Such a triangle only appears once a SECOND cutter perturbs the arrangement, so it slips past the per-cutter admission guards. Any output vertex beyond the host AABB (past pad, which absorbs kernel snap / f64→f32 round-trip jitter) is provably such an artifact, so the triangle is dropped and its orphaned vertices removed (they would otherwise skew bounds() and every AABB-derived consumer: framing, picking, clash, export).

A no-op on clean cuts — when nothing lies outside, positions/normals are left bit-identical so the frozen snapshot corpus is unperturbed. Also a no-op in the degenerate case where EVERY triangle would be dropped (an upstream frame/placement bug, not a cut artifact): the mesh is preserved rather than silently emptied.

Source

pub fn clip_triangles_to_host_aabb( &mut self, min: [f32; 3], max: [f32; 3], ) -> usize

Clip a void-cut result to the host’s pre-cut AABB [min, max], dropping any triangle poking beyond it (see Mesh::clip_triangles_to_aabb). A subtract can only remove material, so anything past the host AABB is a cut artifact. The tolerance absorbs f64→f32 round-trip jitter (sub-mm), so it is a small ABSOLUTE band, NOT a fraction of host size: an unbounded 1e-3 * diag reaches 0.13 m on a 130 m floor slab — wider than the ~0.105 m flush-cap reveal overhang it must trap, which is why only large slabs/roofs leaked it (a 5 m wall’s 5 mm pad already trims the identical overhang, #1633). Clamped to [5 mm, 10 mm]: byte-identical to the former 1e-3 * diag for hosts ≤ 10 m diagonal (1e-3 * diag ≤ 1e-2), trimming on every larger one. Returns the count dropped.

Trait Implementations§

Source§

impl Clone for Mesh

Source§

fn clone(&self) -> Mesh

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Mesh

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Mesh

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Mesh

§

impl RefUnwindSafe for Mesh

§

impl Send for Mesh

§

impl Sync for Mesh

§

impl Unpin for Mesh

§

impl UnsafeUnpin for Mesh

§

impl UnwindSafe for Mesh

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<U> As for U

Source§

fn as_<T>(self) -> T
where T: CastFrom<U>, U: Sized,

Casts self to type T. The semantics of numeric casting with the as operator are followed, so <T as As>::as_::<U> can be used in the same way as T as U for numeric conversions. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.