fix(B06): BOX암거 끝 고름 판정면 = 구체 끝~에이프런 바깥 · 본체 = 두 끝 사이 · 날개벽 없는 끝 = 바깥 0.3m 띠(108-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -28,12 +28,40 @@ export type {
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
/** 고르는 부재 — 좌 · 우 끝 또는 본체(구체 가운데 1/3 · 좌측 「본체」 상자 · 66-1). */
|
||||
/** 고르는 부재 — 좌 · 우 끝 또는 본체(두 구체 끝 사이 · 좌측 「본체」 상자 · 66-1 · 108-3). */
|
||||
export type BoxPick = BoxSideRole | "body";
|
||||
|
||||
/** 선택 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
||||
export type BoxHighlightSetter = (role: BoxPick | null) => void;
|
||||
|
||||
/** 날개벽(에이프런) 없는 끝의 판정 띠 폭(m) — 구체 끝 바깥으로. */
|
||||
const BARE_END_HIT_M = 0.3;
|
||||
|
||||
/**
|
||||
* 고르기 판정면(108-3) — 본체 = 두 구체 끝 사이 · 끝 = 구체 끝(`side.offset`) ~ 에이프런 바깥
|
||||
* (`apron[1]`) · 에이프런이 없으면 구체 끝 바깥 얇은 띠. 높이 = 저판 밑 ~ 상판 위. 서로 겹치지 않는다.
|
||||
*/
|
||||
export function boxHitBands(layout: BoxLayout): Array<[BoxPick, OffsetPoint[]]> {
|
||||
const top = Math.max(...layout.topSlab.map((point) => point.elevation));
|
||||
const bottom = Math.min(...layout.bottomSlab.map((point) => point.elevation));
|
||||
const band = (from: number, to: number): OffsetPoint[] => [
|
||||
{ offset: from, elevation: top },
|
||||
{ offset: to, elevation: top },
|
||||
{ offset: to, elevation: bottom },
|
||||
{ offset: from, elevation: bottom },
|
||||
];
|
||||
return [
|
||||
["body", band(layout.sides[0].offset, layout.sides[1].offset)],
|
||||
...layout.sides.map((side): [BoxPick, OffsetPoint[]] => {
|
||||
const outward = side.role === "left" ? 1 : -1;
|
||||
const outer = side.apron.length
|
||||
? side.apron[1].offset
|
||||
: side.offset + outward * BARE_END_HIT_M;
|
||||
return [side.role, band(side.offset, outer)];
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* BOX암거 구체 오버레이 — computeBoxLayout 결과를 그린다.
|
||||
* `onSelectSide`가 오면 좌·우 절반이 선택 대상이 된다(조정창이 그 측을 잡는다).
|
||||
@@ -172,27 +200,11 @@ export function appendBoxOverlay(
|
||||
label.textContent = layout.label.text;
|
||||
layer.append(label);
|
||||
|
||||
// 투명 겹면 — 구체 가운데 1/3 = 본체, 그 바깥 양쪽 = 그 끝(66-1). 어느 것을 조정할지 클릭으로 고른다.
|
||||
// 투명 겹면(108-3) — 본체 = 두 구체 끝 사이 · 끝 = 구체 끝 ~ 에이프런 바깥(날개벽 없으면 바깥
|
||||
// 얇은 띠) · 높이 저판 밑 ~ 상판 위. 어느 것을 조정할지 클릭으로 고른다.
|
||||
const hits = new Map<BoxPick, SVGPolygonElement>();
|
||||
if (onSelectSide) {
|
||||
const midOffset = (layout.sides[0].offset + layout.sides[1].offset) / 2;
|
||||
const third = (layout.sides[0].offset - layout.sides[1].offset) / 6;
|
||||
const top = Math.max(...layout.topSlab.map((point) => point.elevation));
|
||||
const bottom = Math.min(...layout.bottomSlab.map((point) => point.elevation));
|
||||
const band = (from: number, to: number): OffsetPoint[] => [
|
||||
{ offset: from, elevation: top },
|
||||
{ offset: to, elevation: top },
|
||||
{ offset: to, elevation: bottom },
|
||||
{ offset: from, elevation: bottom },
|
||||
];
|
||||
const parts: Array<[BoxPick, OffsetPoint[]]> = [
|
||||
["body", band(midOffset - third, midOffset + third)],
|
||||
...layout.sides.map((side): [BoxPick, OffsetPoint[]] => {
|
||||
const outer = side.apron.length ? side.apron[1].offset : side.offset;
|
||||
const inner = midOffset + Math.sign(side.offset - midOffset) * Math.abs(third);
|
||||
return [side.role, band(inner, outer)];
|
||||
}),
|
||||
];
|
||||
const parts: Array<[BoxPick, OffsetPoint[]]> = boxHitBands(layout);
|
||||
for (const [role, points] of parts) {
|
||||
const hit = polygon(
|
||||
points,
|
||||
|
||||
Reference in New Issue
Block a user