Files
OneCAD-Draft/src/scene/section_cull.rs
T

224 lines
8.5 KiB
Rust

//! SECTIONCULL: keep only what lies inside a section object — the part a
//! live section shows — and remove the rest for good. Solids, surfaces and
//! closed meshes are cut to it, open meshes are clipped face by face, and
//! every other object wholly outside it is erased. Objects across its
//! boundary that cannot be cut stay whole.
use codec::{EntityType, Handle};
use glam::DVec3;
use rustc_hash::FxHashMap;
use super::Scene;
/// What culling to a section does to the space it lives in.
#[derive(Default)]
pub struct SectionCull {
/// Bodies the section crosses, with what stays of each.
pub cut: Vec<(Handle, kernel::brep::Body)>,
/// Open meshes the section crosses, clipped to it.
pub clipped: Vec<EntityType>,
/// Objects wholly outside the section.
pub outside: Vec<Handle>,
}
enum Place {
Inside,
Outside,
Across,
}
const TOLERANCE: f64 = 1e-9;
fn place(planes: &[kernel::space::Plane], points: &[[f64; 3]]) -> Place {
let mut inside = true;
for plane in planes {
let behind = points
.iter()
.filter(|point| plane.distance_to(**point).unwrap_or(0.0) < -TOLERANCE)
.count();
if behind == points.len() {
return Place::Outside;
}
inside &= behind == 0;
}
if inside {
Place::Inside
} else {
Place::Across
}
}
fn same_extent(a: &kernel::brep::Body, b: &kernel::brep::Body) -> bool {
match (kernel::brep::body_bounds(a), kernel::brep::body_bounds(b)) {
(Some(a), Some(b)) => (0..3).all(|axis| {
(a.min[axis] - b.min[axis]).abs() <= 1e-7 && (a.max[axis] - b.max[axis]).abs() <= 1e-7
}),
_ => false,
}
}
impl Scene {
/// The section SECTIONCULL works with: the selected section object, or
/// else the live section of the current space.
pub fn cull_section(&self) -> Option<Handle> {
let is_section = |handle: &Handle| {
matches!(
self.document.get_entity(*handle),
Some(EntityType::Extended(extended))
if matches!(
extended.data,
codec::entities::ExtendedEntityData::SectionObject(_)
)
)
};
if let Some(handle) = self
.selected_handles_in_order()
.into_iter()
.find(is_section)
{
return Some(handle);
}
let block = self.current_layout_block_handle();
self.document
.entities()
.filter(|entity| match entity {
EntityType::Extended(extended) => match &extended.data {
codec::entities::ExtendedEntityData::SectionObject(data) => {
let owner = extended.common.owner_handle;
data.flags & 1 != 0
&& !extended.common.invisible
&& (owner.is_null() || owner == block)
}
_ => false,
},
_ => false,
})
.map(|entity| entity.common().handle)
.last()
}
/// Plan culling the current space to section object `section`; `None`
/// when it is not a section object.
pub fn section_cull(&self, section: Handle) -> Option<SectionCull> {
let Some(EntityType::Extended(extended)) = self.document.get_entity(section) else {
return None;
};
let codec::entities::ExtendedEntityData::SectionObject(data) = &extended.data else {
return None;
};
let slice_depth = crate::entities::extended::section_is_slice(extended)
.then(|| crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0));
let planes = Self::section_planes(data, slice_depth, None)?;
// Every drawn point of every object in the space, from its wires and
// its shaded mesh.
let block = self.current_layout_block_handle();
let mut points: FxHashMap<Handle, Vec<[f64; 3]>> = FxHashMap::default();
let wires = self.wires_for_block_culled(block, None, None, None, None, None, true, None);
for wire in &wires {
let Some(handle) = Self::handle_from_wire_name(&wire.name) else {
continue;
};
let entry = points.entry(handle).or_default();
for (index, &[x, y, z]) in wire.points.iter().enumerate() {
let [lx, ly, lz] = wire.points_low.get(index).copied().unwrap_or([0.0; 3]);
let point = DVec3::new(
f64::from(x) + f64::from(lx),
f64::from(y) + f64::from(ly),
f64::from(z) + f64::from(lz),
);
if point.is_finite() {
entry.push(point.to_array());
}
}
}
for set in self.interaction_meshes_arc().iter() {
let handle = set.entity_handle();
let (Some(handle), Some(aabb)) = (handle, super::mesh_interaction_aabb(set)) else {
continue;
};
let entry = points.entry(handle).or_default();
for corner in 0..8 {
entry.push([
aabb[if corner & 1 == 0 { 0 } else { 3 }],
aabb[if corner & 2 == 0 { 1 } else { 4 }],
aabb[if corner & 4 == 0 { 2 } else { 5 }],
]);
}
}
let mut cull = SectionCull::default();
// A live section hides what lies wholly outside it, so the space's
// own objects are walked too.
let mut handles: Vec<Handle> = points
.keys()
.copied()
.chain(
self.document
.entities()
.filter(|entity| entity.common().owner_handle == block)
.map(|entity| entity.common().handle),
)
.collect();
handles.sort_unstable_by_key(|handle| handle.value());
handles.dedup();
for handle in handles {
if handle == section || self.is_layer_locked(handle) {
continue;
}
let Some(entity) = self.document.get_entity(handle) else {
continue;
};
// Bodies are cut with the kernel: their drawn wires may already
// show the live cut, so they say nothing about the source.
if let Some(body) = self.section_source_body(handle) {
match Self::section_body(&body, data, slice_depth, None) {
Ok(None) => cull.outside.push(handle),
Ok(Some(kept)) if !same_extent(&body, &kept) => cull.cut.push((handle, kept)),
_ => {}
}
continue;
}
if let Some((vertices, _)) = crate::entities::mesh::mesh_polygons(entity) {
match place(&planes, &vertices) {
Place::Outside => cull.outside.push(handle),
Place::Inside => {}
Place::Across => {
let mut mesh = entity.clone();
for plane in &planes {
let Some((vertices, faces)) =
crate::entities::mesh::mesh_polygons(&mesh)
else {
break;
};
let side = |point: [f64; 3]| plane.distance_to(point).unwrap_or(0.0);
if vertices.iter().all(|point| side(*point) >= -TOLERANCE) {
continue;
}
let Some(mut clipped) = crate::entities::mesh::clipped_mesh(
&vertices,
&faces,
side,
true,
mesh.common(),
) else {
break;
};
clipped.common.handle = handle;
mesh = EntityType::Mesh(clipped);
}
cull.clipped.push(mesh);
}
}
continue;
}
if let Some(points) = points.get(&handle) {
if matches!(place(&planes, points), Place::Outside) {
cull.outside.push(handle);
}
}
}
Some(cull)
}
}