// Numerical v13 stock-transmission formula applied to the unchanged real B geometry/thickness. // CPU-only diagnostic: this does not execute the corrected shader or measure GPU pixels. import {readFile} from 'node:fs/promises'; import {pathToFileURL,fileURLToPath} from 'node:url'; import ts from 'typescript'; import * as THREE from 'three'; const root=fileURLToPath(new URL('../',import.meta.url)); async function compile(file){ let s=await readFile(file,'utf8'); s=s.replace(/from ['"]three['"]/g,`from '${pathToFileURL(root+'node_modules/three/build/three.module.js')}'`); s=s.replace(/from ['"]three\/addons\/([^'"]+)['"]/g,(_,p)=>`from '${pathToFileURL(root+'node_modules/three/examples/jsm/'+p)}'`); for(const m of [...s.matchAll(/from ['"](\.[^'"]+)['"]/g)]) s=s.replace(m[0],`from '${await compile(new URL(m[1]+'.ts',file))}'`); return 'data:text/javascript;base64,'+Buffer.from(ts.transpileModule(s,{compilerOptions:{module:ts.ModuleKind.ES2020,target:ts.ScriptTarget.ES2020}}).outputText).toString('base64'); } const {PhysicalGlassComparison}=await import(await compile(pathToFileURL(root+'src/rendering/PhysicalGlassComparison.ts'))); const {sampleGobangSurface}=await import(await compile(pathToFileURL(root+'src/rendering/PatternedGlassGeometry.ts'))); const {comparisonLayout}=await import(await compile(pathToFileURL(root+'src/rendering/ComparisonLayout.ts'))); const photo=new THREE.Mesh(new THREE.PlaneGeometry(1,1,128,128),new THREE.ShaderMaterial({uniforms:{uTexture:{value:new THREE.Texture()},uDepthMap:{value:new THREE.Texture()},uDepthScale:{value:.25},uDepthBias:{value:0}}})); const adapter=new PhysicalGlassComparison({extensions:{has:()=>false}},photo); const w=Number(process.argv[2]||390),h=Number(process.argv[3]||844), aspect=w/h, photoAspect=.8; const baseH=Math.min(1.78*aspect/photoAspect,1.45),shape={width:baseH*photoAspect*.8,height:baseH*.8,grid:20,curvature:1}; const panes=comparisonLayout(w,h,aspect).panes, pane=panes[0]; const camera=new THREE.PerspectiveCamera(40,aspect,.1,100);camera.position.z=3;camera.updateMatrixWorld(); adapter.sync(shape,.6,new THREE.Vector3(.6,.8,2.6),new THREE.Color()); const geometry=adapter.glass.geometry,p=geometry.attributes.position,n=geometry.attributes.normal,uv=geometry.attributes.uv; const tex=adapter.material.thicknessMap,texW=tex.image.width,texH=tex.image.height,texData=tex.image.data; const thicknessFactor=adapter.material.thickness; const xValues=[];for(let i=0;;i++){if(i>0&&uv.getY(i)!==0)break;xValues.push(uv.getX(i));} const nx=xValues.length;const yValues=[];for(let i=0;itexData[(clamp(y,0,texH-1)*texW+clamp(x,0,texW-1))*4+1]/255;return thicknessFactor*((1-fy)*((1-fx)*at(x0,y0)+fx*at(x0+1,y0))+fy*((1-fx)*at(x0,y0+1)+fx*at(x0+1,y0+1)));} function idx(a,v){let lo=0,hi=a.length-1;while(hi-lo>1){const m=(lo+hi)>>1;if(a[m]<=v)lo=m;else hi=m;}return lo;} function interpAt(u,v){const ix=idx(xValues,u),iy=idx(yValues,v),fx=(u-xValues[ix])/(xValues[ix+1]-xValues[ix]),fy=(v-yValues[iy])/(yValues[iy+1]-yValues[iy]),a=iy*nx+ix,b=a+1,c=a+nx,d=c+1;const ids=fx>=fy?[a,b,d]:[a,d,c],ws=fx>=fy?[1-fx,fx-fy,fy]:[1-fy,fx,fy-fx];const pos=new THREE.Vector3(),norm=new THREE.Vector3();ids.forEach((id,i)=>{pos.addScaledVector(new THREE.Vector3().fromBufferAttribute(p,id),ws[i]);norm.addScaledVector(new THREE.Vector3().fromBufferAttribute(n,id),ws[i]);});pos.z+=.6;return{pos,norm:norm.normalize()};} function refract(i,n,eta){const d=n.dot(i),k=1-eta*eta*(1-d*d);return k<0?new THREE.Vector3():i.clone().multiplyScalar(eta).addScaledVector(n,-(eta*d+Math.sqrt(k)));} function screen(pt){const s=pt.clone().project(camera);return new THREE.Vector2(s.x*pane.width*.5,s.y*pane.height*.5);} function resultAt(u,v){const {pos,norm}=interpAt(u,v),i=pos.clone().sub(camera.position).normalize(),r=refract(i,norm,1/1.5),t=tAt(u,v),e=pos.clone().addScaledVector(r,t),b=screen(e).distanceTo(screen(pos)),s=sampleGobangSurface(u,v,shape),normal=new THREE.Vector3(-s.dx,-s.dy,1).normalize(),front=new THREE.Vector3((u-.5)*shape.width,(v-.5)*shape.height,.6+s.height),inc=front.clone().sub(camera.position).normalize(),inside=refract(inc,normal,1/1.5),backZ=.6-.008-.085*shape.width/20,back=front.clone().addScaledVector(inside,(backZ-front.z)/inside.z),out=refract(inside,new THREE.Vector3(0,0,1),1.5);const aAt=z=>screen(back.clone().addScaledVector(out,(z-backZ)/out.z)).distanceTo(screen(front)); const aBasePoint=back.clone().addScaledVector(out,(.6-.1296-.25-backZ)/out.z),aNearPoint=back.clone().addScaledVector(out,(.6-.1296-backZ)/out.z); const nearReprojected=camera.position.clone().addScaledVector(aNearPoint.clone().sub(camera.position),(.6-.1296-.25-camera.position.z)/(aNearPoint.z-camera.position.z)); const delta=nearReprojected.sub(aBasePoint);delta.z=0;delta.multiplyScalar(Math.min(1,(shape.width/20)*.35/Math.max(delta.length(),.00001))*.65); const aNearRefined=screen(aBasePoint.clone().add(delta)).distanceTo(screen(front)); return{u,v,normal:norm.toArray(),thickness:t,axial:pos.z-backZ,mapError:t-(pos.z-backZ),normalBakeCorrection:1/norm.z,pathCorrection:1/-r.z,b,aNear:aAt(.6-.1296),aBase:aAt(.6-.1296-.25),aNearRefined};} let results=[];for(let y=0;y<250;y++)for(let x=0;x<200;x++)results.push(resultAt((x+.5)/200,(y+.5)/250)); function stats(key){const vals=results.map(x=>x[key]).sort((a,b)=>a-b);return{min:vals[0],median:vals[(vals.length*.5)|0],p95:vals[(vals.length*.95)|0],max:vals.at(-1),mean:vals.reduce((a,b)=>a+b,0)/vals.length};} const samples=[[.511,.509],[.5125,.5125],[.525,.525],[.505,.505],[.499,.5]].map(([u,v])=>resultAt(u,v)); const pixelProjectionWidth=screen(new THREE.Vector3(shape.width/2,0,.6)).x*2; console.log(JSON.stringify({shape,pane,glassDisplayedCssWidth:pixelProjectionWidth,material:{thickness:thicknessFactor,thicknessMap:{width:texW,height:texH,flipY:tex.flipY,colorSpace:tex.colorSpace,wrapS:tex.wrapS,wrapT:tex.wrapT,greenMin:Math.min(...texData.filter((v,i)=>i%4===1)),greenMax:Math.max(...texData.filter((v,i)=>i%4===1))}},frontSegments:[nx-1,yValues.length-1],thickness:stats('thickness'),mapError:stats('mapError'),normalBakeCorrection:stats('normalBakeCorrection'),actualNearAInteriorCss:stats('aNearRefined'),pathCorrection:stats('pathCorrection'),stockOffsetCss:stats('b'),twoSurfaceNearCss:stats('aNear'),twoSurfaceBaseCss:stats('aBase'),samples},null,2));