feat(core,view): bent coping stage 2 — mitred coping, stretch-local bearings, blocks and columns
- buildPier builds on buildAxis: each object carries its stretch k and a local s from that stretch's base; coping parts are per stretch with the mitre plane trimming the overlap in the scene (renderer.localClippingEnabled). - Straight P1 output is unchanged (regression tests pass). - Bent coping is supported for 현장타설말뚝 only; footings and piles on a bent axis remain 미실시 until stage 3. Kink outside the coping and columns within the Level half-width of the kink are refused. - W03/W04 use coping coordinates (g). - Tests: constructed 60° bend — world coordinates of bearings, blocks and the column survive the stretch frames; mitre sides; scene mesh count; refusals. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46709be532
commit
5371932cca
6 files changed
+295
-78
No files matched your search
+33
-13
@@ -30,14 +30,32 @@ const COLORS: Record<PartName, number> = {
|
||||
* The scene uses y as up and places the first column centre at the origin (spec §1).
|
||||
* Basis vectors: X = (u.e, 0, -u.n), Z = (nrm.e, 0, -nrm.n). The determinant is +1, so faces keep their winding.
|
||||
*/
|
||||
export function frameMatrix(m: PierModel): THREE.Matrix4 {
|
||||
const X = new THREE.Vector3(m.u.e, 0, -m.u.n);
|
||||
export function stretchMatrix(m: PierModel, k: number): THREE.Matrix4 {
|
||||
const st = m.stretches[k];
|
||||
const X = new THREE.Vector3(st.u.e, 0, -st.u.n);
|
||||
const Y = new THREE.Vector3(0, 1, 0);
|
||||
const Z = new THREE.Vector3(m.nrm.e, 0, -m.nrm.n);
|
||||
const origin = new THREE.Vector3(m.bp1.e - m.pivot.e, 0, -(m.bp1.n - m.pivot.n));
|
||||
const Z = new THREE.Vector3(st.nrm.e, 0, -st.nrm.n);
|
||||
const origin = new THREE.Vector3(st.origin.e - m.pivot.e, 0, -(st.origin.n - m.pivot.n));
|
||||
return new THREE.Matrix4().makeBasis(X, Y, Z).setPosition(origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mitre plane at the bend point. Stretch 0 keeps the side (p − kink)·mitre ≤ 0 and stretch 1 the other side,
|
||||
* so each stretch's full-length slab is trimmed to its own half and the two meet at the mitred joint.
|
||||
* Three.js keeps fragments with a non-negative distance from the plane, hence the sign flip for stretch 0.
|
||||
*/
|
||||
/** Straight-frame matrix (stretch 0). */
|
||||
export function frameMatrix(m: PierModel): THREE.Matrix4 {
|
||||
return stretchMatrix(m, 0);
|
||||
}
|
||||
|
||||
export function mitrePlane(m: PierModel, k: number): THREE.Plane | undefined {
|
||||
if (!m.kink || !m.mitre) return undefined;
|
||||
const normal = new THREE.Vector3(m.mitre.e, 0, -m.mitre.n).multiplyScalar(k === 0 ? -1 : 1);
|
||||
const point = new THREE.Vector3(m.kink.e - m.pivot.e, 0, -(m.kink.n - m.pivot.n));
|
||||
return new THREE.Plane().setFromNormalAndCoplanarPoint(normal, point);
|
||||
}
|
||||
|
||||
export interface PierScene {
|
||||
root: THREE.Group;
|
||||
parts: Record<PartName, THREE.Group>;
|
||||
@@ -45,15 +63,15 @@ export interface PierScene {
|
||||
}
|
||||
|
||||
export function buildPierScene(m: PierModel): PierScene {
|
||||
const M = frameMatrix(m);
|
||||
const Ms = m.stretches.map((_, k) => stretchMatrix(m, k));
|
||||
const parts = {} as Record<PartName, THREE.Group>;
|
||||
for (const key of Object.keys(PART_LABELS) as PartName[]) {
|
||||
parts[key] = new THREE.Group();
|
||||
parts[key].name = key;
|
||||
}
|
||||
|
||||
const add = (part: PartName, geometry: THREE.BufferGeometry, opacity = 1) => {
|
||||
geometry.applyMatrix4(M);
|
||||
const add = (part: PartName, geometry: THREE.BufferGeometry, opacity = 1, k = 0, clippingPlanes?: THREE.Plane[]) => {
|
||||
geometry.applyMatrix4(Ms[k]);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: COLORS[part],
|
||||
roughness: 0.75,
|
||||
@@ -61,22 +79,24 @@ export function buildPierScene(m: PierModel): PierScene {
|
||||
transparent: opacity < 1,
|
||||
opacity,
|
||||
side: THREE.DoubleSide,
|
||||
clippingPlanes,
|
||||
});
|
||||
parts[part].add(new THREE.Mesh(geometry, material));
|
||||
};
|
||||
|
||||
// Coping: each part's elevation outline extruded across its width (no boolean operations).
|
||||
// Coping: each part's elevation outline extruded across its width (no boolean operations), trimmed by the mitre plane.
|
||||
for (const part of m.copingParts) {
|
||||
const shape = new THREE.Shape(part.profile.map(([s, z]) => new THREE.Vector2(s, z)));
|
||||
const coping = new THREE.ExtrudeGeometry(shape, { depth: part.nTo - part.nFrom, bevelEnabled: false });
|
||||
coping.translate(0, 0, part.nFrom);
|
||||
add('coping', coping);
|
||||
const plane = mitrePlane(m, part.k);
|
||||
add('coping', coping, 1, part.k, plane ? [plane] : undefined);
|
||||
}
|
||||
|
||||
for (const c of m.columns) {
|
||||
const g = new THREE.CylinderGeometry(c.diameter / 2, c.diameter / 2, c.top - c.bottom, 48);
|
||||
g.translate(c.s, (c.top + c.bottom) / 2, 0);
|
||||
add('columns', g);
|
||||
add('columns', g, 1, c.k);
|
||||
}
|
||||
|
||||
for (const f of m.footings) {
|
||||
@@ -94,17 +114,17 @@ export function buildPierScene(m: PierModel): PierScene {
|
||||
for (const p of m.piles) {
|
||||
const g = new THREE.CylinderGeometry(p.diameter / 2, p.diameter / 2, p.top - p.bottom, 20);
|
||||
g.translate(p.s, (p.top + p.bottom) / 2, p.n);
|
||||
add('piles', g);
|
||||
add('piles', g, 1, p.k);
|
||||
}
|
||||
|
||||
for (const b of m.blocks) {
|
||||
const h = b.top - b.bottom;
|
||||
const g = new THREE.BoxGeometry(1.2, h, 1.2);
|
||||
g.translate(b.s, b.bottom + h / 2, b.n);
|
||||
add('blocks', g);
|
||||
add('blocks', g, 1, b.k);
|
||||
const pad = new THREE.BoxGeometry(1.0, 0.05, 1.0);
|
||||
pad.translate(b.s, b.padTop - 0.025, b.n);
|
||||
add('pads', pad);
|
||||
add('pads', pad, 1, b.k);
|
||||
}
|
||||
|
||||
// Ground: a translucent plane at the ground EL, used only to judge soil cover (not a computed input).
|
||||
|
||||
Reference in new issue
Block a user