From 704dae26ae51d14b142c906758416e2b88b6ada0 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 8 Oct 2026 16:20:13 +0900 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfix(B06):=20BOX=EC=95=94=EA=B1=B0=20?= =?UTF-8?q?=EB=81=9D=20=EA=B3=A0=EB=A6=84=20=ED=8C=90=EC=A0=95=EB=A9=B4=20?= =?UTF-8?q?=3D=20=EA=B5=AC=EC=B2=B4=20=EB=81=9D~=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=94=84=EB=9F=B0=20=EB=B0=94=EA=B9=A5=20=C2=B7=20=EB=B3=B8?= =?UTF-8?q?=EC=B2=B4=20=3D=20=EB=91=90=20=EB=81=9D=20=EC=82=AC=EC=9D=B4=20?= =?UTF-8?q?=C2=B7=20=EB=82=A0=EA=B0=9C=EB=B2=BD=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EB=81=9D=20=3D=20=EB=B0=94=EA=B9=A5=200.3m=20=EB=9D=A0(108-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7 --- B06_Section/B06_Section_UI_Cross_Box.ts | 52 +++++++++++++++---------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_Box.ts b/B06_Section/B06_Section_UI_Cross_Box.ts index 711175ff1..fd5820f08 100644 --- a/B06_Section/B06_Section_UI_Cross_Box.ts +++ b/B06_Section/B06_Section_UI_Cross_Box.ts @@ -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(); 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,