Files
Aislo/resources/tester/helper_135_2_grayscale.cjs
T
eomsangdonandClaude Opus 5.5 895c6d4d3a fix(b05): 3D 흑백 지형 단추가 예상형상 지형에도 먹게 — 분할본 원래 색 · far 색 버퍼 따로 · 원래 색 = 색 버퍼 열쇠(135-2)
- _Split: 사본 색을 지형 원래 색(ORIGINAL_COLORS)으로 · far 색 버퍼 clone
- 뷰어 applyGrayscaleTo: 원래 색 기억을 메시 userData → 색 버퍼 열쇠(WeakMap) · ③ 사면 keepColor 그대로
- 새 시험 test_135_2_grayscale(+ 도우미)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
2026-10-09 14:59:29 +09:00

97 lines
3.9 KiB
JavaScript

/* 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));