Implement patterned glass and maximum-depth distance controls
Add gobang surface refraction and shared photo/depth distance adjustment. Compare V2 Small and Base at input 770 with maximum-depth masks and pixel diagnostics. Record browser QA and update runtime dependencies and model notices.
This commit is contained in:
@@ -6,14 +6,14 @@ const paletteShader = await readFile(new URL('../src/shaders/region-palette.frag
|
||||
const historyShader = await readFile(new URL('../src/shaders/region-history.frag', import.meta.url), 'utf8');
|
||||
const main = await readFile(new URL('../src/main.ts', import.meta.url), 'utf8');
|
||||
const html = await readFile(new URL('../index.html', import.meta.url), 'utf8');
|
||||
assert.match(main, /uGridSize: \{ value: 13 \}/);
|
||||
assert.match(main, /uGridSize: \{ value: GOBANG_GLASS.grid \}/);
|
||||
assert.match(main, /uStrength: \{ value: 1 \}/);
|
||||
assert.match(main, /uRegionStrength: \{ value: 1 \}/);
|
||||
assert.match(main, /uRegionStrength: \{ value: 0 \}/);
|
||||
assert.match(main, /uCurvature: \{ value: 1 \}/);
|
||||
assert.match(main, /uRegionPaletteSize: \{ value: new THREE\.Vector2\(65, 13\) \}/);
|
||||
assert.match(main, /new THREE\.WebGLRenderTarget\(65, 13/);
|
||||
assert.match(main, /paletteWidth = gridX \* 5/);
|
||||
assert.match(html, /id="grid-size" type="range" min="10" max="24" step="1" value="13"/);
|
||||
assert.match(html, /id="grid-size" type="range" min="10" max="24" step="1" value="20"/);
|
||||
assert.match(html, /id="curvature" type="range" min="-1" max="1" step="0\.01" value="1"/);
|
||||
assert.match(html, /id="gloss-strength" type="range" min="0" max="0\.4" step="0\.01" value="0\.30"/);
|
||||
assert.match(html, /id="region-fixed-note"/);
|
||||
@@ -22,11 +22,11 @@ assert.doesNotMatch(main, /strengthInput|strengthOutput|regionStrengthInput|regi
|
||||
for (const [, id] of main.matchAll(/document\.querySelector\('#([^']+)'\)/g)) {
|
||||
assert.ok(html.includes(`id="${id}"`), `main DOM reference #${id} must exist in index.html`);
|
||||
}
|
||||
assert.match(main, /uniforms\.uGridSize\.value = style === 0 \? 13 : 24; uniforms\.uStrength\.value = 1/);
|
||||
assert.match(main, /uniforms\.uGridSize\.value = style === 0 \? 13 : style === 2 \? GOBANG_GLASS.grid : 24; uniforms\.uRegionStrength\.value = style === 0 \? 1 : 0; uniforms\.uStrength\.value = 1/);
|
||||
assert.match(main, /setMode\('final'\);\s*status\.textContent = automatic \?/);
|
||||
assert.match(html, /<details>\s*<summary>사진 구도<\/summary>/);
|
||||
assert.match(html, /<details>\s*<summary>개발 비교<\/summary>/);
|
||||
assert.match(html, /<summary>개발 비교<\/summary>[\s\S]*id="video"[\s\S]*id="depth-image"[\s\S]*id="view-mode"[\s\S]*id="effect"[\s\S]*id="mosaic-style"[\s\S]*id="depth-scale"[\s\S]*id="light-x"[\s\S]*id="sensitivity"/);
|
||||
assert.match(html, /<summary>개발 비교<\/summary>[\s\S]*id="video"[\s\S]*id="depth-image"[\s\S]*id="view-mode"[\s\S]*id="effect"[\s\S]*id="depth-scale"[\s\S]*id="light-x"[\s\S]*id="sensitivity"/);
|
||||
assert.match(shader, /cell\.x\*5\.\+index/);
|
||||
assert.match(shader, /for\(int y=-1;y<=1;y\+\+\)for\(int x=-1;x<=1;x\+\+\)/);
|
||||
assert.match(shader, /candidateColor=mix\(radialColor,splitColor,splitHistory\.a\)/);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
const shader = await readFile(new URL('../src/shaders/mosaic.frag', import.meta.url), 'utf8');
|
||||
const baseline = await readFile(new URL('../docs/qa-surface-reflection/baseline-mosaic.frag', import.meta.url), 'utf8');
|
||||
const shader = (await readFile(new URL('../src/shaders/mosaic.frag', import.meta.url), 'utf8')).replace(/\r\n/g, '\n');
|
||||
const baseline = (await readFile(new URL('../docs/qa-surface-reflection/baseline-mosaic.frag', import.meta.url), 'utf8')).replace(/\r\n/g, '\n');
|
||||
|
||||
function statement(source, type, name) {
|
||||
const match = source.match(new RegExp(`\\b${type} ${name}=([^;]+);`));
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import * as THREE from 'three';
|
||||
import { WindowReveal } from '../src/objects/WindowReveal.ts';
|
||||
import { PHOTO_GLASS_DISTANCE } from '../src/constants/glass.ts';
|
||||
|
||||
const glassZ = 0.6;
|
||||
const cameraZ = 3;
|
||||
const depthScales = [0, 0.25, 0.4];
|
||||
const glassGaps = [PHOTO_GLASS_DISTANCE.minGap, PHOTO_GLASS_DISTANCE.maxGap];
|
||||
const aspects = [0.7, 1.6];
|
||||
const offsets = [
|
||||
[0, 0],
|
||||
@@ -58,29 +60,31 @@ for (const aspect of aspects) {
|
||||
const frontW = baseW * (3 - glassZ) / 3;
|
||||
const frontH = baseH * (3 - glassZ) / 3;
|
||||
for (const depthScale of depthScales) {
|
||||
const backZ = glassZ - 0.04 - depthScale;
|
||||
const backW = baseW * (3 - backZ) / 3;
|
||||
const backH = baseH * (3 - backZ) / 3;
|
||||
const photo = makePhoto(backW, backH, backZ);
|
||||
const priorFrame = makePriorFrame(frontW, frontH);
|
||||
const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide });
|
||||
const reveal = new WindowReveal(material);
|
||||
reveal.update(frontW, frontH, backW, backH, glassZ, backZ);
|
||||
reveal.meshes.forEach((mesh) => mesh.updateMatrixWorld(true));
|
||||
for (const [offsetX, offsetY] of offsets) {
|
||||
const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
|
||||
camera.position.set(offsetX, offsetY, cameraZ);
|
||||
camera.lookAt(0, 0, 0);
|
||||
for (const xRatio of samples) {
|
||||
for (const yRatio of samples) {
|
||||
const x = xRatio * frontW / 2;
|
||||
const y = yRatio * frontH / 2;
|
||||
if (!coveredBy([photo, ...priorFrame.meshes], camera, x, y, glassZ)) baselineMisses += 1;
|
||||
assert.equal(coveredBy([photo, ...reveal.meshes], camera, x, y, glassZ), true, `uncovered aperture ray aspect=${aspect} depth=${depthScale} offset=${offsetX},${offsetY} sample=${xRatio},${yRatio}`);
|
||||
for (const glassGap of glassGaps) {
|
||||
const backZ = glassZ - glassGap - depthScale;
|
||||
const backW = baseW * (3 - backZ) / 3;
|
||||
const backH = baseH * (3 - backZ) / 3;
|
||||
const photo = makePhoto(backW, backH, backZ);
|
||||
const priorFrame = makePriorFrame(frontW, frontH);
|
||||
const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide });
|
||||
const reveal = new WindowReveal(material);
|
||||
reveal.update(frontW, frontH, backW, backH, glassZ, backZ);
|
||||
reveal.meshes.forEach((mesh) => mesh.updateMatrixWorld(true));
|
||||
for (const [offsetX, offsetY] of offsets) {
|
||||
const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
|
||||
camera.position.set(offsetX, offsetY, cameraZ);
|
||||
camera.lookAt(0, 0, 0);
|
||||
for (const xRatio of samples) {
|
||||
for (const yRatio of samples) {
|
||||
const x = xRatio * frontW / 2;
|
||||
const y = yRatio * frontH / 2;
|
||||
if (!coveredBy([photo, ...priorFrame.meshes], camera, x, y, glassZ)) baselineMisses += 1;
|
||||
assert.equal(coveredBy([photo, ...reveal.meshes], camera, x, y, glassZ), true, `uncovered aperture ray aspect=${aspect} depth=${depthScale} gap=${glassGap} offset=${offsetX},${offsetY} sample=${xRatio},${yRatio}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
photo.geometry.dispose(); photo.material.dispose(); priorFrame.geometry.dispose(); priorFrame.material.dispose(); reveal.dispose(); material.dispose();
|
||||
}
|
||||
photo.geometry.dispose(); photo.material.dispose(); priorFrame.geometry.dispose(); priorFrame.material.dispose(); reveal.dispose(); material.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +107,4 @@ reusableReveal.dispose();
|
||||
assert.deepEqual(disposeCounts.map((getCount) => getCount()), [1, 1, 1, 1], 'disposal emits exactly once per reveal geometry');
|
||||
reuseMaterial.dispose();
|
||||
|
||||
console.log(`Window reveal coverage passed: ${baselineMisses} previous-frame misses sealed across ${aspects.length} aspects, ${depthScales.length} depth scales, ${offsets.length} view offsets (including neutral) and ${samples.length ** 2} aperture samples; geometry reuse/disposal verified.`);
|
||||
console.log(`Window reveal coverage passed: ${baselineMisses} previous-frame misses sealed across ${aspects.length} aspects, ${depthScales.length} depth scales, ${glassGaps.length} glass gaps, ${offsets.length} view offsets (including neutral) and ${samples.length ** 2} aperture samples; geometry reuse/disposal verified.`);
|
||||
|
||||
Reference in New Issue
Block a user