fix(hatch): deselect refresh, boundary offset, fill-click

Three hatch fixes:

- Selected hatches are tinted, but the hatch model cache and GPU upload
  were keyed only on geometry_epoch, so Esc cleared the selection set
  without repainting — the hatch stayed blue (#71). Key both on
  selection_generation too.

- add_hatch stored the boundary entity in local render space, so the
  boundary wire re-projected `world_offset` away from the fill on
  offset (large-coordinate) drawings. Build it in WCS.

- Clicking a hatch only selected it on its boundary wire, not its fill.
  Two causes: visible_hatches_for_click used a narrower block-visibility
  test than the render path (fill drawn but not clickable), and
  point_in_polygon closed every NaN sub-path — degrading the explicit
  2-point edge encoding of a boundary into zero-area loops. Mirror the
  render's visibility test and only auto-close sub-paths of >=3 verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-09 22:47:48 +03:00
commit 6b4272521c
4 changed files with 82 additions and 37 deletions

View file

@ -221,7 +221,7 @@ impl shader::Primitive for Primitive {
let (uo_y, us_y) = uv_crop_axis(sr.y, sr.height);
inner.upload_blit_uv(queue, [uo_x, uo_y], [us_x, us_y]);
inner.upload_uniforms(queue, &vp.uniforms);
let cur_key = (vp.geometry_epoch, vp.camera_generation);
let cur_key = (vp.geometry_epoch, vp.camera_generation, vp.selection_generation);
let fill_mode = vp.fill_mode;
// 3D face fill requires *both* the doc-level FILLMODE *and* the
// per-view Solid toggle. Hatches / wipeouts deliberately ignore
@ -229,9 +229,12 @@ impl shader::Primitive for Primitive {
// the Wireframe overlay style.
let face3d_fill_active = fill_mode && !vp.view_wireframe;
if cur_key != inner.cached_epoch {
// Static buffers (hatches/images/meshes) only need refresh on
// a real geometry change, not on every camera tick.
if vp.geometry_epoch != inner.cached_epoch.0 {
// Hatches carry a selected-tint, so re-upload on a geometry OR
// a selection change (issue #71); images / meshes only need a
// geometry change.
let geo_changed = vp.geometry_epoch != inner.cached_epoch.0;
let sel_changed = vp.selection_generation != inner.cached_epoch.2;
if geo_changed || sel_changed {
if fill_mode {
inner.upload_hatches(device, &vp.hatches[..]);
inner.upload_wipeouts(device, &vp.wipeout_hatches[..]);
@ -239,6 +242,8 @@ impl shader::Primitive for Primitive {
inner.upload_hatches(device, &[]);
inner.upload_wipeouts(device, &[]);
}
}
if geo_changed {
inner.upload_images(device, queue, &vp.images[..]);
inner.upload_meshes(device, &vp.meshes[..]);
}