fix(globe): Navara lines are picked within 8 px of their segments

Review of Task 4: the CPU line pick (shared with the sprite drill, hover
and the synchronous pick) took half the drawn width + 3 px, so a 1-2 px
trail needed a 3.5-4 px aim. Now within 8 px of the projected segments,
or half the width + 3 px for wider lines. Tested at 7.5 and 8.5 px from
a 1 px line; the contract suite's line click passes on both engines.
This commit is contained in:
German Khokhlov committed 2026-10-16 19:05:20 +04:00
1 parent e804351205
commit 93866c42fc
2 files changed
+31 -3

No files matched your search

+8 -3
View File
@@ -1,7 +1,7 @@
// CPU picks of movers and lines for every engine that needs them (Navara's // CPU picks of movers and lines for every engine that needs them (Navara's
// hover, drill and synchronous pick, the fake engine): projected positions // hover, drill and synchronous pick, the fake engine): projected positions
// within the drawn size (+3 px, Navara's PICK_RADIUS), lines within half // within the drawn size (+3 px, Navara's PICK_RADIUS), lines within 8 px
// their width (+3 px), hidden behind the globe excluded, nearest to the // (or half their width + 3 px when wider), hidden behind the globe excluded, nearest to the
// camera first. Pure: src/geo and the engine's projector only. // camera first. Pure: src/geo and the engine's projector only.
import * as geo from '../geo/index.js'; import * as geo from '../geo/index.js';
@@ -12,6 +12,8 @@ const scratchScreen = { x: 0, y: 0 };
const scratchScreenB = { x: 0, y: 0 }; const scratchScreenB = { x: 0, y: 0 };
export const PICK_RADIUS_PX = 3; export const PICK_RADIUS_PX = 3;
/** A line is picked within 8 px of its drawn segments (thin trails too). */
export const LINE_PICK_PX = 8;
/** Distance from point p to segment ab (2D). */ /** Distance from point p to segment ab (2D). */
export function distanceToSegment(p, a, b) { export function distanceToSegment(p, a, b) {
@@ -102,7 +104,10 @@ export function createCpuPicker({ projector, camera, movers, lines }) {
function lineHits(at, hits) { function lineHits(at, hits) {
for (const { options, flat, modelMatrix } of lines()) { for (const { options, flat, modelMatrix } of lines()) {
if (options.pickId === undefined) continue; if (options.pickId === undefined) continue;
const limit = options.widthPx / 2 + PICK_RADIUS_PX; const limit = Math.max(
LINE_PICK_PX,
options.widthPx / 2 + PICK_RADIUS_PX,
);
let best = null; let best = null;
for (let index = 0; index + 1 < flat.length / 3; index++) { for (let index = 0; index + 1 < flat.length / 3; index++) {
const a = transformed(modelMatrix, flat, index, scratch); const a = transformed(modelMatrix, flat, index, scratch);
+23
View File
@@ -249,3 +249,26 @@ test('a GPU answer naming an instance drawn far from the click is stale', async
assert.equal((await answer).id, 'drawn-here'); assert.equal((await answer).id, 'drawn-here');
assert.equal(picking.clickStats().stale, 1); assert.equal(picking.clickStats().stale, 1);
}); });
test('a thin line is picked within 8 px of its segments', () => {
const line = {
options: normalizeLineOptions({
id: 'trail',
widthPx: 1,
color: [1, 1, 1, 1],
pickId: 'trail-1',
}),
flat: new Float64Array([0, 50, 100, 200, 50, 100]),
modelMatrix: null,
};
const picking = createNavaraPicking({
view: fakeView(),
projector,
camera,
movers: () => [],
lines: () => [line],
resolveBatch: () => null,
});
assert.equal(picking.pick({ x: 100, y: 57.5 })?.id, 'trail-1');
assert.equal(picking.pick({ x: 100, y: 58.5 }), undefined);
});