diff --git a/B05_Profile/B05_Profile_UI_Corridor.ts b/B05_Profile/B05_Profile_UI_Corridor.ts index e895fa76..174cfb70 100644 --- a/B05_Profile/B05_Profile_UI_Corridor.ts +++ b/B05_Profile/B05_Profile_UI_Corridor.ts @@ -36,8 +36,9 @@ export interface CorridorStructureHook { * * 3 = 리본에 셀 마스크·축 고정 커브 조각 삼각형 추가 — 2026-08-26. * 4 = 절취 측벽 서피스(`cutWalls`) 추가 — 2026-08-27. + * 5 = 측벽에서 빗금(UV)을 뺀다 — 벽은 방향성을 표현하지 않는다(2026-08-27 사용자). */ -const ENVELOPE_VERSION = 4; +const ENVELOPE_VERSION = 5; interface CorridorEnvelope { version: number; @@ -68,7 +69,6 @@ interface CorridorEnvelope { cutWalls?: Array<{ side: "left" | "right"; positionsBase64: string; - uvsBase64: string; }>; } @@ -200,7 +200,6 @@ function serialize(build: CorridorBuildResult, hash: string): CorridorEnvelope { cutWalls: (build.cutWalls ?? []).map((wall) => ({ side: wall.side, positionsBase64: base64FromFloats(wall.positions), - uvsBase64: base64FromBytes(new Uint8Array(wall.uvs.buffer.slice(0))), })), }; } @@ -228,7 +227,6 @@ function deserialize(envelope: CorridorEnvelope): CorridorBuildResult { cutWalls: (envelope.cutWalls ?? []).map((wall) => ({ side: wall.side, positions: floatsFromBase64(wall.positionsBase64), - uvs: new Float32Array(bytesFromBase64(wall.uvsBase64).buffer), })), }; } diff --git a/B05_Profile/B05_Profile_UI_Corridor_Build.ts b/B05_Profile/B05_Profile_UI_Corridor_Build.ts index 1dc22dbe..6f677665 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Build.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Build.ts @@ -92,12 +92,11 @@ export interface CorridorRibbon { /** * 잘린 자리 **측벽 서피스**(2026-08-27 사용자) — 리본 메시에 섞지 않고 구조물처럼 - * 따로 그린다. 삼각형 수프(정점당 3, 삼각형당 9)와 짝 UV(정점당 2). + * 따로 그린다. 삼각형 수프(정점당 3, 삼각형당 9). 빗금이 없어 UV는 담지 않는다. */ export interface CorridorCutWall { side: "left" | "right"; positions: Float64Array; - uvs: Float32Array; } /** 시·종점 마구리(캡) — 설계선과 지반선 사이를 세로로 봉인하는 스트립(모델 좌표). */ diff --git a/B05_Profile/B05_Profile_UI_Corridor_Cut.ts b/B05_Profile/B05_Profile_UI_Corridor_Cut.ts index 5ee2520c..863d7ec1 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Cut.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Cut.ts @@ -293,37 +293,26 @@ function pushSkirt( loop: ReadonlyArray<[number, number]>, ribbon: CorridorRibbon, tris: number[], - uvs: number[], ): void { const ground = ribbon.groundZ; if (!ground) return; const cols = ribbon.colCount; - const startChainage = ribbon.chainages[0]; - /** 격자점 → [x, y, 서피스 z, 지반 z, u]. */ - const at = (row: number, col: number): [number, number, number, number, number] => { + /** 격자점 → [x, y, 서피스 z, 지반 z − 여유]. */ + const at = (row: number, col: number): [number, number, number, number] => { const i = (row * cols + col) * 3; return [ ribbon.positions[i], ribbon.positions[i + 1], ribbon.positions[i + 2], ground[row * cols + col] - SKIRT_MARGIN_M, - (ribbon.chainages[row] - startChainage) / 2, ]; }; - const push = (x: number, y: number, z: number, u: number): void => { - tris.push(x, y, z); - uvs.push(u, z / 2); // 빗금은 2m마다 한 타일 — 세로도 실척으로 이어진다. - }; for (let i = 0; i < loop.length - 1; i += 1) { - const [x1, y1, top1, bottom1, u1] = at(loop[i][0], loop[i][1]); - const [x2, y2, top2, bottom2, u2] = at(loop[i + 1][0], loop[i + 1][1]); + const [x1, y1, top1, bottom1] = at(loop[i][0], loop[i][1]); + const [x2, y2, top2, bottom2] = at(loop[i + 1][0], loop[i + 1][1]); if (top1 - bottom1 < 1e-6 && top2 - bottom2 < 1e-6) continue; // 높이 0이면 벽이 없다. - push(x1, y1, top1, u1); - push(x2, y2, top2, u2); - push(x2, y2, bottom2, u2); - push(x1, y1, top1, u1); - push(x2, y2, bottom2, u2); - push(x1, y1, bottom1, u1); + tris.push(x1, y1, top1, x2, y2, top2, x2, y2, bottom2); + tris.push(x1, y1, top1, x2, y2, bottom2, x1, y1, bottom1); } } @@ -368,7 +357,6 @@ export function maskFillByPlanCurves( const uvs: number[] = []; // 측벽은 리본 메시가 아니라 **별도 서피스**로 나간다(2026-08-27 사용자). const wallTris: number[] = []; - const wallUvs: number[] = []; const startChainage = ribbon.chainages[0]; const point = (row: number, col: number): [number, number, number] => { const i = (row * cols + col) * 3; @@ -427,7 +415,7 @@ export function maskFillByPlanCurves( traceMaskLoops(rows, cols, mask).forEach((raw) => { const loop = dropCollinear(raw); if (loop.length < 4 || !planes.length) return; - pushSkirt(loop, ribbon, wallTris, wallUvs); + pushSkirt(loop, ribbon, wallTris); const meanRow = loop.reduce((sum, [row]) => sum + row, 0) / loop.length; const chainage = ribbon.chainages[Math.min(rows - 1, Math.round(meanRow))]; const plane = planes.reduce((best, item) => @@ -448,13 +436,7 @@ export function maskFillByPlanCurves( ], }); }); - if (wallTris.length) { - walls.push({ - side, - positions: new Float64Array(wallTris), - uvs: new Float32Array(wallUvs), - }); - } + if (wallTris.length) walls.push({ side, positions: new Float64Array(wallTris) }); return { ...ribbon, cellMask: mask, diff --git a/B05_Profile/B05_Profile_UI_Corridor_Mesh.ts b/B05_Profile/B05_Profile_UI_Corridor_Mesh.ts index f39cffb6..cabd6c54 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Mesh.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Mesh.ts @@ -429,13 +429,12 @@ export function createCorridorGroup(build: CorridorBuildResult, bounds: ModelBou } const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); - geometry.setAttribute("uv", new THREE.BufferAttribute(wall.uvs.slice(0), 2)); geometry.computeVertexNormals(); const mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial({ + // 빗금 없음 — 벽은 방향성을 표현할 면이 아니다(2026-08-27 사용자). color: KIND_COLORS.fill, - map: hatchTexture(HATCH_DIRECTION.fill ?? -1), side: THREE.DoubleSide, polygonOffset: true, polygonOffsetFactor: POLYGON_OFFSET_FACTOR,