Complete 2D solid workflow

This commit is contained in:
ramox81 committed 2026-08-21 10:14:26 +03:00
1 parent 573ed3245e
commit 17dc0aff39
11 files changed
+257 -181

No files matched your search

+1 -1
View File
@@ -1260,7 +1260,7 @@ pub fn tessellate(
| EntityType::PolyfaceMesh(_)
| EntityType::PolygonMesh(_)
| EntityType::Mesh(_)
);
) || matches!(entity, EntityType::Solid(solid) if solid.thickness.abs() > 1.0e-10);
// Thickness walls ride on the wire that carries their edges, not
// on a wire of their own: they are pick geometry for that entity,
// and `fill_tris` below deliberately splits off into a fill-only
+3 -3
View File
@@ -2158,8 +2158,8 @@ impl Scene {
}
/// Build a solid-fill HatchModel for a DXF Solid entity.
/// Conventional DXF SOLID corners use Z-order; legacy entities may already
/// be in perimeter order. Use the same non-crossing resolver as wire fill.
/// SOLID corners use Z-order. Preserve it in the projected hatch as well so
/// intentionally crossing geometry is not silently rewritten.
pub(super) fn solid_hatch_model(solid: &DxfSolid, color: [f32; 4]) -> HatchModel {
// Keep the corners in f64 until the AABB centre is known, then store
// each as a small f32 offset from it — same precision-preserving anchor
@@ -2167,7 +2167,7 @@ impl Scene {
// to f32 costs ~0.06 units of resolution at UTM magnitudes (~1e6), so
// the quad snapped to a grid and the fill drifted off its outline.
let wcs = crate::entities::solid::wcs_corners(solid);
let order = crate::entities::solid::perimeter_indices(&wcs);
let order = [0, 1, 3, 2];
let corners: [[f64; 2]; 4] = order.map(|index| [wcs[index][0], wcs[index][1]]);
let mut min = [f64::INFINITY; 2];
let mut max = [f64::NEG_INFINITY; 2];
+21
View File
@@ -22,6 +22,20 @@ use crate::scene::model::wire_model::WireModel;
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
if !wire.fill_is_2d_solid || wire.fill_is_3d {
return true;
}
let view = view_dir.normalize_or_zero();
wire.fill_tris.chunks_exact(3).any(|triangle| {
let first = glam::Vec3::from_array(triangle[0]);
let second = glam::Vec3::from_array(triangle[1]);
let third = glam::Vec3::from_array(triangle[2]);
let normal = (second - first).cross(third - first).normalize_or_zero();
normal.length_squared() > 0.0 && normal.dot(view).abs() >= 1.0 - 1.0e-5
})
}
// ── Vertex layout ──────────────────────────────────────────────────────────
#[repr(C)]
@@ -149,6 +163,7 @@ impl Face3DGpu {
all_wires: &[WireModel],
keep_3d_mesh_fills: bool,
show_2d_solid_fills: bool,
view_dir: glam::Vec3,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> Self {
let depth_of =
@@ -200,6 +215,9 @@ impl Face3DGpu {
if !show_2d_solid_fills && wire.fill_is_2d_solid {
continue;
}
if !planar_solid_faces_view(wire, view_dir) {
continue;
}
// A real 3-D surface fill (PolyfaceMesh / PolygonMesh) carries a
// double-single low residual paired with `fill_tris` — it lives at
// true world coordinates and must keep its real depth. 2-D fills
@@ -259,6 +277,7 @@ impl Face3DGpu {
all_wires,
keep_3d_mesh_fills,
show_2d_solid_fills,
view_dir,
depth_map,
);
Self {
@@ -275,6 +294,7 @@ fn upload_block_chunks(
wires: &[WireModel],
keep_3d_mesh_fills: bool,
show_2d_solid_fills: bool,
view_dir: glam::Vec3,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> (Vec<BlockFace3DChunk>, Vec<BlockFace3DChunk>) {
let mut slots = rustc_hash::FxHashMap::default();
@@ -285,6 +305,7 @@ fn upload_block_chunks(
};
if wire.fill_tris.is_empty()
|| (!show_2d_solid_fills && wire.fill_is_2d_solid)
|| !planar_solid_faces_view(wire, view_dir)
|| (wire.fill_is_3d && !keep_3d_mesh_fills)
{
continue;
+5 -3
View File
@@ -312,12 +312,12 @@ pub struct Pipeline {
/// a pick bumps only `selection_generation`, refreshing the overlay without
/// touching the main wire buffers.
pub cached_selection: (u64, u64),
/// `(wire_content_id, face3d_fill_active, show_2d_solid_fills)` the Face3D
/// `(wire_content_id, face3d_fill_active, show_2d_solid_fills, view_dir)` the Face3D
/// edge/fill buffers were uploaded for. A stable content id avoids retaining
/// the resident wire Arc:
/// that Arc must stay uniquely owned by Scene so a small edit can splice it
/// in place instead of rebuilding the whole drawing.
pub cached_face3d_key: (u64, bool, bool),
pub cached_face3d_key: (u64, bool, bool, [u32; 3]),
/// Handle → indices into the resident wire set, built once per wire upload
/// (when `cached_wire_id` changes). Lets the selection/hover xray overlay
/// gather just the highlighted entity's wires (`O(highlighted)`) instead of
@@ -2323,7 +2323,7 @@ impl Pipeline {
cached_epoch: (u64::MAX, u64::MAX, u64::MAX),
cached_wire_id: u64::MAX,
cached_selection: (u64::MAX, u64::MAX),
cached_face3d_key: (u64::MAX, false, false),
cached_face3d_key: (u64::MAX, false, false, [u32::MAX; 3]),
wire_handle_index: std::sync::Arc::new(rustc_hash::FxHashMap::default()),
render_sig: u64::MAX,
skip_geometry: false,
@@ -3023,6 +3023,7 @@ impl Pipeline {
all_wires: &[WireModel],
wireframe_only: bool,
show_2d_solid_fills: bool,
view_dir: glam::Vec3,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) {
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
@@ -3056,6 +3057,7 @@ impl Pipeline {
all_wires,
keep_3d_mesh_fills,
show_2d_solid_fills,
view_dir,
depth_map,
));
}
+13 -4
View File
@@ -378,7 +378,7 @@ impl shader::Primitive for Primitive {
inner.cached_wire_id = u64::MAX;
inner.cached_selection = (u64::MAX, u64::MAX);
inner.cached_mesh_content_id = u64::MAX;
inner.cached_face3d_key = (u64::MAX, false, false);
inner.cached_face3d_key = (u64::MAX, false, false, [u32::MAX; 3]);
inner.cached_hatch_source = None;
inner.cached_preview_hatch_source = None;
inner.cached_wipeout_source = None;
@@ -484,6 +484,12 @@ impl shader::Primitive for Primitive {
// the view toggle so 2D fills stay on even when the user picks
// the Wireframe overlay style.
let face3d_fill_active = fill_mode && !vp.view_wireframe;
let solid_fill_active = fill_mode && vp.show_2d_solid_fills;
let view_dir_key = [
vp.view_dir.x.to_bits(),
vp.view_dir.y.to_bits(),
vp.view_dir.z.to_bits(),
];
let fill_changed = inner.cached_fill_mode != fill_mode;
let hatch_changed = inner
.cached_hatch_source
@@ -574,14 +580,16 @@ impl shader::Primitive for Primitive {
&& !face_pass_unchanged);
if face3d_changed
|| face3d_fill_active != inner.cached_face3d_key.1
|| vp.show_2d_solid_fills != inner.cached_face3d_key.2
|| solid_fill_active != inner.cached_face3d_key.2
|| view_dir_key != inner.cached_face3d_key.3
{
inner.upload_face3d(
device,
&vp.face3d_wires[..],
&vp_wires[..],
!face3d_fill_active,
vp.show_2d_solid_fills,
solid_fill_active,
vp.view_dir,
&draw_depths,
);
inner.cached_face3d_source = Some(Arc::clone(&vp.face3d_wires));
@@ -590,7 +598,8 @@ impl shader::Primitive for Primitive {
inner.cached_face3d_key = (
vp.wire_content_id,
face3d_fill_active,
vp.show_2d_solid_fills,
solid_fill_active,
view_dir_key,
);
// Wire buffers are world-space, so a camera move alone doesn't
// change them — only the view_proj uniform (uploaded every frame).