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;
|
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 */
|
/** Hit test a single symbol against its actual shape */
|
||||||
function pointInSymbol(px: number, py: number, sym: PlacedSymbol): boolean {
|
function pointInSymbol(px: number, py: number, sym: PlacedSymbol): boolean {
|
||||||
if (isCurvedType(sym.symbolId)) return pointInArcBand(px, py, sym);
|
if (isCurvedType(sym.symbolId)) return pointInArcBand(px, py, sym);
|
||||||
if (isSpurType(sym.symbolId)) return pointInTrapezoid(px, py, sym);
|
if (isSpurType(sym.symbolId)) return pointInTrapezoid(px, py, sym);
|
||||||
if (isInductionType(sym.symbolId)) return pointInInduction(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);
|
return pointInRect(px, py, sym.x, sym.y, sym.w, sym.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user