Files
Pier_v1/src/core/rebar/coping-fold.ts
T
ypshimandClaude Haiku 5.5 6a15a13fc7 feat(rebar): 꺾인 코핑 철근을 꺾임에 맞춰 접는다 (교축 꺾은 철근 · 미터 전단 1조)
편 좌표(g)로 계산한 다주형 코핑 철근을 꺾인 코핑에 맞춘다 (coping-fold.ts):
- 교축 방향 철근은 그 n에서 미터면과 만나는 점에서 꺾은 한 개, 길이는 실제 경로 (바깥 길고 안쪽 짧음)
- 교축 직각 철근(전단·크로스타이)은 미터 쐐기에 걸리면 빼고, 꺾임점에 가장 가까운 전단 위치 1조를 미터 방향으로
- 상면 1단 이음은 꺾임점에서 l_s/2 + 미터 퍼짐 밖으로
조각(pieces)은 구간별 국부 좌표로 담아 3D가 그대로 그릴 수 있게 한다. 곧은 코핑은 바뀌지 않는다.

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

74 lines
4.2 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.
/**
* 꺾인 코핑의 철근 접기 (결정 2026-11-10). Pure.
*
* 코핑 철근은 코핑을 편 좌표 (g = 코핑 시작에서 축을 따라 잰 거리, EL, n)로 계산한다. 꺾인 코핑에서는:
* - 교축 방향 철근은 꺾임점에서 꺾은 한 개: 그 철근의 n에서 미터면과 만나는 점에서 꺾는다. 길이는 실제 경로로 다시 잰다
* (편 길이 + a0(n) − a1(n), 바깥쪽이 길고 안쪽이 짧다).
* - 교축 직각 철근 (전단 · 크로스타이)은 각 구간 축에 직각이다. 미터 쐐기에 걸리는 것은 빼고, 꺾임점에 전단철근
* 1조를 미터 방향으로 둔다 (가장 가까운 전단 위치의 모양 그대로).
* 조각 좌표는 각 구간의 국부 좌표 (s, EL, n)이다: 구간 0은 s = g, 구간 1은 s = g − 꺾임점 g.
*/
import type { PierModel } from '../geometry';
export type FoldPoint = [number, number, number];
export interface FoldPiece { k: 0 | 1; pts: FoldPoint[] }
export interface CopingFold {
/** g of the kink (m). */
kinkG: number;
/** Local s of the mitre at offset n, in stretch 0 (from the kink, + ahead) and stretch 1 (from its base). */
a0: (n: number) => number;
a1: (n: number) => number;
/** Largest |a| over the coping width (m): half the wedge the mitre sweeps. */
wedge: number;
}
export function copingFold(model: PierModel): CopingFold | null {
if (!model.kink || !model.mitre || model.stretches.length < 2) return null;
const m = model.mitre;
const dot = (a: { e: number; n: number }, b: { e: number; n: number }) => a.e * b.e + a.n * b.n;
const [s0, s1] = model.stretches;
// A point (s, n) of stretch k is on the mitre when (s − s_kink)·(u·m) + n·(nrm·m) = 0.
const r0 = dot(s0.nrm, m) / dot(s0.u, m);
const r1 = dot(s1.nrm, m) / dot(s1.u, m);
return { kinkG: s1.base, a0: (n) => -n * r0, a1: (n) => -n * r1, wedge: (model.copingB / 2) * Math.max(Math.abs(r0), Math.abs(r1)) };
}
const pathLength = (pts: FoldPoint[]) => pts.slice(1).reduce((sum, p, i) => sum + Math.hypot(p[0] - pts[i][0], p[1] - pts[i][1], p[2] - pts[i][2]), 0);
/** One bar from the unfolded g into stretch pieces; a run across the kink turns at its own mitre point. Length in mm. */
export function foldBar(f: CopingFold, pts: FoldPoint[]): { pieces: FoldPiece[]; length: number } {
const side = (x: number): 0 | 1 => (x <= f.kinkG + 1e-12 ? 0 : 1);
const local = (p: FoldPoint, k: 0 | 1): FoldPoint => [k === 0 ? p[0] : p[0] - f.kinkG, p[1], p[2]];
const pieces: FoldPiece[] = [{ k: side(pts[0][0]), pts: [local(pts[0], side(pts[0][0]))] }];
for (let i = 1; i < pts.length; i++) {
const a = pts[i - 1], b = pts[i];
const ka = side(a[0]), kb = side(b[0]);
if (ka !== kb) {
const t = (f.kinkG - a[0]) / (b[0] - a[0]);
const z = a[1] + t * (b[1] - a[1]), n = a[2] + t * (b[2] - a[2]);
const m0: FoldPoint = [f.kinkG + f.a0(n), z, n], m1: FoldPoint = [f.a1(n), z, n];
// Both are the same world point on the mitre, seen from each stretch.
if (ka === 0) { pieces[pieces.length - 1].pts.push(m0); pieces.push({ k: 1, pts: [m1] }); }
else { pieces[pieces.length - 1].pts.push(m1); pieces.push({ k: 0, pts: [m0] }); }
}
pieces[pieces.length - 1].pts.push(local(b, kb));
}
return { pieces, length: pieces.reduce((sum, p) => sum + pathLength(p.pts), 0) * 1000 };
}
/** True for a bar across the width: every point at one g. */
export const isTransverse = (pts: FoldPoint[]) => pts.every((p) => Math.abs(p[0] - pts[0][0]) < 1e-9) && pts.some((p) => Math.abs(p[2] - pts[0][2]) > 1e-9);
/** True when a transverse bar at g would cut the mitre plane somewhere over its width. */
export function inWedge(f: CopingFold, pts: FoldPoint[], margin = 0): boolean {
const reach = Math.max(...pts.map((p) => Math.max(Math.abs(f.a0(p[2])), Math.abs(f.a1(p[2])))));
return Math.abs(pts[0][0] - f.kinkG) <= reach + margin + 1e-9;
}
/** A transverse bar laid on the mitre (stretch 0 coordinates): each point moves to the mitre at its n, plus `dx` along the axis. */
export function onMitre(f: CopingFold, pts: FoldPoint[], dx = 0): { pieces: FoldPiece[]; length: number } {
const moved = pts.map((p): FoldPoint => [f.kinkG + f.a0(p[2]) + dx, p[1], p[2]]);
return { pieces: [{ k: 0, pts: moved }], length: pathLength(moved) * 1000 };
}