Files
Pier_v1/src/core/axis.ts
T

99 lines
3.4 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.
import type { Vec2 } from './geometry';
/**
* Coping axis as a polyline IP1 → (IP4) → IP3 (spec §4.1). Each stretch is straight and has its own frame.
* Coordinates: k = stretch index, s = arc length from IP1 (the same s for both stretches), n = offset to the right of u.
* The lateral offset uses the same convention as the straight model: nrm = (u.n, −u.e).
*/
export interface AxisSegment {
start: Vec2;
u: Vec2;
nrm: Vec2;
/** Arc length at the start of this stretch. */
s0: number;
}
export interface Axis {
segments: AxisSegment[];
/** Total arc length L_IP. */
length: number;
/** Bend point IP4, or null for a straight axis. */
kink: Vec2 | null;
/** Mitre normal u1 + u2 (unit) at the kink; null for a straight axis. A point is on stretch 0 when (p − kink)·mitre ≤ 0. */
mitre: Vec2 | null;
}
export interface AxisPoint {
k: number;
s: number;
n: number;
}
const dist = (a: Vec2, b: Vec2) => Math.hypot(b.e - a.e, b.n - a.n);
const sub = (a: Vec2, b: Vec2): Vec2 => ({ e: a.e - b.e, n: a.n - b.n });
const dot = (a: Vec2, b: Vec2) => a.e * b.e + a.n * b.n;
const unit = (v: Vec2): Vec2 => {
const len = Math.hypot(v.e, v.n);
if (len === 0) throw new Error('축 길이가 0입니다.');
return { e: v.e / len, n: v.n / len };
};
function frame(start: Vec2, end: Vec2): { u: Vec2; nrm: Vec2 } {
const u = unit(sub(end, start));
return { u, nrm: { e: u.n, n: -u.e } };
}
export function buildAxis(ip1: Vec2, ip3: Vec2, kink: Vec2 | null): Axis {
if (!kink) {
const f = frame(ip1, ip3);
return { segments: [{ start: ip1, ...f, s0: 0 }], length: dist(ip1, ip3), kink: null, mitre: null };
}
const a = dist(ip1, kink);
const f1 = frame(ip1, kink);
const f2 = frame(kink, ip3);
return {
segments: [
{ start: ip1, ...f1, s0: 0 },
{ start: kink, ...f2, s0: a },
],
length: a + dist(kink, ip3),
kink,
mitre: unit({ e: f1.u.e + f2.u.e, n: f1.u.n + f2.u.n }),
};
}
/** Stretch and local coordinates of a world point. Points on the stretch-0 side of the mitre plane belong to stretch 0. */
export function toAxis(axis: Axis, p: Vec2): AxisPoint {
const k = axis.mitre && axis.kink && dot(sub(p, axis.kink), axis.mitre) > 0 ? 1 : 0;
const seg = axis.segments[k];
const d = sub(p, seg.start);
return { k, s: seg.s0 + dot(d, seg.u), n: dot(d, seg.nrm) };
}
/** World point of local coordinates (inverse of toAxis for the same k). */
export function fromAxis(axis: Axis, q: AxisPoint): Vec2 {
const seg = axis.segments[q.k];
const ds = q.s - seg.s0;
return {
e: seg.start.e + ds * seg.u.e + q.n * seg.nrm.e,
n: seg.start.n + ds * seg.u.n + q.n * seg.nrm.n,
};
}
/** Arc-length span of the coping of length L, centred on the axis (spec §4.2: centre at L_IP / 2). */
export function copingSpan(axis: Axis, L: number): { sStart: number; sEnd: number } {
return { sStart: axis.length / 2 - L / 2, sEnd: axis.length / 2 + L / 2 };
}
/** Arc length of the kink, or null for a straight axis. */
export function kinkArc(axis: Axis): number | null {
return axis.kink ? axis.segments[1].s0 : null;
}
/** Positions (arc length) closer than minDistance to the kink. Used to refuse columns at the bend. */
export function nearKink(axis: Axis, sPositions: number[], minDistance: number): number[] {
const a = kinkArc(axis);
if (a === null) return [];
return sPositions.filter((s) => Math.abs(s - a) < minDistance);
}