diff --git a/src/core/geometry.ts b/src/core/geometry.ts index dec96df..cfa3b2c 100644 --- a/src/core/geometry.ts +++ b/src/core/geometry.ts @@ -203,6 +203,18 @@ export function buildPier(input: PierInput): PierModel { const ip3v: Vec2 = { e: ip3.x, n: ip3.y }; const kink = bent ? solveIp4(ip1v, ip2v, ip3v, ip4.angleDeg) : null; const axis = buildAxis(ip1v, ip3v, kink); + if (kink) { + // The bend must move the axis forward: the kink ahead of IP1 along IP1→IP2, and IP3 ahead of the kink along the turned direction. + const d1 = { e: ip2v.e - ip1v.e, n: ip2v.n - ip1v.n }; + const len1 = Math.hypot(d1.e, d1.n); + const rad = (-ip4.angleDeg * Math.PI) / 180; + const d2 = { e: (d1.e * Math.cos(rad) - d1.n * Math.sin(rad)) / len1, n: (d1.e * Math.sin(rad) + d1.n * Math.cos(rad)) / len1 }; + const kinkAhead = (kink.e - ip1v.e) * d1.e + (kink.n - ip1v.n) * d1.n > 0; + const ip3Ahead = (ip3v.e - kink.e) * d2.e + (ip3v.n - kink.n) * d2.n > 0; + if (!kinkAhead || !ip3Ahead) { + throw new Error('꺾임각과 IP 배치가 맞지 않습니다. IP4가 축 뒤로 넘어갑니다. IP3이 꺾인 방향의 앞쪽에 있는지 확인하세요.'); + } + } const lengthIp = axis.length; const sStart = copingSpan(axis, L).sStart; const arcA = kinkArc(axis); diff --git a/tests/axis.test.ts b/tests/axis.test.ts index f84fa76..d30ac47 100644 --- a/tests/axis.test.ts +++ b/tests/axis.test.ts @@ -37,21 +37,21 @@ describe('straight axis (regression against spec §12)', () => { }); }); -// Synthetic bend: IP1 (0, 0), IP2 (1, 0), IP3 (6, 2), 60° right turn. The P1 sheet is straight (IP1, IP2, IP3 collinear), -// so P1 cannot exercise a bend. The kink falls at about s = 7.15 m, inside a 6 m coping centred on the axis. +// Synthetic bend: IP1 (0, 0), IP2 (1, 0), kink (7, 0), IP3 (9, −3.464), a 60° right turn with IP3 ahead on the turned line. +// The P1 sheet is straight (IP1, IP2, IP3 collinear), so P1 cannot exercise a bend. describe('bent axis IP1 → IP4 → IP3', () => { const p1 = loadP1(); const ip1 = { e: 0, n: 0 }; const ip2 = { e: 1, n: 0 }; - const ip3 = { e: 6, n: 2 }; + const ip3 = { e: 9, n: -3.4641016 }; const kink = solveIp4(ip1, ip2, ip3, 60)!; const axis = buildAxis(ip1, ip3, kink); const a = kinkArc(axis)!; - it('kink lies on the IP1→IP2 line, at s ≈ 7.15, and the arc length adds the two stretches', () => { + it('kink lies on the IP1→IP2 line, at s = 7, and the arc length adds the two stretches', () => { const cross = (kink.e - ip1.e) * (ip2.n - ip1.n) - (kink.n - ip1.n) * (ip2.e - ip1.e); expect(cross).toBeCloseTo(0, 9); - expect(a).toBeCloseTo(7.15, 1); + expect(a).toBeCloseTo(7, 6); const len2 = Math.hypot(ip3.e - kink.e, ip3.n - kink.n); expect(axis.length).toBeCloseTo(a + len2, 9); }); diff --git a/tests/bent-coping.test.ts b/tests/bent-coping.test.ts index 8d1ce3f..0f130d2 100644 --- a/tests/bent-coping.test.ts +++ b/tests/bent-coping.test.ts @@ -6,16 +6,16 @@ import { buildPierScene, mitrePlane } from '../src/view/scene'; import type { PierInput } from '../src/core/types'; import { loadP1 } from './helpers'; -// Constructed bend: IP1 (0, 0), IP2 (1, 0), IP3 (6, 2), 60° right turn. The kink sits at arc ≈ 7.15 m, -// inside a 6 m coping centred on the axis, and a T형 column (arc ≈ 4.73) stays clear of it. +// Constructed bend: IP1 (0, 0), IP2 (1, 0), 60° right turn, kink (7, 0), IP3 (9, −3.464) four metres ahead on the turned line. +// Axis length 11 m; a 6 m coping centred on it has its kink at g = 4.5, and the T형 column (g = 3) stays 1.5 m clear of it. const IP1 = { e: 0, n: 0 }; -const IP3 = { e: 6, n: 2 }; +const IP3 = { e: 9, n: -3.4641016 }; const ANGLE = 60; const L = 6; const B = 3.1; const FRONT_N = -0.7; const BACK_N = 0.6; -const GS = [0.8, 1.9, 3.0, 4.1, 5.2]; +const GS = [0.6, 1.9, 3.2]; function bentInput(foundationType: PierInput['foundationType'] = '현장타설말뚝'): PierInput { const base = loadP1(); @@ -133,3 +133,11 @@ describe('bent coping model (stage 2)', () => { expect(() => buildPier(bad)).toThrow('꺾임점이 코핑 범위 밖'); }); }); + +describe('bend geometry must move forward', () => { + it('IP3 behind the kink on the turned line is refused', () => { + const base = bentInput(); + const ips = base.ips.map((p) => (p.name === 'IP3' ? { ...p, x: 6, y: 2 } : p)); + expect(() => buildPier({ ...base, ips })).toThrow('꺾임각과 IP 배치'); + }); +});