Files
Pier_v1/src/core/abut/wings.ts
T
ypshimandClaude Haiku 5.5 786c4a73dd fix(abut): 날개벽의 연직 헌치 삭제
- 위 연직 헌치(흉벽 배면)와 아래 연직 헌치(벽체 배면) 삭제. 관련 코드와 Haunch 타입 제거
- 저판 상면 헌치는 유지
- 테스트 갱신. 전체 637건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-03 17:19:35 +09:00

144 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 교대 날개벽 · AA3b (2026-11-03, spec_abut §4.7): 날개벽 평면, 상단선, 단부 하단. 순수 함수.
*
* - 평면: 교좌면 끝(IP1 끝 s = 0 또는 IP3 끝 s = L)에서 날개벽 선(AA2)을 따라 길이 6.5 m. 두께 0.45 m는 교대 안쪽으로 둔다.
* - 상단: 시작점 = 흉벽 상단 EL(그 끝의 s), 끝까지 날개벽 경사(i)로 직선.
* - 하단 (일반형): 단부 하단 = 끝점 상단 − 1.5 m. 단축형: 하단 = 저판 상면 높이(평평하게).
* - 좌(①쪽) 날개벽은 받침 ①에 가까운 끝, 우(n쪽)는 반대 끝 (spec_abut §4.7).
* - 하단 선 (AA3c): 저판 뒷굽 끝(배면 −1.8 m)까지는 저판 상면, 그 밖은 뒷굽 끝 상단 모서리에서 단부 하단까지 직선
* (변화단면). 뒷굽 끝까지의 날개벽 거리 = 1.8 / |sin Δ| (단축형 1.807 m와 같은 식).
* - 헌치 300 × 300 (AA3c): 흉벽 배면(−0.5) 모서리에 위 헌치, 벽체 배면(−0.8) 모서리에 아래 헌치. 수직 삼각기둥.
* 위 헌치는 브라켓 상면(흉벽 상단 − 0.85)부터 날개벽 상단까지, 아래 헌치는 저판 상면부터 브라켓 상면까지다.
* - 아직 없음: 등단면·변화단면 수평 단 구분 (QA-12), 저판 측면 윤곽의 정밀 모서리.
*
* 수치는 spec_abut §12 A2 합성 예제(좌 46.838 → 46.714, 하단 45.214 / 우 47.183 → 47.059, 하단 45.559)와 맞춘다.
*/
import type { AbutModel, AbutPoint, WingForm } from './geometry';
import type { AbutMembers } from './members';
export const WING_LENGTH_M = 6.5;
export const WING_THICKNESS_M = 0.45;
/** 단부 높이 (끝점 상단에서 단부 하단까지, m). */
export const WING_END_HEIGHT_M = 1.5;
export interface WingSolid {
side: 'left' | 'right';
form: Exclude<WingForm, 'none'>;
/** 날개벽이 시작하는 축 끝: 0 (IP1 끝) 또는 L (IP3 끝). */
sEnd: number;
/** 평면 네 꼭짓점 (E, N), 순서: 시작, 끝, 끝 안쪽, 시작 안쪽. */
corners: [AbutPoint, AbutPoint, AbutPoint, AbutPoint];
topStartEl: number;
topEndEl: number;
/** 단부 하단 EL (일반형: 끝 상단 − 1.5; 단축형: 저판 상면). */
bottomEndEl: number;
/** 축 끝에서 날개벽 선을 따라 잰 뒷굽 끝까지의 거리 (m). */
heelDistanceM: number;
/** 상단선 (d = 축 끝에서 날개벽 방향 거리 m, EL m). */
topLine: { dM: number; el: number }[];
/** 하단선: 저판 상면 → 뒷굽 끝 → 단부 하단 (일반형), 또는 저판 상면 평탄 (단축형). */
bottomLine: { dM: number; el: number }[];
/**
* 저판 상면 헌치 (수평, 2026-11-03): between the 저판 상면 and the 날개벽 내측면. The triangle lies in the plane EL–s,
* with legs HAUNCH_LEG_M along the 저판 상면 and along the 내측면; it runs along the wing from dMFrom (벽체 배면) to dMTo (뒷굽).
*/
footingHaunch?: { dMFrom: number; dMTo: number; el: number };
/** 수평 단 (QA-12, spec_abut §4.7): 등단면 / 변화단면1 / 변화단면2. 경계는 단부 높이 1.5 m와 교좌면 EL. */
bands: WingBand[];
}
export interface WingBand {
name: '등단면' | '변화단면1' | '변화단면2' | '단축형 전구간';
/** 높이 범위 (EL m). */
zTopEl: number;
zBottomEl: number;
}
export const HAUNCH_LEG_M = 0.3;
export function buildWings(model: AbutModel, members: AbutMembers, forms?: { left: WingForm; right: WingForm }): WingSolid[] {
if (!model.wing) return [];
const L = model.length;
const wings = forms ?? model.wing.wings;
// 좌 = 받침 ① 쪽 끝 (① 에 가까운 축 끝).
const firstS = model.bearings[0].s;
const leftS = firstS < L / 2 ? 0 : L;
const rightS = leftS === 0 ? L : 0;
const slope = model.wing.slopePct / 100;
const dirRad = (model.wing.lineDeg * Math.PI) / 180;
const w = { e: Math.cos(dirRad), n: Math.sin(dirRad) };
const ip1 = model.ip1;
// 축 끝에서 안쪽 방향: s = 0이면 +u, s = L이면 −u.
const at = (s: number) => ({ e: ip1.e + model.u.e * s, n: ip1.n + model.u.n * s });
// 배면 쪽으로 가는 날개벽 방향의 횡방향 성분 k = |w · (전면 방향)|: 날개벽 거리 d는 횡방향 t = −d·k.
const frontVec = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide };
const k = Math.abs(w.e * frontVec.e + w.n * frontVec.n);
const footingTop = members.footingTopEl;
const heelDistanceM = Math.abs(members.shape.footingBackM) / k;
const build = (side: 'left' | 'right', sEnd: number, topStart: number, form: Exclude<WingForm, 'none'>): WingSolid => {
const a = sEnd === 0 ? 1 : -1;
const p0 = at(sEnd);
const p1 = { e: p0.e + w.e * WING_LENGTH_M, n: p0.n + w.n * WING_LENGTH_M };
const inward = { e: model.u.e * a * WING_THICKNESS_M, n: model.u.n * a * WING_THICKNESS_M };
const topEnd = topStart + slope * WING_LENGTH_M;
const bottomEnd = form === 'general' ? topEnd - WING_END_HEIGHT_M : footingTop;
const topAt = (d: number) => topStart + slope * d;
const topLine = [
{ dM: 0, el: topStart },
{ dM: WING_LENGTH_M, el: topEnd },
];
const bottomLine =
form === 'general' && heelDistanceM < WING_LENGTH_M
? [
{ dM: 0, el: footingTop },
{ dM: heelDistanceM, el: footingTop },
{ dM: WING_LENGTH_M, el: bottomEnd },
]
: [
{ dM: 0, el: footingTop },
{ dM: WING_LENGTH_M, el: form === 'general' ? bottomEnd : footingTop },
];
// 저판 상면 헌치 (수평): from the 벽체 배면 to the 뒷굽, on the 저판 상면 next to the 날개벽 내측면.
const footingHaunch = {
dMFrom: Math.abs(members.shape.wallBackM) / k,
dMTo: heelDistanceM,
el: footingTop,
};
// 수평 단 (QA-12): 등단면 = 끝 상단 − 1.5 m 위, 변화단면1 = 그 아래 ~ 교좌면 EL, 변화단면2 = 교좌면 EL ~ 저판 상면.
// 교좌면 EL은 날개벽 끝에 가장 가까운 받침의 교좌면이다.
const nearest = model.bearings.reduce((best, b) => (Math.abs(b.s - sEnd) < Math.abs(best.s - sEnd) ? b : best));
const bands: WingBand[] =
form === 'general'
? [
{ name: '등단면', zTopEl: topStart, zBottomEl: bottomEnd },
{ name: '변화단면1', zTopEl: bottomEnd, zBottomEl: nearest.seatEl },
{ name: '변화단면2', zTopEl: nearest.seatEl, zBottomEl: footingTop },
]
: [{ name: '단축형 전구간', zTopEl: topStart, zBottomEl: footingTop }];
return {
side,
form,
sEnd,
corners: [p0, p1, { e: p1.e + inward.e, n: p1.n + inward.n }, { e: p0.e + inward.e, n: p0.n + inward.n }],
topStartEl: topStart,
topEndEl: topEnd,
bottomEndEl: bottomEnd,
heelDistanceM,
topLine,
bottomLine,
footingHaunch,
bands,
};
};
const out: WingSolid[] = [];
if (wings.left !== 'none') out.push(build('left', leftS, leftS === 0 ? members.parapetTopStartEl : members.parapetTopEndEl, wings.left));
if (wings.right !== 'none') out.push(build('right', rightS, rightS === 0 ? members.parapetTopStartEl : members.parapetTopEndEl, wings.right));
return out;
}