fix(xref): blocks respect their internal draw order

Xref merge now inserts entities in the source block's chain order and
carries SortEntitiesTable objects across (remapped to merged handles).
draw_depth_map values become [depth, half] so exploded block/xref fills
and wide-polyline band wires both compose child depth as
insert_depth + in-block rank x half, recursing into nested inserts with
the same divisor - bands and fills from one block interleave by the
block's internal order instead of stacking at the insert's single depth.
Band batches split per rank via WireModel.depth_override; thin wires
stay merged. The wire arena falls back to a full rebuild when a slab
carries mixed depths so refreshes don't flatten band overrides.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-21 15:53:28 +03:00
commit e177f92ff5
48 changed files with 337 additions and 50 deletions

View file

@ -1237,6 +1237,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
let guide = crate::scene::WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1277,6 +1278,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
previews.push(crate::scene::WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -1354,6 +1354,7 @@ fn tessellate_dimension_inner(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1382,6 +1383,7 @@ fn tessellate_dimension_inner(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1410,6 +1412,7 @@ fn tessellate_dimension_inner(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1439,6 +1442,7 @@ fn tessellate_dimension_inner(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1484,6 +1488,7 @@ fn tessellate_dimension_inner(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -592,6 +592,7 @@ impl LeaderTess for Leader {
return WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -700,6 +701,7 @@ impl LeaderTess for Leader {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -1406,6 +1406,7 @@ impl MultiLeaderTess for MultiLeader {
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1697,6 +1698,7 @@ impl MultiLeaderTess for MultiLeader {
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1766,6 +1768,7 @@ impl MultiLeaderTess for MultiLeader {
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1803,6 +1806,7 @@ impl MultiLeaderTess for MultiLeader {
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -755,6 +755,7 @@ pub fn tessellate_table(
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -5,7 +5,7 @@ use acadrust::entities::{Block, BlockEnd};
use acadrust::tables::TableEntry;
use acadrust::types::{Handle, Vector3};
use acadrust::{CadDocument, EntityType};
use rustc_hash::FxHashMap as HashMap;
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::path::{Path, PathBuf};
/// Status of an external reference block.
@ -234,14 +234,40 @@ fn merge_xref_into_block(
// ── Entities ────────────────────────────────────────────────────────
let xref_ms_handle = xref_doc.header.model_space_block_handle;
let entities: Vec<EntityType> = xref_doc
.entities()
.filter(|e| !matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)))
.cloned()
// Merge in each block's DISPLAY order: a block record's entity_handles
// chain is the draw order inside that block, while the flat entity list
// is stream order — merging in stream order scrambled the xref's own
// stacking (fills over outlines etc.), since the host ranks draw order
// by the freshly-allocated handles. Entities missing from every chain
// keep their stream order at the end.
let ordered: Vec<Handle> = {
let mut ordered: Vec<Handle> = Vec::new();
let mut seen: HashSet<Handle> = HashSet::default();
for br in xref_doc.block_records.iter() {
for &h in &br.entity_handles {
if seen.insert(h) {
ordered.push(h);
}
}
}
for e in xref_doc.entities() {
let h = e.common().handle;
if seen.insert(h) {
ordered.push(h);
}
}
ordered
};
let entities: Vec<(Handle, EntityType)> = ordered
.into_iter()
.filter_map(|h| xref_doc.get_entity(h).map(|e| (h, e.clone())))
.filter(|(_, e)| !matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)))
.collect();
// old xref-doc entity handle → merged host handle, for the sortents copy.
let mut entity_handle_map: HashMap<Handle, Handle> = HashMap::default();
let mut dropped = 0usize;
for mut entity in entities {
for (old_h, mut entity) in entities {
// Drop parser-garbage entities (bad normals / vertex counts / inf
// coords) before they enter the host doc — they trigger huge
// allocations and unbounded recursion in the wire pipeline.
@ -285,7 +311,48 @@ fn merge_xref_into_block(
entity.common_mut().owner_handle = new_owner;
// Clear the foreign handle so acadrust assigns a new one.
set_handle(&mut entity, Handle::NULL);
let _ = doc.add_entity(entity);
if let Ok(new_h) = doc.add_entity(entity) {
entity_handle_map.insert(old_h, new_h);
}
}
// ── Draw-order overrides ────────────────────────────────────────────
// Re-point the xref's SortEntitiesTables at the merged blocks with the
// merged entity handles, so an explicit DRAWORDER inside the xref
// survives the merge.
{
use acadrust::objects::{ObjectType, SortEntitiesTable};
for obj in xref_doc.objects.values() {
let ObjectType::SortEntitiesTable(t) = obj else {
continue;
};
if t.is_empty() {
continue;
}
let new_block = if t.block_owner_handle == xref_ms_handle {
br_handle
} else if let Some(&h) = br_handle_map.get(&t.block_owner_handle) {
h
} else {
continue;
};
let mut nt = SortEntitiesTable::new();
nt.handle = doc.allocate_handle();
nt.block_owner_handle = new_block;
for e in t.entries() {
let Some(&ne) = entity_handle_map.get(&e.entity_handle) else {
continue;
};
let ns = entity_handle_map
.get(&e.sort_handle)
.copied()
.unwrap_or(e.sort_handle);
nt.add_entry(ne, ns);
}
if !nt.is_empty() {
doc.objects.insert(nt.handle, ObjectType::SortEntitiesTable(nt));
}
}
}
dropped
}

View file

@ -188,6 +188,7 @@ impl CadCommand for AlignedDimensionCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -241,6 +242,7 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -132,6 +132,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -182,6 +182,7 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -181,6 +181,7 @@ impl CadCommand for DimBaselineCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -161,6 +161,7 @@ impl CadCommand for DimContinueCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -87,6 +87,7 @@ impl CadCommand for DimJogLineCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -102,6 +102,7 @@ impl CadCommand for DimTeditCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -165,6 +165,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -220,6 +220,7 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -152,6 +152,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -460,6 +460,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -123,6 +123,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -92,6 +92,7 @@ impl CadCommand for QdimCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -187,6 +187,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -141,6 +141,7 @@ impl CadCommand for TableCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -86,6 +86,7 @@ impl CadCommand for ToleranceCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -162,6 +162,7 @@ impl CadCommand for AttdefCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -138,6 +138,7 @@ impl CadCommand for MlineCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -110,6 +110,7 @@ impl CadCommand for ImageCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -86,6 +86,7 @@ impl CadCommand for RayCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -183,6 +184,7 @@ impl CadCommand for XLineCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -91,6 +91,7 @@ impl CadCommand for RevCloudCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -116,6 +116,7 @@ impl CadCommand for WipeoutCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -164,6 +165,7 @@ impl CadCommand for WipeoutCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -71,6 +71,7 @@ impl CadCommand for AreaCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -65,6 +65,7 @@ impl CadCommand for DistCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -69,6 +69,7 @@ impl MeasureGeomCommand {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -3135,6 +3135,7 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -83,6 +83,7 @@ impl CadCommand for MviewCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -307,6 +307,7 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -50,6 +50,7 @@ impl CadCommand for PlotWindowCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -67,6 +67,7 @@ impl CadCommand for ZoomWindowCommand {
Some(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -85,6 +85,11 @@ pub struct LocalWire {
/// expand-time: transform corners by the Insert transform → world AABB
/// → test against the camera's world-space view rect.
pub aabb_local: [f32; 4],
/// This child's signed draw-order rank in (-1,1) within its owning block
/// (from the scene draw-depth map, so SortEntitiesTable overrides apply).
/// Composed at expand time into a `depth_override` for wide-polyline
/// bands so a band orders against its block siblings.
pub local_rank: f32,
}
#[derive(Clone, Debug)]
@ -119,6 +124,9 @@ pub struct NestedRef {
/// expansion it is mapped to world by the accumulated transform and the
/// nested wires are clipped to it.
pub clip_poly: Option<Vec<[f64; 2]>>,
/// The nested INSERT's own signed draw-order rank in (-1,1) within the
/// parent block — narrows the depth sub-range its children may occupy.
pub local_rank: f32,
}
#[derive(Clone, Debug)]
@ -135,6 +143,11 @@ pub struct BlockDefn {
/// the wire renderer is 2D-dominant. Expressed in this defn's *offset*
/// Absolute world-space XY (the double-single render path keeps it precise).
pub aabb_local: [f32; 4],
/// Raw entity count of the source block record (`entity_handles.len()`).
/// Divisor for nested depth composition: a nested insert's children get a
/// sub-range of `parent_scale / (child_count + 1)` — the same formula the
/// exploded-fill path uses, so bands and fills stay on one depth scale.
pub child_count: usize,
}
#[derive(Default, Debug)]
@ -156,7 +169,15 @@ impl BlockCache {
/// included too. The Model_Space / Paper_Space block_records are skipped
/// because their entities are emitted as top-level wires, not via the
/// cache.
pub fn build(doc: &CadDocument, anno_scale: f32, bg_color: [f32; 4]) -> Self {
pub fn build(
doc: &CadDocument,
anno_scale: f32,
bg_color: [f32; 4],
// Scene draw-depth map ([depth, half] per handle) — source of each
// block child's in-block rank, so band depth composition agrees with
// the SortEntitiesTable-aware ranking the fill explosion uses.
depth_map: &HashMap<u64, [f32; 2]>,
) -> Self {
use crate::par::prelude::*;
let mut cache = Self::new();
let referenced = collect_referenced_blocks(doc);
@ -168,7 +189,12 @@ impl BlockCache {
// post-pass (it resolves nested references and is comparatively cheap).
cache.defns = referenced
.par_iter()
.map(|name| (name.clone(), Arc::new(build_defn(doc, name, anno_scale, bg_color))))
.map(|name| {
(
name.clone(),
Arc::new(build_defn(doc, name, anno_scale, bg_color, depth_map)),
)
})
.collect();
cache.compute_block_aabbs(&referenced);
cache
@ -285,6 +311,7 @@ fn build_defn(
block_name: &str,
anno_scale: f32,
bg_color: [f32; 4],
depth_map: &HashMap<u64, [f32; 2]>,
) -> BlockDefn {
let br = match doc.block_records.get(block_name) {
Some(br) => br,
@ -326,14 +353,16 @@ fn build_defn(
continue
}
EntityType::Insert(nested_ins) => {
subs.push(LocalSub::Nested(build_nested_ref(nested_ins, doc, bg_color)));
subs.push(LocalSub::Nested(build_nested_ref(
nested_ins, doc, bg_color, depth_map,
)));
}
_ => {
// A wide polyline inside a block carries its `world_width` on
// the LocalWire; `emit_wire` scales it by the insert transform
// so the shader band matches the scaled geometry (same band the
// top-level path draws — depth-tested + linetype-dashed).
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color) {
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map) {
subs.push(LocalSub::Wire(lw));
}
}
@ -342,6 +371,7 @@ fn build_defn(
BlockDefn {
subs,
aabb_local: [0.0; 4],
child_count: br.entity_handles.len(),
}
}
@ -349,6 +379,7 @@ fn build_nested_ref(
nested_ins: &acadrust::entities::Insert,
doc: &CadDocument,
bg_color: [f32; 4],
depth_map: &HashMap<u64, [f32; 2]>,
) -> NestedRef {
// Store the RAW colour — `adapt_to_bg` runs at emit time
// (`Batches::finalize`) so the same cached defn can serve renders
@ -389,6 +420,9 @@ fn build_nested_ref(
l0,
instance_offsets: array_offsets(nested_ins),
clip_poly,
local_rank: depth_map
.get(&nested_ins.common.handle.value())
.map_or(0.0, |d| d[0]),
}
}
@ -397,6 +431,7 @@ fn tessellate_sub_local(
sub: &EntityType,
anno_scale: f32,
bg_color: [f32; 4],
depth_map: &HashMap<u64, [f32; 2]>,
) -> Vec<LocalWire> {
let h = sub.common().handle;
@ -505,6 +540,7 @@ fn tessellate_sub_local(
lt_l0,
lw_l0,
aabb_local,
local_rank: depth_map.get(&h.value()).map_or(0.0, |d| d[0]),
});
}
result
@ -693,7 +729,7 @@ pub fn expand_insert(
is_xref,
bg_color,
};
expand_defn(defn, &base_xform, &ctx, &mut batches, &mut visited, 0);
expand_defn(defn, &base_xform, &ctx, &mut batches, &mut visited, 0, (0.0, 1.0));
}
Some(batches.finalize(&name, selected, bg_color))
}
@ -758,6 +794,10 @@ struct StyleKey {
/// discriminator, so greek fills must stay in their own batches even
/// when their color/style would otherwise collide with regular wires.
is_fill_only: bool,
/// Bit-cast composed block-local depth for band wires (`0` = no override).
/// Keeps bands of different in-block draw ranks in separate batches so
/// each finalized WireModel carries one correct `depth_override`.
depth_bits: u32,
}
#[derive(Default, Debug)]
@ -769,6 +809,9 @@ struct BatchEntry {
world_width: f32,
aci: u8,
plinegen: bool,
/// Composed block-local draw-order offset for a band batch (see
/// `WireModel::depth_override`). `None` for every other batch.
local_depth: Option<f32>,
points: Vec<[f32; 3]>,
points_low: Vec<[f32; 3]>,
snap_pts: Vec<(glam::DVec3, SnapHint)>,
@ -866,6 +909,7 @@ impl Batches {
WireModel {
taper_widths: Vec::new(),
world_width: b.world_width,
depth_override: b.local_depth,
fill_is_3d: false,
pick_tris: b.pick_tris,
pick_tris_low: b.pick_tris_low,
@ -911,6 +955,7 @@ fn style_key(
aci: u8,
plinegen: bool,
is_fill_only: bool,
local_depth: Option<f32>,
) -> StyleKey {
StyleKey {
color: [
@ -935,6 +980,7 @@ fn style_key(
aci,
plinegen,
is_fill_only,
depth_bits: local_depth.map_or(0, f32::to_bits),
}
}
@ -945,6 +991,10 @@ fn expand_defn(
out: &mut Batches,
visited: &mut Vec<String>,
depth: usize,
// Block-local depth sub-range `(base, scale)` accumulated through nested
// inserts: a child at rank r lands at `base + r * scale`, all within
// (-1,1) of the top-level insert. Seeded `(0.0, 1.0)` by `expand_insert`.
d_range: (f32, f32),
) {
if depth > MAX_NESTING_DEPTH {
eprintln!("block_cache: nested-block depth > {MAX_NESTING_DEPTH}, truncating");
@ -977,7 +1027,7 @@ fn expand_defn(
continue;
}
}
emit_wire(lw, accum_xform, ctx, out);
emit_wire(lw, accum_xform, ctx, out, d_range);
}
LocalSub::Nested(nref) => {
if visited.iter().any(|n| n == &nref.block_name) {
@ -1055,6 +1105,12 @@ fn expand_defn(
bg_color: ctx.bg_color,
};
visited.push(nref.block_name.clone());
// Children of this nested insert stack inside the slot its own
// rank owns — same composition the exploded-fill path applies.
let nested_range = (
d_range.0 + nref.local_rank * d_range.1,
d_range.1 / (nested_defn.child_count.max(1) as f32 + 1.0),
);
if let Some(cp) = &nref.clip_poly {
// XCLIP'd nested insert: expand into an isolated batch set,
// finalize to whole wires, clip them to the boundary mapped
@ -1065,7 +1121,15 @@ fn expand_defn(
// instance would differ).
let composed = nref.xform.then(accum_xform);
let mut sub = Batches::default();
expand_defn(nested_defn, &composed, &inner_ctx, &mut sub, visited, depth + 1);
expand_defn(
nested_defn,
&composed,
&inner_ctx,
&mut sub,
visited,
depth + 1,
nested_range,
);
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
let world_poly: Vec<[f64; 2]> = cp
.iter()
@ -1093,6 +1157,7 @@ fn expand_defn(
out,
visited,
depth + 1,
nested_range,
);
}
}
@ -1129,6 +1194,7 @@ fn emit_wire(
accum_xform: &Transform,
ctx: &ExpandCtx,
out: &mut Batches,
d_range: (f32, f32),
) {
if lw.points.is_empty()
&& lw.fill_tris.is_empty()
@ -1173,6 +1239,13 @@ fn emit_wire(
0.0
};
// Only band wires take a per-child composed depth: their solid area is
// what covers siblings, and their width already splits them into their own
// batches — thin wires keep the shared whole-insert depth so same-style
// batches stay merged.
let local_depth = (lw.world_width > 0.0)
.then(|| d_range.0 + lw.local_rank * d_range.1);
let key = style_key(
final_color,
final_pat_len,
@ -1182,6 +1255,7 @@ fn emit_wire(
lw.aci,
lw.plinegen,
lw.is_fill_only,
local_depth,
);
// If the open batch for this style would exceed wgpu's per-buffer limit
@ -1205,6 +1279,7 @@ fn emit_wire(
lw.is_fill_only,
)
});
entry.local_depth = local_depth;
// NaN separator between previously-appended geometry and this wire so the
// GPU shader treats them as disconnected polylines within one buffer.

View file

@ -656,6 +656,7 @@ pub(crate) fn tessellate_entity(
return vec![WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@ -995,6 +996,7 @@ pub(crate) fn tessellate_entity(
let marker = WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1376,6 +1378,7 @@ fn lod_stub_wire(
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1464,6 +1467,7 @@ fn lod_stub_wire_3d(
WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -372,6 +372,7 @@ pub fn tessellate(
elem_wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -664,6 +665,7 @@ pub fn tessellate(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -703,6 +705,7 @@ pub fn tessellate(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -749,6 +752,7 @@ pub fn tessellate(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -776,6 +780,7 @@ pub fn tessellate(
wires.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -833,6 +838,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -872,6 +878,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -908,6 +915,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -937,6 +945,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -988,6 +997,7 @@ pub fn tessellate(
return vec![WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1038,6 +1048,7 @@ pub fn tessellate(
return vec![WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1081,6 +1092,7 @@ pub fn tessellate(
return vec![WireModel {
taper_widths: Vec::new(),
world_width: polyline_band_width(entity),
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@ -1173,6 +1185,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@ -1233,6 +1246,7 @@ pub fn tessellate(
fill_tris,
fill_tris_low,
fill_is_3d,
depth_override: None,
});
}
@ -1240,6 +1254,7 @@ pub fn tessellate(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -1282,6 +1297,7 @@ pub fn tessellate(
return vec![WireModel {
taper_widths: Vec::new(),
world_width: polyline_band_width(entity),
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@ -1325,6 +1341,7 @@ pub fn tessellate(
return vec![WireModel {
taper_widths: widths,
world_width,
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@ -1424,6 +1441,7 @@ pub fn tessellate(
vec![WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris,
pick_tris_low,

View file

@ -754,7 +754,7 @@ impl Scene {
if self.selected.contains(&handle) {
m.color = [0.15, 0.55, 1.00, m.color[3]];
}
let d = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
let d = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
m.draw_depth = d;
if let Some(b) = &mut backdrop {
b.draw_depth = d;
@ -814,6 +814,21 @@ impl Scene {
.unwrap_or(false)
};
let mut models: Vec<HatchModel> = Vec::new();
// Draw-order composition (#408): every fill inherits its INSERT's
// draw-depth slot, offset by the child's rank INSIDE its block
// (draw_depth_map ranks per owning block), scaled to stay within the
// insert's half-gap in the host ordering. Without this every block /
// xref fill landed at depth 0 and the stack's LIFO pop reversed the
// block's internal stacking.
let depth_map = self.draw_depth_map();
let block_count = |name: &str| -> usize {
self.document
.block_records
.get(name)
.map(|br| br.entity_handles.len())
.unwrap_or(1)
.max(1)
};
// Exploding an INSERT materializes (clones) every child of its block —
// including 3D solids that each carry megabytes of SAB geometry — just
// to scan the result for hatch fills. On xref-heavy drawings whose
@ -874,17 +889,22 @@ impl Scene {
&self.document,
&ins.common.layer,
);
let [base_depth, half_gap] = depth_map
.get(&ins.common.handle.value())
.copied()
.unwrap_or([0.0, 0.0]);
let mut stack: Vec<(
EntityType,
usize,
[f32; 4],
crate::scene::view::render::InheritStyle,
(f32, f32),
)> = ins
.explode_from_document(&self.document)
.into_iter()
.map(|e| (normalize(e), 0usize, ins_color, l0))
.map(|e| (normalize(e), 0usize, ins_color, l0, (base_depth, half_gap)))
.collect();
while let Some((sub, depth, sub_ins_color, sub_l0)) = stack.pop() {
while let Some((sub, depth, sub_ins_color, sub_l0, (d_base, d_half))) = stack.pop() {
match sub {
EntityType::Insert(nins) => {
if depth >= 32 {
@ -916,8 +936,22 @@ impl Scene {
&nins.common.layer,
)
};
// Nested insert: its slot = parent slot + its rank in
// the parent block, subdivided by its own block size.
let nd_base = d_base
+ depth_map
.get(&nins.common.handle.value())
.map_or(0.0, |d| d[0])
* d_half;
let nd_half = d_half / (block_count(&nins.block_name) as f32 + 1.0);
for e in nins.explode_from_document(&self.document) {
stack.push((normalize(e), depth + 1, child_ins_color, child_l0));
stack.push((
normalize(e),
depth + 1,
child_ins_color,
child_l0,
(nd_base, nd_half),
));
}
}
EntityType::Hatch(dxf) => {
@ -942,6 +976,12 @@ impl Scene {
if let Some(mut model) =
Self::hatch_model_from_dxf(&dxf, color)
{
// In-block rank → within the insert's depth slot.
model.draw_depth = d_base
+ depth_map
.get(&dxf.common.handle.value())
.map_or(0.0, |d| d[0])
* d_half;
if let Some(poly) = &clip_poly {
let clipped = pick::xclip::clip_hatch_boundary(
&model.boundary,

View file

@ -873,8 +873,13 @@ pub struct Scene {
/// effective sort key (SortEntitiesTable override or own handle), then
/// fed to the 2D pipelines as a small clip-z bias so entities of
/// *different* types order correctly against each other. 3D meshes are
/// excluded (they keep real geometric depth).
draw_depth_cache: RefCell<Option<(u64, Arc<HashMap<u64, f32>>)>>,
/// excluded (they keep real geometric depth). Each value is
/// `[depth, half]`: `depth` = the entity's signed rank in (-1,1), `half` =
/// half the gap to the neighbouring ranks (`1/(block_count+1)`) — the
/// sub-range a block INSERT's children may occupy without crossing the
/// insert's siblings. Fill explosion and band wires compose child depths
/// as `depth + child_rank * half`.
draw_depth_cache: RefCell<Option<(u64, Arc<HashMap<u64, [f32; 2]>>)>>,
/// Cached hatch fill models, keyed by geometry_epoch. View culling
/// is handled at draw time via `hatch_skip_flags` in the pipeline,
/// not at build time — that lets the GPU buffer stay stable across
@ -1295,7 +1300,8 @@ impl Scene {
// its internal geometry / text / attributes must NOT be scaled
// individually (that would double-scale — AutoCAD even forbids
// annotative attributes inside annotative blocks for this reason).
let built = cache::block_cache::BlockCache::build(&self.document, 1.0, bg);
let built =
cache::block_cache::BlockCache::build(&self.document, 1.0, bg, &self.draw_depth_map());
let arc = Arc::new(built);
*self.block_defn_cache.borrow_mut() = Some((self.block_epoch, Arc::clone(&arc)));
arc
@ -3314,7 +3320,7 @@ impl Scene {
/// its owning block by effective sort key (SortEntitiesTable override or
/// own handle). The result feeds the 2D pipelines as a clip-z bias so
/// entities of different types order correctly against each other.
pub(super) fn draw_depth_map(&self) -> Arc<HashMap<u64, f32>> {
pub(super) fn draw_depth_map(&self) -> Arc<HashMap<u64, [f32; 2]>> {
{
// Draw order depends on each block's entity COUNT (the rank
// denominator) and the entities' handles / SortEntitiesTable — none of
@ -3384,17 +3390,20 @@ impl Scene {
.unwrap_or(hv);
by_block.entry(block).or_default().push((hv, eff));
}
let mut depth_map: HashMap<u64, f32> = HashMap::default();
let mut depth_map: HashMap<u64, [f32; 2]> = HashMap::default();
for (_block, mut v) in by_block {
v.sort_by_key(|(_, eff)| *eff);
let denom = (v.len() as f32) + 1.0;
// Adjacent ranks are 2/denom apart; a child sub-range of
// ±half = ±1/denom around the parent depth never crosses them.
let half = 1.0 / denom;
for (rank, (hv, _)) in v.into_iter().enumerate() {
// Signed (-1,1): back ranks → negative, front → positive,
// mid → ~0. The shader applies `z -= draw_depth * BIAS`, so a
// default/unranked 0.0 means "no bias" (neutral) — which keeps
// 3D mesh faces and transient wires at their real depth.
let norm = (rank as f32 + 1.0) / denom; // (0,1)
depth_map.insert(hv, (norm - 0.5) * 2.0);
depth_map.insert(hv, [(norm - 0.5) * 2.0, half]);
}
}
let arc = Arc::new(depth_map);
@ -3527,7 +3536,7 @@ impl Scene {
}
}
let mut m = model.clone();
m.draw_depth = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
m.draw_depth = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
Some(m)
})
.collect()
@ -3551,7 +3560,7 @@ impl Scene {
return None;
}
let mut m = model.clone();
m.draw_depth = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
m.draw_depth = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
Some(m)
})
.collect(),

View file

@ -130,6 +130,14 @@ pub struct WireModel {
/// exactly like `points` — no separate collector pass. The renderer
/// gathers these across all wires into the text vertex buffer.
pub text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
/// Block-local composed draw-order offset in (-1, 1) for a wire that must
/// order against its *siblings inside the block* — currently wide-polyline
/// bands, whose solid area would otherwise cover later-drawn siblings.
/// `None` (all other wires) = the whole-insert depth resolved from `name`.
/// `Some(local)` composes at draw time: `insert_depth + local * insert_half`,
/// mirroring how exploded block fills seed their depth, so bands and fills
/// from the same block interleave by the block's internal draw order.
pub depth_override: Option<f32>,
/// `true` when [`fill_tris`] is a real 3-D surface (PolyfaceMesh /
/// PolygonMesh face) that must render with hidden-surface depth and only in
/// shaded modes. `false` for a flat 2-D overlay fill (SOLID arrowhead,
@ -186,6 +194,7 @@ impl WireModel {
Self {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -419,6 +428,7 @@ impl Default for WireModel {
dash_align_end: None,
fill_tris: Vec::new(),
fill_tris_low: Vec::new(),
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -114,15 +114,10 @@ impl Face3DGpu {
face3d_wires: &[WireModel],
all_wires: &[WireModel],
keep_3d_mesh_fills: bool,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> Self {
let depth_of = |w: &WireModel| -> f32 {
w.name
.parse::<u64>()
.ok()
.and_then(|h| depth_map.get(&h).copied())
.unwrap_or(0.0)
};
let depth_of =
|w: &WireModel| -> f32 { super::wire_gpu::wire_draw_depth(w, depth_map) };
let mut verts_3d: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
let mut verts_2d: Vec<Face3DVertex> = Vec::new();

View file

@ -1477,7 +1477,7 @@ impl Pipeline {
&self,
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> (
std::sync::Arc<Vec<WireGpu>>,
std::sync::Arc<rustc_hash::FxHashMap<u64, Vec<u32>>>,
@ -1543,7 +1543,7 @@ impl Pipeline {
wires: &[WireModel],
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
hover: Option<acadrust::Handle>,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) {
let hover = hover.filter(|h| !selected.contains(h));
if selected.is_empty() && hover.is_none() {
@ -1627,7 +1627,7 @@ impl Pipeline {
&mut self,
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) {
self.gpu_preview_wires = if wires.is_empty() {
vec![]
@ -1912,7 +1912,7 @@ impl Pipeline {
face3d_wires: &[WireModel],
all_wires: &[WireModel],
wireframe_only: bool,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) {
// Edge buffer is always built from `face3d_wires`, so 3DFACE
// outlines stay on the screen regardless of mode.

View file

@ -105,7 +105,7 @@ pub fn is_mesh_edge(w: &WireModel, mesh_names: &rustc_hash::FxHashSet<u64>) -> b
/// Surface) are excluded from `draw_depth_map`, so their fallback edge wires
/// get draw_depth 0.0. Two coincident opaque such wires share a z-bias and
/// resolve by submission order, which a tail relocation would flip.
fn append_unsafe(wires: &[&WireModel], depth_map: &FxHashMap<u64, f32>) -> bool {
fn append_unsafe(wires: &[&WireModel], depth_map: &FxHashMap<u64, [f32; 2]>) -> bool {
wires.iter().any(|w| {
w.color[3] < 0.999
|| handle_of(w).map_or(true, |h| !depth_map.contains_key(&h.value()))
@ -202,7 +202,7 @@ impl WireArena {
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[&WireModel],
depth_map: &FxHashMap<u64, f32>,
depth_map: &FxHashMap<u64, [f32; 2]>,
const_bgl: &wgpu::BindGroupLayout,
mesh_edge: bool,
) -> Option<Self> {
@ -288,7 +288,7 @@ impl WireArena {
queue: &wgpu::Queue,
changes: &[(Handle, ChangeKind)],
wires: &[&WireModel],
depth_map: &FxHashMap<u64, f32>,
depth_map: &FxHashMap<u64, [f32; 2]>,
) -> bool {
let Some(ranges) = handle_ranges(wires) else {
return false;
@ -405,13 +405,27 @@ impl WireArena {
// entity's z-bias. Refresh each live slab's draw_depth from the new
// depth map and re-upload the whole (small) const buffer; the instance
// buffer is untouched.
// A slab whose consts carry DIFFERENT depths holds block-band wires
// with composed per-child offsets (`depth_override`); a flat refresh
// to the entity's base depth would erase them. Fall back to a full
// rebuild, which recomputes each wire's composed depth.
if !self.mesh_edge {
for slab in self.slabs.values() {
let first = self.consts_cpu[slab.const_off as usize].draw_depth;
for k in 1..slab.const_len {
if self.consts_cpu[(slab.const_off + k) as usize].draw_depth != first {
return false;
}
}
}
}
for (h, slab) in &self.slabs {
// Mesh-edge wires keep depth 0 (no draw-order bias); regular wires
// take the re-normalised map value.
let dd = if self.mesh_edge {
0.0
} else {
depth_map.get(&h.value()).copied().unwrap_or(0.0)
depth_map.get(&h.value()).map_or(0.0, |d| d[0])
};
for k in 0..slab.const_len {
self.consts_cpu[(slab.const_off + k) as usize].draw_depth = dd;

View file

@ -488,17 +488,23 @@ pub(crate) fn emit_wire_native(
/// Looks up a wire's draw-order depth from the per-entity map using the
/// handle encoded in its `name`. Falls back to 0.0 (transient / preview
/// wires that carry no document handle).
/// wires that carry no document handle). A wire carrying a block-local
/// `depth_override` (a wide-polyline band inside a block) composes it into
/// the insert's own sub-range so the band orders against its block siblings.
pub(crate) fn wire_draw_depth(
wire: &WireModel,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> f32 {
wire
let base = wire
.name
.parse::<u64>()
.ok()
.and_then(|h| depth_map.get(&h).copied())
.unwrap_or(0.0)
.and_then(|h| depth_map.get(&h).copied());
match (base, wire.depth_override) {
(Some([d, half]), Some(local)) => d + local * half,
(Some([d, _]), None) => d,
(None, _) => 0.0,
}
}
/// Build the shared per-wire `WireConst` storage buffer and its bind group
@ -546,7 +552,7 @@ impl WireGpu {
pub fn from_run(
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
mesh_edge: bool,
const_bgl: Option<&wgpu::BindGroupLayout>,
) -> Vec<Self> {
@ -632,7 +638,7 @@ impl WireGpu {
pub fn from_batch(
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &rustc_hash::FxHashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
const_bgl: Option<&wgpu::BindGroupLayout>,
) -> Vec<Self> {
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();

View file

@ -307,6 +307,7 @@ pub fn apply_along(
.map(|pts| WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@ -348,6 +349,7 @@ pub fn apply_along(
out.push(WireModel {
taper_widths: Vec::new(),
world_width: 0.0,
depth_override: None,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),

View file

@ -60,7 +60,7 @@ pub struct ViewportData {
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
/// depth field (84 construction sites); the bias is looked up by handle
/// at GPU-upload time from this map instead.
pub(in crate::scene) draw_depths: Arc<rustc_hash::FxHashMap<u64, f32>>,
pub(in crate::scene) draw_depths: Arc<rustc_hash::FxHashMap<u64, [f32; 2]>>,
pub(in crate::scene) hatches: Arc<Vec<HatchModel>>,
/// Wipeout fills — rendered in a separate pass AFTER wires.
pub(in crate::scene) wipeout_hatches: Arc<Vec<HatchModel>>,