fix(pick): catch a wide polyline on its band, not just its centreline

A wide polyline draws a solid band, but hit-testing only ever saw the
centreline 8 px out, so a donut could only be selected by clicking the
phantom circle inside the disc it paints. `self.hatches` — the map the
hatch pick channel walks — is filled from HATCH and SOLID alone, so the
band `wide_fills` produces was drawn (via `synced_hatch_models`) and never
picked.

The band rides in on `pick_tris`, the same channel a thickness wall uses:
`wide_fills` already computes it, so it costs one tessellation and inherits
the block transform. It ear clips rather than fans — an arc band is an
annular sector, concave on its inner edge — reusing `triangulate_planar`.

Wiring `pick_tris` through `Lines` alone was not enough: a plain wide
polyline leaves `to_truck` as `Contour`, and a PLINEGEN=0 one as
`SegmentedLines`. Both now carry it.

This needs the wire's cullable box to cover the band, and it did not.
`bounding_box()` boxes a polyline's stored vertices, knowing nothing of the
width painted around them: a donut's two vertices sit on one horizontal
line, so the box had zero height and rejected every click on the disc
before any triangle was tested. Every site that stamps the entity box onto
a wire now goes through `set_wire_aabb`, which widens it over `pick_tris`
(a no-op for the entities that have none, which is nearly all), and the
block cache chains `pick_tris` into its own box for the same reason.

That box was already too small for the centreline itself — an arc leaves
the vertex box — so a donut was unselectable at its top and bottom at
enough zoom. Widening fixes that too.

Verified end-to-end: tessellate a width-0.5 donut on a 0.05 centreline
radius, then `click_hit` five points 20 px clear of the centreline — all
land, and a click outside the disc still misses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-17 15:22:56 +03:00
1 parent 07d7888b94
commit 4f1e710b35
9 files changed
+111 -34

No files matched your search

+8 -7
View File
@@ -212,14 +212,15 @@ pub fn click_hit<'a>(
return Some(n);
}
// No drawn surface either. Entities extruded by a thickness draw their wall
// as four edges and nothing in between, so the interior is a hole the cursor
// falls straight through even though the wall reads as a face. Test the
// pick-only wall geometry to close it, front-most first.
// No fill of this wire's own either. `pick_tris` closes the surfaces that
// `points` only bounds: a thickness wall (drawn as four edges with nothing
// between them) and a wide polyline's band (drawn, but by the hatch
// pipeline, so no fill hangs off this wire). Without them the cursor falls
// through what plainly reads as solid. Front-most wins.
//
// Ranked below `fill_tris` on purpose: those triangles are actually
// rendered, these are invisible, and where an opaque face overlaps a wall
// the user is looking at the face — so the face is what they mean to click.
// Ranked below `fill_tris` because that geometry is this wire's own drawn
// surface — where the two overlap, the nearer thing to the eye is decided
// by depth, but a wire that has a real fill should win on it first.
let mut best_wall: Option<(f32, &str)> = None;
for wire in wires {
if wire.pick_tris.is_empty() {