feat(view): 지형 모델이 없을 때 교각별 지반 패드를 정사각형으로 표시
- 한 변 = 코핑 길이 + 2 m, 교각 축에 맞춰 코핑 중심에 둠, 지반고 높이 - 패드는 교각별 추정이며 서로 잇지 않음 (지형 면 아님) - 테스트: 상면 네 모서리로 한 변과 대각선 길이 확인 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2caed5b87f
commit
844177b40e
2 files changed
+26
-15
No files matched your search
+8
-6
@@ -20,8 +20,8 @@ export const PART_LABELS: Record<PartName, string> = {
|
|||||||
ground: '지반',
|
ground: '지반',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Ground-level line drawn without a terrain grid (m): length = coping + margin, thin and narrow. */
|
/** Ground pad drawn without a terrain grid (m): a square of side = coping length + margin, thin. */
|
||||||
const GROUND_LINE = { margin: 2, thickness: 0.05, width: 0.15 } as const;
|
const GROUND_PAD = { margin: 2, thickness: 0.05 } as const;
|
||||||
|
|
||||||
const COLORS: Record<PartName, number> = {
|
const COLORS: Record<PartName, number> = {
|
||||||
coping: 0xb8c2cc,
|
coping: 0xb8c2cc,
|
||||||
@@ -177,15 +177,17 @@ export function buildPierScene(m: PierModel, terrain?: TerrainGrid): PierScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ground: the terrain grid around the piers when the ground model has one (수 m 떨어진 지형까지 보임).
|
// Ground: the terrain grid around the piers when the ground model has one (수 m 떨어진 지형까지 보임).
|
||||||
// Without a terrain, only a thin line at this pier's 지반고 across the coping (no surface is assumed, 승인 2026-10-27).
|
// Without a terrain, a thin square pad at this pier's 지반고, side = coping length + 2 m (승인 2026-10-27).
|
||||||
|
// It is a local estimate, not a terrain surface: the pads are not joined to each other.
|
||||||
if (terrain) {
|
if (terrain) {
|
||||||
const material = new THREE.MeshStandardMaterial({ color: COLORS.ground, roughness: 0.9, metalness: 0, transparent: true, opacity: 0.35, side: THREE.DoubleSide });
|
const material = new THREE.MeshStandardMaterial({ color: COLORS.ground, roughness: 0.9, metalness: 0, transparent: true, opacity: 0.35, side: THREE.DoubleSide });
|
||||||
material.userData.baseOpacity = 0.35;
|
material.userData.baseOpacity = 0.35;
|
||||||
parts.ground.add(new THREE.Mesh(terrainGeometry(terrain, m.pivot), material));
|
parts.ground.add(new THREE.Mesh(terrainGeometry(terrain, m.pivot), material));
|
||||||
} else {
|
} else {
|
||||||
const line = new THREE.BoxGeometry(m.copingL + GROUND_LINE.margin, GROUND_LINE.thickness, GROUND_LINE.width);
|
const side = m.copingL + GROUND_PAD.margin;
|
||||||
line.translate(m.copingL / 2, m.groundEl, 0);
|
const pad = new THREE.BoxGeometry(side, GROUND_PAD.thickness, side);
|
||||||
add('ground', line, 1);
|
pad.translate(m.copingL / 2, m.groundEl, 0);
|
||||||
|
add('ground', pad, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = new THREE.Group();
|
const root = new THREE.Group();
|
||||||
|
|||||||
@@ -10,18 +10,27 @@ const model = buildPier(loadP1());
|
|||||||
const terrain: TerrainGrid = { origin: [259815.676, 179088.094], step: 5, nx: 2, ny: 2, z: [30, 31, 32, 33] };
|
const terrain: TerrainGrid = { origin: [259815.676, 179088.094], step: 5, nx: 2, ny: 2, z: [30, 31, 32, 33] };
|
||||||
|
|
||||||
describe('지반 표시 (지형 모델이 없을 때)', () => {
|
describe('지반 표시 (지형 모델이 없을 때)', () => {
|
||||||
it('draws a thin ground-level line at the pier 지반고, not a 60 m square', () => {
|
it('draws a thin square pad at the pier 지반고, side = coping length + 2 m', () => {
|
||||||
const scene = buildPierScene(model);
|
const scene = buildPierScene(model);
|
||||||
|
const mesh = scene.parts.ground.children[0] as THREE.Mesh;
|
||||||
|
mesh.updateMatrixWorld(true);
|
||||||
|
const pos = mesh.geometry.getAttribute('position');
|
||||||
|
const pts: THREE.Vector3[] = [];
|
||||||
|
for (let i = 0; i < pos.count; i++) pts.push(new THREE.Vector3().fromBufferAttribute(pos, i).applyMatrix4(mesh.matrixWorld));
|
||||||
|
const top = Math.max(...pts.map((p) => p.y));
|
||||||
|
const corners = pts.filter((p) => Math.abs(p.y - top) < 1e-6);
|
||||||
|
// Four corners of the top face, with a distinct set of positions.
|
||||||
|
const unique = corners.filter((p, i) => corners.findIndex((q) => q.distanceTo(p) < 1e-6) === i);
|
||||||
|
expect(unique).toHaveLength(4);
|
||||||
|
const side = model.copingL + 2;
|
||||||
|
const edges = unique.flatMap((a, i) => unique.slice(i + 1).map((b) => a.distanceTo(b))).sort((a, b) => a - b);
|
||||||
|
// Four sides of length `side`, two diagonals of length side·√2.
|
||||||
|
for (const e of edges.slice(0, 4)) expect(e).toBeCloseTo(side, 3);
|
||||||
|
for (const e of edges.slice(4)) expect(e).toBeCloseTo(side * Math.SQRT2, 3);
|
||||||
|
// Thin: a pad, not a solid block. Centred on the ground EL.
|
||||||
const box = new THREE.Box3().setFromObject(scene.parts.ground);
|
const box = new THREE.Box3().setFromObject(scene.parts.ground);
|
||||||
const size = box.getSize(new THREE.Vector3());
|
expect(box.getSize(new THREE.Vector3()).y).toBeLessThan(0.1);
|
||||||
// Thin: a line, not a plane. Centred on the ground EL.
|
|
||||||
expect(size.y).toBeLessThan(0.1);
|
|
||||||
expect(box.getCenter(new THREE.Vector3()).y).toBeCloseTo(model.groundEl, 2);
|
expect(box.getCenter(new THREE.Vector3()).y).toBeCloseTo(model.groundEl, 2);
|
||||||
// Spans the coping along the pier, plus a small margin. The line may be rotated to the 교축, so measure its
|
|
||||||
// horizontal extent as a diagonal of the box's x and z extents.
|
|
||||||
const plan = Math.hypot(size.x, size.z);
|
|
||||||
expect(plan).toBeGreaterThan(model.copingL);
|
|
||||||
expect(plan).toBeLessThan(model.copingL + 3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps the ground group out of the pier size', () => {
|
it('keeps the ground group out of the pier size', () => {
|
||||||
|
|||||||
Reference in new issue
Block a user