feat(abut): 벽체·흉벽 평면을 skew로 변경
- 벽체 블록, 흉벽 배면 단차부, 흉벽 본체를 날개벽 선 방향(s = s_axis + (ws/wt)·t)으로 밀린 프리즘으로 만듦 - 벽체 전면·배면이 저판과 같은 skew 평행사변형을 이루고, 날개벽 바깥 선과 맞닿음 - 테스트: 같은 교좌면 단계 안에서 벽체와 흉벽이 공통 체적 없이 맞닿음 (인접 단계는 경계 상자만 겹치므로 제외). 전체 641건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a756fd11ce
commit
08b1138c96
2 files changed
+23
-8
No files matched your search
+16
-5
@@ -163,6 +163,19 @@ export function buildAbutGroup(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const footingTop = members.footingTopEl;
|
const footingTop = members.footingTopEl;
|
||||||
|
// Skew (2026-11-03): the 벽체·흉벽 plan follows the wing lines, s = s_axis + k·t, with k = ws/wt of the wing direction.
|
||||||
|
const skewAxis = wingDirectionAxis(model);
|
||||||
|
const skewK = skewAxis.ws / skewAxis.wt;
|
||||||
|
// A prism whose profile (s, EL) is given at the face t0 and which is extruded along t with the skew (λ·(k, 0, 1)).
|
||||||
|
const skewPrism = (profile: [number, number][], depth: number, t0: number) =>
|
||||||
|
prism(
|
||||||
|
profile.map(([s, y]) => [s + skewK * t0, y] as [number, number]),
|
||||||
|
depth,
|
||||||
|
new THREE.Vector3(0, 0, t0),
|
||||||
|
ex,
|
||||||
|
ey,
|
||||||
|
new THREE.Vector3(skewK, 0, 1),
|
||||||
|
);
|
||||||
const footingBottom = members.footingBottomEl;
|
const footingBottom = members.footingBottomEl;
|
||||||
|
|
||||||
// 벽체 (spec §4.3, 삽도): the full 2400 block from the footing top up to the 교좌면 step (t −0.8 … 1.6).
|
// 벽체 (spec §4.3, 삽도): the full 2400 block from the footing top up to the 교좌면 step (t −0.8 … 1.6).
|
||||||
@@ -175,7 +188,7 @@ export function buildAbutGroup(
|
|||||||
[seg.sTo, seg.seatEl],
|
[seg.sTo, seg.seatEl],
|
||||||
[seg.sFrom, seg.seatEl],
|
[seg.sFrom, seg.seatEl],
|
||||||
];
|
];
|
||||||
add('wall', prism(block, members.shape.wallFrontM - members.shape.wallBackM, new THREE.Vector3(0, 0, members.shape.wallBackM), ex, ey, ez), 0xb7c7d3);
|
add('wall', skewPrism(block, members.shape.wallFrontM - members.shape.wallBackM, members.shape.wallBackM), 0xb7c7d3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 흉벽 (삽도): sits on the 벽체 top, above the 교좌면 step. The back strip (t −0.8 … −0.5) rises to the 브라켓 상면 (흉벽 상단 − 0.85),
|
// 흉벽 (삽도): sits on the 벽체 top, above the 교좌면 step. The back strip (t −0.8 … −0.5) rises to the 브라켓 상면 (흉벽 상단 − 0.85),
|
||||||
@@ -187,21 +200,19 @@ export function buildAbutGroup(
|
|||||||
[seg.sTo, bracketAt(seg.sTo)],
|
[seg.sTo, bracketAt(seg.sTo)],
|
||||||
[seg.sFrom, bracketAt(seg.sFrom)],
|
[seg.sFrom, bracketAt(seg.sFrom)],
|
||||||
];
|
];
|
||||||
add('parapet', prism(backStrip, members.shape.parapetBackM - members.shape.wallBackM, new THREE.Vector3(0, 0, members.shape.wallBackM), ex, ey, ez), 0x8898aa);
|
add('parapet', skewPrism(backStrip, members.shape.parapetBackM - members.shape.wallBackM, members.shape.wallBackM), 0x8898aa);
|
||||||
const parapet: [number, number][] = [
|
const parapet: [number, number][] = [
|
||||||
[seg.sFrom, seg.seatEl],
|
[seg.sFrom, seg.seatEl],
|
||||||
[seg.sTo, seg.seatEl],
|
[seg.sTo, seg.seatEl],
|
||||||
[seg.sTo, parapetTopAt(seg.sTo)],
|
[seg.sTo, parapetTopAt(seg.sTo)],
|
||||||
[seg.sFrom, parapetTopAt(seg.sFrom)],
|
[seg.sFrom, parapetTopAt(seg.sFrom)],
|
||||||
];
|
];
|
||||||
add('parapet', prism(parapet, -members.shape.parapetBackM, new THREE.Vector3(0, 0, members.shape.parapetBackM), ex, ey, ez), 0x8898aa);
|
add('parapet', skewPrism(parapet, -members.shape.parapetBackM, members.shape.parapetBackM), 0x8898aa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (footingBottom !== undefined) {
|
if (footingBottom !== undefined) {
|
||||||
// 저판: s 0 … L, t −1.8 … +2.8, from the 기초 하단 to the 저판 상면.
|
// 저판: s 0 … L, t −1.8 … +2.8, from the 기초 하단 to the 저판 상면.
|
||||||
// Skew (2026-11-03): the 저판 ends follow the wing outer lines (s = sEnd + (ws/wt)·t), so the plan is a parallelogram.
|
// Skew (2026-11-03): the 저판 ends follow the wing outer lines (s = sEnd + (ws/wt)·t), so the plan is a parallelogram.
|
||||||
const skewAxis = wingDirectionAxis(model);
|
|
||||||
const skewK = skewAxis.ws / skewAxis.wt;
|
|
||||||
const footProfile: [number, number][] = [
|
const footProfile: [number, number][] = [
|
||||||
[skewK * members.shape.footingBackM, members.shape.footingBackM],
|
[skewK * members.shape.footingBackM, members.shape.footingBackM],
|
||||||
[L + skewK * members.shape.footingBackM, members.shape.footingBackM],
|
[L + skewK * members.shape.footingBackM, members.shape.footingBackM],
|
||||||
|
|||||||
@@ -61,9 +61,13 @@ describe('교대 3D: 받침블럭과 접속슬래브 (spec §4.2, §4.8)', () =>
|
|||||||
const box = new THREE.Box3().setFromObject(a).intersect(new THREE.Box3().setFromObject(b));
|
const box = new THREE.Box3().setFromObject(a).intersect(new THREE.Box3().setFromObject(b));
|
||||||
return Math.max(0, box.max.x - box.min.x) * Math.max(0, box.max.y - box.min.y) * Math.max(0, box.max.z - box.min.z);
|
return Math.max(0, box.max.x - box.min.x) * Math.max(0, box.max.y - box.min.y) * Math.max(0, box.max.z - box.min.z);
|
||||||
};
|
};
|
||||||
for (const w of (named('wall') as THREE.Group).children) {
|
// Same 교좌면 step only: the skewed prisms of neighbouring steps share a face, and their bounding boxes overlap slightly.
|
||||||
for (const p of (named('parapet') as THREE.Group).children) expect(volume(w, p)).toBeLessThan(1e-9);
|
const walls = (named('wall') as THREE.Group).children;
|
||||||
}
|
const parapets = (named('parapet') as THREE.Group).children;
|
||||||
|
walls.forEach((w, k) => {
|
||||||
|
expect(volume(w, parapets[2 * k])).toBeLessThan(1e-9);
|
||||||
|
expect(volume(w, parapets[2 * k + 1])).toBeLessThan(1e-9);
|
||||||
|
});
|
||||||
// back face: the ledge (t −0.8 … −0.5) and the 흉벽 back (−0.5) meet at one horizontal edge, not a vertical offset
|
// back face: the ledge (t −0.8 … −0.5) and the 흉벽 back (−0.5) meet at one horizontal edge, not a vertical offset
|
||||||
const backs = (named('wall') as THREE.Group).children.map((w) => new THREE.Box3().setFromObject(w).min.z);
|
const backs = (named('wall') as THREE.Group).children.map((w) => new THREE.Box3().setFromObject(w).min.z);
|
||||||
expect(Math.min(...backs)).toBeCloseTo(members.shape.wallBackM, 6);
|
expect(Math.min(...backs)).toBeCloseTo(members.shape.wallBackM, 6);
|
||||||
|
|||||||
Reference in new issue
Block a user