Improve EPC clickability: shape-accurate hit testing with generous margin
Replace bounding-box hit test with proper shape-aware check that tests proximity to line segments and oriented end boxes with 8px hit margin, making the thin EPC much easier to select and drag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
82bb1b46c8
commit
d721f47757
@ -146,11 +146,59 @@ function pointInInduction(px: number, py: number, sym: PlacedSymbol): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Check if a point is near the EPC shape (line segments + end boxes) */
|
||||
function pointInEpc(px: number, py: number, sym: PlacedSymbol): boolean {
|
||||
const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;
|
||||
const hitMargin = 8; // generous click target around thin lines
|
||||
const ox = sym.x, oy = sym.y;
|
||||
|
||||
// Check left box
|
||||
const lb = EPC_CONFIG.leftBox;
|
||||
if (waypoints.length >= 2) {
|
||||
const p0x = ox + waypoints[0].x, p0y = oy + waypoints[0].y;
|
||||
const p1x = ox + waypoints[1].x, p1y = oy + waypoints[1].y;
|
||||
const angle = Math.atan2(p1y - p0y, p1x - p0x);
|
||||
const cos = Math.cos(-angle), sin = Math.sin(-angle);
|
||||
const dx = px - p0x, dy = py - p0y;
|
||||
const lx = dx * cos - dy * sin, ly = dx * sin + dy * cos;
|
||||
if (lx >= -lb.w - hitMargin && lx <= hitMargin && ly >= -lb.h / 2 - hitMargin && ly <= lb.h / 2 + hitMargin) return true;
|
||||
}
|
||||
|
||||
// Check right box
|
||||
const rb = EPC_CONFIG.rightBox;
|
||||
if (waypoints.length >= 2) {
|
||||
const last = waypoints[waypoints.length - 1];
|
||||
const prev = waypoints[waypoints.length - 2];
|
||||
const plx = ox + last.x, ply = oy + last.y;
|
||||
const angle = Math.atan2(ply - (oy + prev.y), plx - (ox + prev.x));
|
||||
const cos = Math.cos(-angle), sin = Math.sin(-angle);
|
||||
const dx = px - plx, dy = py - ply;
|
||||
const lx = dx * cos - dy * sin, ly = dx * sin + dy * cos;
|
||||
if (lx >= -rb.w - hitMargin && lx <= hitMargin && ly >= -rb.h / 2 - hitMargin && ly <= rb.h / 2 + hitMargin) return true;
|
||||
}
|
||||
|
||||
// Check line segments
|
||||
for (let i = 0; i < waypoints.length - 1; i++) {
|
||||
const ax = ox + waypoints[i].x, ay = oy + waypoints[i].y;
|
||||
const bx = ox + waypoints[i + 1].x, by = oy + waypoints[i + 1].y;
|
||||
const sdx = bx - ax, sdy = by - ay;
|
||||
const len2 = sdx * sdx + sdy * sdy;
|
||||
if (len2 === 0) continue;
|
||||
const t = Math.max(0, Math.min(1, ((px - ax) * sdx + (py - ay) * sdy) / len2));
|
||||
const nx = ax + t * sdx, ny = ay + t * sdy;
|
||||
const dist = Math.sqrt((px - nx) ** 2 + (py - ny) ** 2);
|
||||
if (dist <= hitMargin) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Hit test a single symbol against its actual shape */
|
||||
function pointInSymbol(px: number, py: number, sym: PlacedSymbol): boolean {
|
||||
if (isCurvedType(sym.symbolId)) return pointInArcBand(px, py, sym);
|
||||
if (isSpurType(sym.symbolId)) return pointInTrapezoid(px, py, sym);
|
||||
if (isInductionType(sym.symbolId)) return pointInInduction(px, py, sym);
|
||||
if (isEpcType(sym.symbolId)) return pointInEpc(px, py, sym);
|
||||
return pointInRect(px, py, sym.x, sym.y, sym.w, sym.h);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user