forked from 1b-agent/opencadstudio-upstream
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and nothing else, so two things the user can see were not pickable. A wire renders as a band `line_weight_px` wide, so pick now widens to that rendered half-width via `pick_tolerance_px`, mirroring the wire shader's `select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for every standard weight (the widest, 2.11 mm, renders 7.97 px half-width — just under the 8 px threshold); this keeps the two sides from drifting apart if the display boost in `lineweight_to_px` ever changes. Entities extruded by a DXF thickness draw their wall as four edges with nothing in between, so the interior was a hole the cursor fell through. WireModel gains `pick_tris`: triangles hit-testing treats as solid and the renderer never uploads. Kept apart from `fill_tris` because that channel reaches the GPU, and a wall that rendered shaded would change how every thickness drawing looks. Ranked below `fill_tris` — those are drawn, these are not, so where they overlap the drawn face is what the user means. Walls are dropped when a wire is projected into a floating viewport: `points` are reprojected and clipped into paper coords there, so cloned model-space triangles would hit-test at a model-scale offset. Extruded entities stay selectable by their edges, which do get projected. The wall loop runs on every hover that misses everything else, so it rejects on the wire's AABB first — an extruded circle is ~128 triangles and a drawing full of thickness would otherwise project every wall on each mouse move. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
72d471c2e7
commit
3f060a570a
63 files changed
+514
-83
No files matched your search
Vendored
+39
-1
@@ -48,6 +48,11 @@ pub struct LocalWire {
|
||||
pub tangent_geoms: Vec<TangentGeom>,
|
||||
pub fill_tris: Vec<[f32; 3]>,
|
||||
pub fill_tris_low: Vec<[f32; 3]>,
|
||||
/// Thickness-wall pick geometry, transformed by the insert like `points`
|
||||
/// so a block child's extruded wall stays selectable at the instance's
|
||||
/// place and scale. Never reaches the GPU.
|
||||
pub pick_tris: Vec<[f32; 3]>,
|
||||
pub pick_tris_low: Vec<[f32; 3]>,
|
||||
/// Per-wire colour from the tessellator output. For most entities this
|
||||
/// equals the sub-entity's resolved colour. For colour-split MTEXT
|
||||
/// (`\C`/`\c` inline overrides) each wire carries its own override colour.
|
||||
@@ -464,6 +469,8 @@ fn tessellate_sub_local(
|
||||
tangent_geoms: wire.tangent_geoms,
|
||||
fill_tris: wire.fill_tris,
|
||||
fill_tris_low: wire.fill_tris_low,
|
||||
pick_tris: wire.pick_tris,
|
||||
pick_tris_low: wire.pick_tris_low,
|
||||
color: wire.color,
|
||||
aci,
|
||||
pattern_length: pat_len,
|
||||
@@ -748,6 +755,10 @@ struct BatchEntry {
|
||||
/// reconstructs `high + low`). Without it absolute f32 fills quantize to
|
||||
/// ~0.5 m and the greek-text rectangles shear.
|
||||
fill_tris_low: Vec<[f32; 3]>,
|
||||
/// Accumulated thickness-wall pick geometry, paired high/low like
|
||||
/// `fill_tris`. Pick-only — no GPU batch reads this.
|
||||
pick_tris: Vec<[f32; 3]>,
|
||||
pick_tris_low: Vec<[f32; 3]>,
|
||||
/// Accumulated SDF glyph quads (world space) for block-instance text.
|
||||
text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||
min_x: f32,
|
||||
@@ -826,6 +837,8 @@ impl Batches {
|
||||
// the cached defn.
|
||||
let color = crate::scene::view::render::adapt_to_bg(b.color, bg_color);
|
||||
WireModel {
|
||||
pick_tris: b.pick_tris,
|
||||
pick_tris_low: b.pick_tris_low,
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: b.text_verts,
|
||||
@@ -1086,7 +1099,11 @@ fn emit_wire(
|
||||
ctx: &ExpandCtx,
|
||||
out: &mut Batches,
|
||||
) {
|
||||
if lw.points.is_empty() && lw.fill_tris.is_empty() && lw.text_verts.is_empty() {
|
||||
if lw.points.is_empty()
|
||||
&& lw.fill_tris.is_empty()
|
||||
&& lw.text_verts.is_empty()
|
||||
&& lw.pick_tris.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1246,6 +1263,27 @@ fn emit_wire(
|
||||
entry.fill_tris.push([hx, hy, hz]);
|
||||
entry.fill_tris_low.push([lx, ly, lz]);
|
||||
}
|
||||
// Thickness walls: same reconstruct → transform → re-split as the fills
|
||||
// above, so a block child's wall tracks the insert's placement and scale.
|
||||
debug_assert!(
|
||||
lw.pick_tris_low.is_empty() || lw.pick_tris.len() == lw.pick_tris_low.len(),
|
||||
"pick_tris_low must be empty or the same length as pick_tris (got {} vs {})",
|
||||
lw.pick_tris.len(),
|
||||
lw.pick_tris_low.len(),
|
||||
);
|
||||
for (idx, p) in lw.pick_tris.iter().enumerate() {
|
||||
let pl = lw.pick_tris_low.get(idx).copied().unwrap_or([0.0; 3]);
|
||||
let v = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + pl[0] as f64,
|
||||
p[1] as f64 + pl[1] as f64,
|
||||
p[2] as f64 + pl[2] as f64,
|
||||
));
|
||||
let (hx, lx) = WireModel::split_ds(v.x);
|
||||
let (hy, ly) = WireModel::split_ds(v.y);
|
||||
let (hz, lz) = WireModel::split_ds(v.z);
|
||||
entry.pick_tris.push([hx, hy, hz]);
|
||||
entry.pick_tris_low.push([lx, ly, lz]);
|
||||
}
|
||||
// SDF glyph quads: reconstruct each block-local f64 position, apply the
|
||||
// insert transform, re-split — same path as points/fills so block-instance
|
||||
// text lands at the right world place and scale. Colour resolves to the
|
||||
|
||||
Reference in new issue
Block a user