import assert from 'node:assert/strict'; import {test} from 'node:test'; import * as THREE from 'three'; import {loadTypescript} from './support/load-typescript.mjs'; const {attachMeshRefraction}=await loadTypescript(new URL('../src/rendering/MeshRefraction.ts',import.meta.url)); const {WindowReveal}=await loadTypescript(new URL('../src/objects/WindowReveal.ts',import.meta.url)); function fixture(){ const rgb=new THREE.Texture(),depth=new THREE.DataTexture(new Uint8Array([128,128,128,255]),1,1); const photo=new THREE.Mesh(new THREE.PlaneGeometry(1,1,128,128),new THREE.ShaderMaterial({uniforms:{uTexture:{value:rgb},uDepthMap:{value:depth},uDepthScale:{value:.25},uDepthBias:{value:0},uIsVideo:{value:false}}})); photo.position.set(.1,-.2,.2204);photo.scale.set(.6,.8,1); const material=new THREE.MeshPhysicalMaterial({transmission:1,ior:1.5}); return{photo,material,controller:attachMeshRefraction(material,photo),rgb,depth}; } test('B extends only its material and traces actual mesh entry to photo triangles',()=>{ const f=fixture();f.controller.sync({width:.66,height:.82,grid:20,curvature:1},.5892,new THREE.Color(.2,.3,.4)); const shader={vertexShader:THREE.ShaderLib.physical.vertexShader,fragmentShader:THREE.ShaderLib.physical.fragmentShader,uniforms:{}}; const before=THREE.ShaderChunk.transmission_fragment; f.material.onBeforeCompile(shader,{}); assert.equal(shader.vertexShader,THREE.ShaderLib.physical.vertexShader,'real physical mesh vertex path is preserved'); assert.match(shader.fragmentShader,/bTracePhotoRay\(\s*vWorldPosition/); assert.match(shader.fragmentShader,/bExitGlass/);assert.match(shader.fragmentShader,/bPhotoHit/); assert.match(shader.fragmentShader,/textureLod\(\s*uBDepthMap/); assert.match(shader.fragmentShader,/textureLod\(\s*uBPhoto/); assert.doesNotMatch(shader.fragmentShader,/#include /); assert.equal(THREE.ShaderChunk.transmission_fragment,before,'never patches global Three or A'); assert.equal(shader.uniforms.uBPhoto.value,f.rgb);assert.equal(shader.uniforms.uBDepthMap.value,f.depth); assert.equal(shader.uniforms.uBBackZ.value,.5892); assert.equal(shader.uniforms.uBDepthScale.value,.25); assert.equal(shader.uniforms.uBPhotoSegments.value,128); const inverse=shader.uniforms.uBPhotoWorldToLocal.value.clone().multiply(f.photo.matrixWorld); inverse.elements.forEach((v,i)=>assert.ok(Math.abs(v-(i%5===0?1:0))<1e-10)); assert.match(f.material.customProgramCacheKey(),/mesh-exit-photo/); }); test('photo, depth and matrices refresh even when glass shape is unchanged',()=>{ const f=fixture(),shape={width:1,height:1,grid:20,curvature:1}; f.controller.sync(shape,.58,new THREE.Color()); const next=new THREE.Texture();f.photo.material.uniforms.uDepthMap.value=next; f.photo.material.uniforms.uDepthScale.value=.4;f.photo.material.uniforms.uDepthBias.value=.2;f.photo.material.uniforms.uIsVideo.value=true; f.photo.position.set(-.2,.4,.17);f.photo.scale.set(1.8,2.4,1); f.controller.sync(shape,.58,new THREE.Color(.5,.4,.3)); const u=f.controller.uniforms; assert.equal(u.uBDepthMap.value,next);assert.equal(u.uBDepthScale.value,.4);assert.equal(u.uBDepthBias.value,.2);assert.equal(u.uBIsVideo.value,true); u.uBPhotoWorldToLocal.value.clone().invert().elements.forEach((v,i)=>assert.ok(Math.abs(v-f.photo.matrixWorld.elements[i])<1e-10)); assert.equal(u.uBBackground.value.r,.5); }); test('refracted rays intersect the exact fixed reveal before choosing photo or room background',async()=>{ const f=fixture(),map=new THREE.Texture(),material=new THREE.MeshStandardMaterial({map,color:0xbca18b,side:THREE.DoubleSide}); const reveal=new WindowReveal(material);reveal.update(.928,1.16,1.45*.8*(3-.2204)/3,1.45*(3-.2204)/3,.6,.2204); const scene=new THREE.Scene();scene.add(...reveal.meshes,new THREE.AmbientLight(0xfff0d9,1.35)); f.controller.syncReveal(reveal.meshes,scene); const shader={uniforms:{},fragmentShader:THREE.ShaderLib.physical.fragmentShader};f.material.onBeforeCompile(shader,{}); assert.equal(shader.uniforms.uBRevealEnabled.value,true); assert.equal(shader.uniforms.uBRevealMapVertical.value,map); assert.equal(shader.uniforms.uBRevealVertices.value.length,16); for(let wall=0;wall<4;wall++)for(let i=0;i<4;i++){ const expected=new THREE.Vector3().fromBufferAttribute(reveal.meshes[wall].geometry.attributes.position,i).applyMatrix4(reveal.meshes[wall].matrixWorld); assert.ok(shader.uniforms.uBRevealVertices.value[wall*4+i].distanceTo(expected)<1e-10); } assert.match(shader.fragmentShader,/bRevealHit\(exitPoint,outgoing/); assert.match(shader.fragmentShader,/backgroundDistance=revealHit\?revealDistance:1e20/,'reveal initializes the background hit distance'); assert.match(shader.fragmentShader,/yardDistance