/* PLAN 135-2 — 흑백 지형 단추: 분할본 색 = 지형 원래 색 · far 색 버퍼 따로 · 원래 색 기억 = 색 버퍼 열쇠. * TS 를 트랜스파일해 돌리고 결과(JSON)를 낸다(helper_115_8 과 같은 방식). */ const fs = require("fs"); const path = require("path"); const ROOT = path.resolve(__dirname, "..", ".."); const NM = path.join(ROOT, "config", "node_modules"); const ts = require(path.join(NM, "typescript")); const THREE = require(path.join(NM, "three")); function loadTs(file) { const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; new Function("exports", "module", "require", js)(box.exports, box, (r) => r === "three" ? THREE : require(r), ); return box.exports; } const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`); const { TerrainBandSplit, ORIGINAL_COLORS } = loadTs(B05("B05_Profile_UI_Corridor_Split")); // 뷰어 applyGrayscaleTo 와 같은 규칙(색 버퍼 열쇠로 원본 한 번만 기억). function gray(object, on) { object.traverse((child) => { const color = child.geometry?.getAttribute("color"); if (!color) return; const target = color.array; let original = ORIGINAL_COLORS.get(color); if (!original) { original = target.slice(); ORIGINAL_COLORS.set(color, original); } if (on) { for (let i = 0; i + 2 < original.length; i += 3) { const g = 0.299 * original[i] + 0.587 * original[i + 1] + 0.114 * original[i + 2]; target[i] = target[i + 1] = target[i + 2] = g; } } else target.set(original); }); } // 지형 — 40 × 40 m 격자 · 색 = 빨강 → 초록(회색과 다름). const N = 21; const pos = []; const col = []; const idx = []; for (let j = 0; j < N; j += 1) for (let i = 0; i < N; i += 1) { pos.push(i * 2 - 20, 0, j * 2 - 20); col.push(1, i / (N - 1), 0); } for (let j = 0; j + 1 < N; j += 1) for (let i = 0; i + 1 < N; i += 1) { const a = j * N + i; idx.push(a, a + N, a + 1, a + 1, a + N, a + N + 1); } const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.Float32BufferAttribute(pos, 3)); geometry.setAttribute("color", new THREE.Float32BufferAttribute(col, 3)); geometry.setIndex(idx); const terrain = new THREE.Group(); terrain.add(new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ vertexColors: true }))); const original = Float32Array.from(col); gray(terrain, true); // 기본 흑백 — 지형 적재 직후 회색 먼저. const split = new TerrainBandSplit(terrain, { minX: -5, maxX: 5, minZ: -5, maxZ: 5 }); const near = split.near[0].geometry.getAttribute("color"); const far = split.far[0].geometry.getAttribute("color"); const same = (a, b) => a.length === b.length && a.every((v, k) => Math.abs(v - b[k]) < 1e-6); const r = {}; r.nearOriginal = same(near.array, original); r.farOriginal = same(far.array, original); r.farOwnBuffer = near !== far && near.array !== far.array; // far 를 감싼 메시 두 번(③ 다시 짬) — 흑백 → 끔이 원래 색으로. const wrap = () => { const root = new THREE.Group(); root.add(new THREE.Mesh(split.far[0].geometry, split.far[0].material)); return root; }; const first = wrap(); gray(first, true); r.nearUntouchedByFarGray = same(near.array, original); const second = wrap(); gray(second, true); // 새 메시 — 회색을 원본으로 잡으면 안 됨. gray(second, false); r.farBackToOriginal = same(far.array, original); gray(terrain, false); r.terrainBackToOriginal = same(geometry.getAttribute("color").array, original); // 뷰어가 같은 열쇠를 쓰는가. const viewer = fs.readFileSync(B05("B05_Profile_UI_Viewer"), "utf8"); r.viewerUsesKey = viewer.includes("ORIGINAL_COLORS.get(color)") && !viewer.includes("userData.originalColors"); process.stdout.write(JSON.stringify(r));