Replace project with current server release and Windows setup

Import the 2026-10-09 source release, Fastify/SQLite backend, restored source
assets, and analytics-enabled server build. Replace files from the previous
release while retaining the existing repository history.

Make builds portable across Windows and Linux, configure local startup,
normalize Windows static asset paths, and fix test fixture cleanup and file
URL handling. Exclude local environment settings, databases, dependencies,
validation output, and TypeScript build cache from version control.

Validation: npm ci, static and server builds, 106 arrival tests, and actual
local HTTP checks passed. The full suite passed 467 of 470 tests; three tests
still require QA fixtures omitted from the supplied release, documented in
README-서버실행.md.
This commit is contained in:
2026-10-09 16:43:35 +09:00
parent 71c8d3b7ff
commit e358e24436
642 changed files with 35202 additions and 17642 deletions
+49 -2
View File
@@ -1,4 +1,4 @@
Pet Behind Glass · AI depth third-party notices
Pet Behind Glass · third-party notices
Depth Anything V2 Small
Model: onnx-community/depth-anything-v2-small-ONNX
@@ -10,8 +10,55 @@ The ONNX model weights are downloaded from Hugging Face by the browser when the
AI depth feature is first used. They are not included in this application bundle.
Transformers.js
Package: @huggingface/transformers 3.8.1
Package: @huggingface/transformers 4.3.0
Project: https://github.com/huggingface/transformers.js
License: Apache License 2.0
The full Apache License 2.0 text is provided in licenses/Apache-2.0.txt.
배민 꾸불림체 / BM Kkubulim
Copyright: Woowa Brothers Corporation
Official font page: https://www.woowahan.com/fonts
Original font: https://woowahan-cdn.woowahan.com/static/fonts/BMKkubulim.otf?d35cc1bf
Official licensing notice: https://www.woowahan.com/fonts/license
License: SIL Open Font License 1.1
The complete font is self-hosted in fonts/BMKkubulim.woff. This is lossless WOFF
packaging of the official OTF, with all original font tables byte-preserved,
no subsetting, and no added WOFF metadata. The font is not sold separately.
The full copyright, reserved font names, intellectual-property notice and OFL
text are provided in licenses/BMKkubulim-OFL.txt.
Three.js 0.169.0
Project: https://github.com/mrdoob/three.js
License: MIT
Full copyright and license: licenses/Threejs-MIT.txt
ONNX Runtime Web / Common
Project: https://github.com/microsoft/onnxruntime
Source revision recorded by the installed web runtime: 8d85527a0
License: MIT, Copyright (c) Microsoft Corporation
Full license: licenses/ONNX-Runtime-MIT.txt
Upstream third-party notices: licenses/ONNX-Runtime-ThirdPartyNotices.txt
The upstream notice collection covers multiple build targets; inclusion here does
not mean every listed component is shipped in this browser build.
Hugging Face Jinja 0.5.10
Bundled by Transformers.js. License: MIT
Full copyright and license: licenses/HuggingFace-Jinja-MIT.txt
Hugging Face Tokenizers 0.2.0
Bundled by Transformers.js. License: Apache License 2.0
Full license: licenses/Apache-2.0.txt
Retained experimental U2Netp model / cutout resources
Model and source details, Apache-2.0 and rembg code MIT text:
models/u2netp/LICENSES.txt
The experiment is not exposed in the consumer settings. These notices are
retained because the resources remain in the application distribution.
Driver.js 1.9.0
Project: https://driverjs.com/
Copyright (c) Kamran Ahmed
License: MIT
Full copyright and permission text: licenses/Driverjs-MIT.txt
File diff suppressed because one or more lines are too long
+535
View File
@@ -0,0 +1,535 @@
import{a as e,c as t,d as n,f as r,h as i,i as a,l as o,m as s,n as c,o as l,p as u,r as ee,u as d}from"./index-BYcEjj7E.js";var f=`uniform sampler2D uSceneTexture,uSceneDepth;
uniform sampler2D uProtectionDepthMap;
uniform mat4 uContentViewProjection,uContentInverseViewProjection;
uniform mat4 uPetWorldToLocal;
uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor;
uniform float uGlassZ,uSourceZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength;
uniform vec2 uViewOffset,uPetSize,uPaneSize;
uniform vec2 uDepthEvidenceRange,uDepthCorrection;
uniform float uEnvironmentStrength;
uniform vec4 uGobangSurface;
uniform float uGobangEnvironmentStrength;
uniform float uGobangBevelRound;
uniform vec4 uPaneRect;
uniform sampler2D uRegionPalette;
uniform sampler2D uRegionAtlas,uRegionHistory;
uniform vec2 uRegionAtlasSize,uRegionGridSize,uRegionPaletteSize;
uniform float uRegionStrength,uRegionAvailable;
varying vec2 vUv;
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;}
vec4 sampleScene(vec2 uv){return texture2D(uSceneTexture,clamp(uv,0.,1.));}
vec4 samplePane(vec2 pane){return sampleScene(projectUv(vec3((pane-.5)*uPaneSize,uGlassZ)));}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
vec2 hash2(vec2 p){return vec2(hash(p),hash(p+vec2(19.19,73.73)));}
vec2 cellBlurStep(vec2 cell,vec2 cellSize,float style){
if(style<=.5)return cellSize*mix(.006,.018,hash(cell+vec2(5.)));
return cellSize*mix(.006,.012,hash(cell+vec2(5.)));
}
vec4 sampleCell(vec2 pane,vec2 sourceUv,float strength,vec2 cell,vec2 cellSize,float style){
vec2 center=mix(pane,sourceUv,strength);
vec2 blurOffset=strength*cellBlurStep(cell,cellSize,style);
vec4 color;
if(style<=.5){
color=samplePane(center)*.6;
color+=samplePane(center+vec2(blurOffset.x,0.))*.1;
color+=samplePane(center-vec2(blurOffset.x,0.))*.1;
color+=samplePane(center+vec2(0.,blurOffset.y))*.1;
color+=samplePane(center-vec2(0.,blurOffset.y))*.1;
}else{
color=samplePane(center)*.4;
color+=samplePane(center+vec2(blurOffset.x,0.))*.15;
color+=samplePane(center-vec2(blurOffset.x,0.))*.15;
color+=samplePane(center+vec2(0.,blurOffset.y))*.15;
color+=samplePane(center-vec2(0.,blurOffset.y))*.15;
}
return color;
}
float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;}
vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
float softLimit(float value,float limit){return value*limit/(limit+abs(value));}
float surfaceCurvature(){float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.);return curvature-(.25/.35)*excess*excess;}
float signedCurvatureScale(){return clamp(surfaceCurvature()/.65,-1.25,1.25);}
float regionDistance(vec3 a,vec3 b){vec3 d=a-b;float y=dot(d,vec3(.299,.587,.114));return dot(d,d)+.35*y*y;}
float regionLuminance(vec3 c){return dot(c,vec3(.299,.587,.114));}
vec3 regionColor(vec2 cell,float index){vec2 uv=(vec2(cell.x*5.+index,cell.y)+.5)/uRegionPaletteSize;return texture2D(uRegionPalette,uv).rgb;}
float nearestRegion(vec3 color,vec2 cell){float best=0.,distance=regionDistance(color,regionColor(cell,0.));for(int i=1;i<4;i++){float candidate=regionDistance(color,regionColor(cell,float(i)));if(candidate<distance){distance=candidate;best=float(i);}}return best;}
float nearestRegion(vec3 color,vec3 p0,vec3 p1,vec3 p2,vec3 p3){float best=0.,distance=regionDistance(color,p0);float candidate=regionDistance(color,p1);if(candidate<distance){distance=candidate;best=1.;}candidate=regionDistance(color,p2);if(candidate<distance){distance=candidate;best=2.;}candidate=regionDistance(color,p3);if(candidate<distance)best=3.;return best;}
vec3 selectRegion(vec3 p0,vec3 p1,vec3 p2,vec3 p3,float index){if(index<.5)return p0;if(index<1.5)return p1;if(index<2.5)return p2;return p3;}
vec3 regionAtlasColor(vec2 cell,vec2 texel){vec2 uv=(cell*8.+clamp(texel,vec2(0.),vec2(7.))+.5)/uRegionAtlasSize;return texture2D(uRegionAtlas,uv).rgb;}
bool reconstructScenePoint(vec2 sceneUv,out vec3 worldPoint){
if(sceneUv.x<0.||sceneUv.x>1.||sceneUv.y<0.||sceneUv.y>1.)return false;
float depth=texture2D(uSceneDepth,sceneUv).x;
if(depth>=.999999)return false;
vec4 clip=vec4(sceneUv*2.-1.,depth*2.-1.,1.);
vec4 world=uContentInverseViewProjection*clip;
if(abs(world.w)<.0001)return false;
worldPoint=world.xyz/world.w;
return true;
}
float regionSurfaceValid(vec2 sceneUv,vec3 glassHit){
vec3 worldPoint;
if(!reconstructScenePoint(sceneUv,worldPoint))return 0.;
float axialGap=glassHit.z-worldPoint.z;
if(axialGap<=.0001)return 0.;
return 1.;
}
float regionProtectionWeight(vec2 sceneUv){
vec3 worldPoint;
if(!reconstructScenePoint(sceneUv,worldPoint))return 0.;
vec3 localPoint=(uPetWorldToLocal*vec4(worldPoint,1.)).xyz;
if(localPoint.x<-.5||localPoint.x>.5||localPoint.y<-.5||localPoint.y>.5)return 0.;
vec2 sourceUv=localPoint.xy+.5;
float sourceDepth=texture2D(uProtectionDepthMap,sourceUv).r;
if(uDepthEvidenceRange.y-uDepthEvidenceRange.x<.0001)return 0.;
return smoothstep(uDepthEvidenceRange.x,uDepthEvidenceRange.y,sourceDepth);
}
// Screen-depth refinement of an artistic ray. Occluded surfaces are unavailable;
// retain the original sample if the visible-depth solve cannot converge.
vec3 refineSceneHit(vec3 glassHit,vec3 ray,vec3 artisticHit,float cellWorld){
vec3 planeHit=glassHit+ray*((uSourceZ-glassHit.z)/safeZ(ray.z));
vec3 origin=glassHit+vec3(artisticHit.xy-planeHit.xy,0.);
vec3 hit=artisticHit,surface;
for(int iteration=0;iteration<3;iteration++){
if(!reconstructScenePoint(projectUv(hit),surface))return artisticHit;
vec3 localPoint=(uPetWorldToLocal*vec4(surface,1.)).xyz;
if(any(greaterThan(abs(localPoint.xy),vec2(.5)))||surface.z>=uGlassZ-.0001)return artisticHit;
hit=origin+ray*((surface.z-origin.z)/safeZ(ray.z));
}
if(!reconstructScenePoint(projectUv(hit),surface))return artisticHit;
if(surface.z>=uGlassZ-.0001)return artisticHit;
float confidence=1.-smoothstep(cellWorld*.05,cellWorld*.25,abs(hit.z-surface.z));
// Compare at the same reference Z, so only the projected sampling position changes.
vec3 viewRay=hit-uContentCameraPosition;
vec3 projected=uContentCameraPosition+viewRay*((uSourceZ-uContentCameraPosition.z)/safeZ(viewRay.z));
vec2 delta=projected.xy-artisticHit.xy;
float limit=cellWorld*uDepthCorrection.y;
delta*=min(1.,limit/max(length(delta),.00001));
return artisticHit+vec3(delta*uDepthCorrection.x*confidence,0.);
}
#ifdef REGION_ATLAS
float regionAtlasEvidence(vec2 sceneUv,vec3 color,vec3 glassHit){
if(regionSurfaceValid(sceneUv,glassHit)<=0.)return 0.;
return regionProtectionWeight(sceneUv)*(1.-smoothstep(.10,.20,regionLuminance(color)));
}
vec4 regionAtlasProbe(vec3 sceneHit,vec2 worldOffset,vec3 glassHit){
vec2 probeSceneUv=projectUv(sceneHit+vec3(worldOffset,0.));
vec4 probe=sampleScene(probeSceneUv);
return vec4(probe.rgb,regionAtlasEvidence(probeSceneUv,probe.rgb,glassHit));
}
vec4 regionAtlasScore(vec3 centerColor,float centerEvidence,vec3 probeColor,float probeEvidence){
float evidenceGain=probeEvidence-centerEvidence,darkGain=regionLuminance(centerColor)-regionLuminance(probeColor),blend=0.;
if(probeEvidence>.05&&evidenceGain>.02&&darkGain>.015)blend=min(.4,evidenceGain*2.)*min(1.,max(0.,darkGain*4.));
return vec4(blend,probeEvidence,0.,0.);
}
vec3 regionAtlasRepresentative(vec3 centerColor,float centerEvidence,vec3 sceneHit,vec3 glassHit,out float evidence){
vec3 bestColor=centerColor;float bestEvidence=centerEvidence,bestBlend=0.;
vec2 probeStep=uPaneSize/(uRegionGridSize*8.)*.25;
vec4 probe,score;
probe=regionAtlasProbe(sceneHit,vec2(probeStep.x,0.),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;}
probe=regionAtlasProbe(sceneHit,vec2(-probeStep.x,0.),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;}
probe=regionAtlasProbe(sceneHit,vec2(0.,probeStep.y),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;}
probe=regionAtlasProbe(sceneHit,vec2(0.,-probeStep.y),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;}
evidence=bestEvidence;
return bestColor;
}
#endif
void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float opticalTravel,float style,out vec2 affineUv,out mat2 transform,out vec2 curvedSlope,out vec2 facetSlope){
vec2 random=hash2(cell),widths;
if(style>.5){
widths=mix(vec2(.82),vec2(1.12),hash2(cell+vec2(11.4,9.8)));
float angle=(random.x-.5)*.08,c=cos(angle),s=sin(angle);
transform=mat2(widths.x*c,-widths.y*s,widths.x*s,widths.y*c);
float centerJitter=.16;
vec2 sourceCenter=(cell+.5)/grid+(random-.5)*centerJitter*cellSize;
affineUv=sourceCenter+transform*(local*cellSize);
}else{
// Default style keeps a single continuous image footprint across cells.
transform=mat2(1.);
affineUv=(cell+.5)/grid+local*cellSize;
}
// Positive height bulges toward the observer (+Z); negative height is concave.
float curvature=clamp(uCurvature,-1.,1.),surfaceCurvatureValue=surfaceCurvature();
vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2;
if(style>.5){
float amplitude=mix(.92,1.08,hash(cell+vec2(41.7,17.3))),height=.009*cellWorld*surfaceCurvatureValue*amplitude;
vec2 skew=(hash2(cell+vec2(23.8,61.2))-.5)*.28;float asym=1.+dot(skew,q);
vec2 dhdq=height*vec2(-4.*q3.x*b.y*asym+b.x*b.y*skew.x,-4.*q3.y*b.x*asym+b.x*b.y*skew.y);
vec2 grad=-2.*dhdq/cellDimensions;
curvedSlope=grad+(random-.5)*.012;facetSlope=(random-.5)*.012;
}else{
curvedSlope=.30*cellDimensions/max(abs(opticalTravel),.6)*signedCurvatureScale()*sin(6.2831853*local);
facetSlope=vec2(0.);
}
}
#ifndef REGION_ATLAS
// Broad prism faces, with narrow rounded transitions at the ridge and center.
// The height and slope meet at cell boundaries; no outline is painted on top.
float gobangProfile(float coordinate,out float derivative){
float span=.5-uGobangSurface.y;
float t=clamp((abs(coordinate)-uGobangSurface.y)/span,0.,1.);
float rounding=uGobangBevelRound,ramp=t-rounding*.5,slope=1.;
if(t<rounding){ramp=t*t/(2.*rounding);slope=t/rounding;}
else if(t>1.-rounding){ramp=1.-rounding-(1.-t)*(1.-t)/(2.*rounding);slope=(1.-t)/rounding;}
derivative=-slope*sign(coordinate)/(span*(1.-rounding));
return 1.-ramp/(1.-rounding);
}
void gobangSurface(vec2 paneUv,vec2 grid,vec2 dimensions,float cellWorld,out float height,out vec3 normal){
vec2 local=fract(paneUv*grid)-.5;
float dx,dy;
float px=gobangProfile(local.x,dx),py=gobangProfile(local.y,dy);
// Crossing directional ribs produce rectangular optical pieces. A minimum
// of X/Y profiles would instead produce conspicuous diagonal triangles.
float shape=.25*px+.55*py+.20*px*py;
vec2 shapeDerivative=vec2((.25+.20*py)*dx,(.55+.20*px)*dy);
// A small continuous ripple bends the ribs without independent cell crops.
vec2 phase=paneUv*grid*6.2831853;
float ripple=.008*sin(phase.x*3.+sin(phase.y))*sin(phase.y);
vec2 rippleDerivative=.008*6.2831853*vec2(3.*cos(phase.x*3.+sin(phase.y))*sin(phase.y),cos(phase.x*3.+sin(phase.y))*cos(phase.y)*sin(phase.y)+sin(phase.x*3.+sin(phase.y))*cos(phase.y));
float amplitude=uGobangSurface.x*cellWorld*clamp(uCurvature,-1.,1.);
height=amplitude*(shape+ripple);
vec2 gradient=amplitude*(shapeDerivative+rippleDerivative)/dimensions;
normal=normalize(vec3(-gradient,1.));
}
vec4 renderGobang(vec3 incident,vec3 glassHit,vec2 paneUv,vec2 grid,float cellWorld){
vec2 dimensions=uPaneSize/grid;
float height;vec3 normal;
gobangSurface(paneUv,grid,dimensions,cellWorld,height,normal);
// Two refinements locate the shallow front surface along this camera ray.
vec3 frontHit=glassHit;
for(int iteration=0;iteration<2;iteration++){
frontHit=uContentCameraPosition+incident*((uGlassZ+height-uContentCameraPosition.z)/safeZ(incident.z));
gobangSurface(frontHit.xy/uPaneSize+.5,grid,dimensions,cellWorld,height,normal);
}
float ior=uGobangSurface.z;
vec3 internalRay=refract(incident,normal,1./ior);
float backZ=uGlassZ-uGobangSurface.w-uGobangSurface.x*cellWorld;
vec3 backHit=frontHit+internalRay*((backZ-frontHit.z)/safeZ(internalRay.z));
vec3 exitRay=refract(internalRay,vec3(0.,0.,1.),ior);
float transmission=step(.0001,dot(exitRay,exitRay));
if(transmission<.5)exitRay=incident;
vec3 sourceHit=backHit+exitRay*((uSourceZ-backHit.z)/safeZ(exitRay.z));
vec3 straightSource=glassHit+incident*((uSourceZ-glassHit.z)/safeZ(incident.z));
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));
float paneWeight=smoothstep(0.,max(1./grid.x,1./grid.y)*.75,edgeDistance);
float amount=clamp(uStrength,0.,1.)*paneWeight;
sourceHit=mix(straightSource,sourceHit,amount);
// Reuse the visible-depth solve; hidden surfaces still fall back to the plane.
sourceHit=refineSceneHit(backHit,normalize(mix(incident,exitRay,amount)),sourceHit,cellWorld);
vec4 result=sampleScene(projectUv(sourceHit));
// One static room environment; the same surface normal controls its reflection.
vec3 viewDirection=-incident,reflected=reflect(incident,normal);
float fresnel=.04+.96*pow(1.-clamp(dot(normal,viewDirection),0.,1.),5.);
vec3 room=mix(vec3(.09,.13,.17),vec3(.78,.84,.86),smoothstep(-.4,.65,reflected.y));
float lightTravel=(uLightPosition.z-frontHit.z)/safeZ(reflected.z);
vec2 lightUv=(frontHit.xy+reflected.xy*lightTravel-uLightPosition.xy)/(min(uPaneSize.x,uPaneSize.y)*vec2(.38,.52));
vec2 window=1.-smoothstep(vec2(.75),vec2(1.1),abs(lightUv));
float windowLight=window.x*window.y*step(0.,lightTravel)*step(.001,reflected.z);
room=mix(room,vec3(.94,.97,1.),windowLight);
float mullion=1.-smoothstep(.015,.06,abs(reflected.x+.12));
room=mix(room,vec3(.12,.16,.20),mullion*.55);
float gloss=clamp(uGlossStrength,0.,1.);
float reflectionWeight=gloss*uGobangEnvironmentStrength*(.35+.65*fresnel)*amount;
reflectionWeight=mix(reflectionWeight,1.,(1.-transmission)*amount);
result.rgb=mix(result.rgb,room,reflectionWeight);
result.a=1.;
return result;
}
#endif
void main(){
vec2 screenUv=vUv;
#ifdef REGION_ATLAS
vec2 atlasPixel=floor(vUv*uRegionAtlasSize),atlasCell=floor(atlasPixel/8.),atlasLocal=(mod(atlasPixel,8.)+.5)/8.;
vec2 atlasPaneUv=(atlasCell+atlasLocal)/uRegionGridSize;
screenUv=projectUv(vec3((atlasPaneUv-.5)*uPaneSize,uGlassZ));
#endif
if(uStrength<=.001){
#ifdef REGION_ATLAS
gl_FragColor=vec4(sampleScene(screenUv).rgb,0.);
#else
gl_FragColor=sampleScene(screenUv);
#include <colorspace_fragment>
#endif
return;}
vec4 f=uContentInverseViewProjection*vec4(screenUv*2.-1.,1.,1.);float fw=abs(f.w)<.0001?(f.w<0.?-.0001:.0001):f.w;vec3 farWorld=f.xyz/fw;vec3 i=normalize(farWorld-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;
vec2 paneUv=gh.xy/uPaneSize+.5;vec2 grid=vec2(uGridSize,max(1.,uGridSize*uPaneSize.y/uPaneSize.x));vec2 gridUv=paneUv*grid,cell=floor(gridUv),local=fract(gridUv)-.5,cellSize=1./grid;float style=clamp(uMosaicStyle,0.,2.),cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
#ifdef REGION_ATLAS
paneUv=atlasPaneUv;grid= uRegionGridSize;gridUv=paneUv*grid;cell=floor(gridUv);local=fract(gridUv)-.5;cellSize=1./grid;cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
#endif
if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){
#ifdef REGION_ATLAS
gl_FragColor=vec4(uBackgroundColor,0.);
#else
gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
#endif
return;}
#ifndef REGION_ATLAS
if(style>1.5){
gl_FragColor=renderGobang(i,gh,paneUv,grid,cellWorld);
#include <colorspace_fragment>
return;
}
#endif
vec2 cellDimensions=uPaneSize/grid;
float opticalTravel=-max(abs(uSourceZ-gh.z),.6);
vec2 affineUv,curvedSlope,facetSlope;mat2 transform;
evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,opticalTravel,style,affineUv,transform,curvedSlope,facetSlope);
if(style<=.5){
vec2 smoothFacet=.003*vec2(sin(6.2831853*paneUv.x),sin(6.2831853*paneUv.y));
curvedSlope+=smoothFacet;facetSlope=smoothFacet;
}else{
vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local));
float normalBlend=.16;
vec2 weights=1.-smoothstep(vec2(-normalBlend),vec2(normalBlend),edge);
float xSplitWeight=weights.x*(1.-weights.y),ySplitWeight=(1.-weights.x)*weights.y;
float diagonalSplitWeight=weights.x*weights.y,centerSplitWeight=(1.-weights.x)*(1.-weights.y);
if(weights.x>0.||weights.y>0.){
vec2 neighborAffine,neighborCurved,neighborFacet;mat2 neighborTransform;
vec2 xWeight=vec2(xSplitWeight,0.);
vec2 yWeight=vec2(0.,ySplitWeight);
affineUv*=centerSplitWeight;transform*=centerSplitWeight;curvedSlope*=centerSplitWeight;facetSlope*=centerSplitWeight;
if(xWeight.x>0.){
vec2 offset=vec2(neighborSign.x,0.);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,opticalTravel,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*xWeight.x;transform+=neighborTransform*xWeight.x;curvedSlope+=neighborCurved*xWeight.x;facetSlope+=neighborFacet*xWeight.x;
}
if(yWeight.y>0.){
vec2 offset=vec2(0.,neighborSign.y);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,opticalTravel,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*yWeight.y;transform+=neighborTransform*yWeight.y;curvedSlope+=neighborCurved*yWeight.y;facetSlope+=neighborFacet*yWeight.y;
}
if(diagonalSplitWeight>0.){
vec2 offset=neighborSign;
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,opticalTravel,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*diagonalSplitWeight;transform+=neighborTransform*diagonalSplitWeight;curvedSlope+=neighborCurved*diagonalSplitWeight;facetSlope+=neighborFacet*diagonalSplitWeight;
}
}
}
vec3 nCurved=normalize(vec3(curvedSlope,1.)),nFacet=normalize(vec3(facetSlope,1.));
vec3 straightHit=gh+i*((uSourceZ-gh.z)/safeZ(i.z));
vec3 curvedRay=bend(i,nCurved),curvedHit=gh+curvedRay*((uSourceZ-gh.z)/safeZ(curvedRay.z));
// Keep the artistic Mosaic optical path independent of physical glass-to-photo contact distance.
vec2 rawDelta=(curvedRay.xy/safeZ(curvedRay.z)-i.xy/safeZ(i.z))*opticalTravel;
vec2 worldDelta=vec2(softLimit(rawDelta.x,cellWorld*.55),softLimit(rawDelta.y,cellWorld*.55));
if(style>.5){
vec3 facetRay=bend(i,nFacet);vec3 facetHit=gh+facetRay*((uSourceZ-gh.z)/safeZ(facetRay.z));
vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy;
worldDelta=vec2(softLimit(base.x,cellWorld*.08),softLimit(base.y,cellWorld*.08));
vec2 d=curve/cellWorld;
worldDelta+=vec2(.3*d.x/(.3+abs(d.x)),.3*d.y/(.3+abs(d.y)))*cellWorld;
}else{
vec3 referenceI=normalize(gh-vec3(0.,0.,uContentCameraPosition.z)),referenceRay=bend(referenceI,nCurved);
vec2 incidenceDelta=curvedRay.xy/max(-curvedRay.z,.0001)-referenceRay.xy/max(-referenceRay.z,.0001);
vec2 boundedIncidence=incidenceDelta/sqrt(incidenceDelta*incidenceDelta+vec2(.025*.025));
vec2 cellEnvelope=.5+.5*cos(6.2831853*local);
worldDelta+=boundedIncidence*cellEnvelope*cellDimensions*signedCurvatureScale()*.18;
}
vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize);
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeTaper=style<=.5?1.25:.75;float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*edgeTaper,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight;
vec4 color;vec3 sceneHit=straightHit+vec3(worldDelta*strength,0.);vec2 finalSceneUv=projectUv(sceneHit);
if(style<=.5){
sceneHit=refineSceneHit(gh,normalize(mix(i,curvedRay,strength)),sceneHit,cellWorld);
finalSceneUv=projectUv(sceneHit);
}
#ifdef REGION_ATLAS
vec3 atlasCenterColor=sampleScene(finalSceneUv).rgb;
float atlasEvidence=regionProtectionWeight(finalSceneUv)*(1.-smoothstep(.10,.20,regionLuminance(atlasCenterColor)));
#endif
#ifdef REGION_ATLAS
if(style<=.5){
float representativeEvidence=atlasEvidence;
color=vec4(regionAtlasRepresentative(atlasCenterColor,atlasEvidence,sceneHit,gh,representativeEvidence),representativeEvidence);
atlasEvidence=representativeEvidence;
}else{
color=sampleCell(paneUv,sampleUv,strength,cell,cellSize,style);
}
#else
if(style<=.5){
color=sampleScene(finalSceneUv);
}else{
color=sampleCell(paneUv,sampleUv,strength,cell,cellSize,style);
}
#endif
#ifndef REGION_ATLAS
if(uRegionAvailable>.5&&uRegionStrength>.001&&uMosaicStyle<.5){
vec3 p0=regionColor(cell,0.),p1=regionColor(cell,1.),p2=regionColor(cell,2.),p3=regionColor(cell,3.);
float ownLabel=nearestRegion(color.rgb,p0,p1,p2,p3);vec4 votes=vec4(0.);float totalWeight=0.,ownWeight=0.;
vec2 texelCoord=fract(gridUv)*8.-.5,anchor=floor(texelCoord+.5);
for(int y=-1;y<=1;y++)for(int x=-1;x<=1;x++){
vec2 offset=vec2(float(x),float(y)),delta=anchor+offset-texelCoord;
float weight=pow(max(0.,1.-length(delta)/1.5),2.);
if(weight>0.){
vec3 atlasColor=regionAtlasColor(cell,anchor+offset);float label=nearestRegion(atlasColor,p0,p1,p2,p3);
votes+=vec4(step(abs(label-vec4(0.,1.,2.,3.)),vec4(.1)))*weight;totalWeight+=weight;
ownWeight+=step(abs(label-ownLabel),.1)*weight;
}
}
float winningLabel=ownLabel,winningWeight=ownWeight;
if(votes.x>winningWeight){winningLabel=0.;winningWeight=votes.x;}if(votes.y>winningWeight){winningLabel=1.;winningWeight=votes.y;}if(votes.z>winningWeight){winningLabel=2.;winningWeight=votes.z;}if(votes.w>winningWeight){winningLabel=3.;winningWeight=votes.w;}
bool simplify=totalWeight>0.&&winningWeight/totalWeight>.55&&ownWeight/totalWeight<.40;
float radialLabel=simplify?winningLabel:ownLabel;vec3 radialColor=selectRegion(p0,p1,p2,p3,radialLabel);
vec2 historyTexel=clamp(floor(fract(gridUv)*8.),vec2(0.),vec2(7.));
vec2 historyUv=(cell*8.+historyTexel+.5)/uRegionAtlasSize;
vec4 splitHistory=texture2D(uRegionHistory,historyUv);
vec3 splitColor=splitHistory.a>.00001?splitHistory.rgb/splitHistory.a:radialColor;
vec3 candidateColor=mix(radialColor,splitColor,splitHistory.a);
color.rgb=mix(color.rgb,candidateColor,clamp(uRegionStrength*clamp(uStrength,0.,1.)*regionSurfaceValid(finalSceneUv,gh),0.,1.));
}
#endif
#ifndef REGION_ATLAS
color.a=1.;
vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float surfaceReflectionWeight=mix(.35,1.,edgeWeight); float glossStrength=clamp(uGlossStrength,0.,1.); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float specGloss=spec*glossStrength*.12*surfaceReflectionWeight*clamp(uStrength,0.,1.); vec2 broadWindowMask=1.-smoothstep(vec2(.15),vec2(1.75),windowCoord); float broadFacing=pow(max(dot(nCurved,v),0.),2.); float broadReflection=broadWindowMask.x*broadWindowMask.y*broadFacing*glossStrength*.035*surfaceReflectionWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),specGloss+broadReflection); gl_FragColor=color;
// Common, static room environment. Reflected direction varies with the same
// curved normal as transmission; no image relighting or timed sparkle.
float fresnel=.04+.96*pow(1.-clamp(dot(nCurved,v),0.,1.),5.);
float environmentWeight=uEnvironmentStrength*glossStrength*(.35+.65*fresnel)*surfaceReflectionWeight*clamp(uStrength,0.,1.);
vec3 environmentColor=mix(vec3(.10,.13,.18),vec3(.65,.72,.80),smoothstep(-.35,.65,reflected.y));
if(style<=.5)color.rgb=mix(color.rgb,environmentColor,environmentWeight);
gl_FragColor=color;
#include <colorspace_fragment>
#else
color.a=atlasEvidence;
gl_FragColor=color;
#endif
}
`,p=`uniform sampler2D uSceneTexture; uniform mat4 uContentViewProjection; uniform mat4 uContentInverseViewProjection; uniform vec3 uContentCameraPosition,uBackgroundColor; uniform float uSceneZoom; uniform float uGlassZ,uSourceZ,uGridSize,uStrength,uTime; uniform vec2 uSceneOffset,uViewOffset,uResolution,uPetSize,uPaneSize;
varying vec2 vUv;
// Preserve the existing 1x material coordinates as the common projection magnifies and translates.
vec2 patternScreenUv(vec2 uv){return (uv-.5-uSceneOffset*.5)/max(uSceneZoom,1.)+.5;}
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;} vec4 sampleScene(vec2 uv){if(any(lessThan(uv,vec2(0.)))||any(greaterThan(uv,vec2(1.))))return vec4(.005605,.009721,.016807,1.);return texture2D(uSceneTexture,uv);} float hash(float x){return fract(sin(x*91.7)*43758.5453);} float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;} vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
void main(){if(uStrength<=.001){gl_FragColor=texture2D(uSceneTexture,vUv);
#include <colorspace_fragment>
return;}vec2 q=vUv*2.-1.;vec4 fh=uContentInverseViewProjection*vec4(q,1.,1.);float fwDen=abs(fh.w)<.0001?(fh.w<0.?-.0001:.0001):fh.w;vec3 fw=fh.xyz/fwDen;vec3 i=normalize(fw-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;vec2 paneUv=gh.xy/uPaneSize+.5;if(any(lessThan(paneUv,vec2(0.)))||any(greaterThan(paneUv,vec2(1.)))){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}float count=max(1.,uGridSize*uPetSize.x/uPetSize.y),coord=patternScreenUv(vUv).x*count,cell=floor(coord),phase=fract(coord)*6.2831853,ridge=sin(phase+uViewOffset.x*.9),variation=mix(.92,1.08,hash(cell));vec3 r=bend(i,normalize(vec3(-ridge*.34*variation,0.,1.)));vec3 sh=gh+i*((uSourceZ-gh.z)/safeZ(i.z)),bh=gh+r*((uSourceZ-gh.z)/safeZ(r.z));vec2 ru=vUv+projectUv(bh)-projectUv(sh);vec2 uv=mix(vUv,ru,clamp(uStrength,0.,1.));vec4 c=sampleScene(uv);c.a=1.;gl_FragColor=c;
#include <colorspace_fragment>
}
`,m=`uniform sampler2D uSceneTexture; uniform mat4 uContentViewProjection; uniform mat4 uContentInverseViewProjection; uniform vec3 uContentCameraPosition,uBackgroundColor; uniform float uSceneZoom; uniform float uGlassZ,uSourceZ,uGridSize,uStrength,uTime; uniform vec2 uSceneOffset,uViewOffset,uResolution,uPetSize,uPaneSize;
varying vec2 vUv;
// Preserve the existing 1x material coordinates as the common projection magnifies and translates.
vec2 patternScreenUv(vec2 uv){return (uv-.5-uSceneOffset*.5)/max(uSceneZoom,1.)+.5;}
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;} vec4 sampleScene(vec2 uv){if(any(lessThan(uv,vec2(0.)))||any(greaterThan(uv,vec2(1.))))return vec4(.005605,.009721,.016807,1.);return texture2D(uSceneTexture,uv);} float hash(vec2 p){return fract(sin(dot(p,vec2(41.3,17.9)))*15731.743);} float noise(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);return mix(mix(hash(i),hash(i+vec2(1.,0.)),f.x),mix(hash(i+vec2(0.,1.)),hash(i+vec2(1.)),f.x),f.y);} float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;} vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
void main(){if(uStrength<=.001){gl_FragColor=texture2D(uSceneTexture,vUv);
#include <colorspace_fragment>
return;}vec2 q=vUv*2.-1.;vec4 fh=uContentInverseViewProjection*vec4(q,1.,1.);float fwDen=abs(fh.w)<.0001?(fh.w<0.?-.0001:.0001):fh.w;vec3 fw=fh.xyz/fwDen;vec3 i=normalize(fw-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;vec2 paneUv=gh.xy/uPaneSize+.5;if(any(lessThan(paneUv,vec2(0.)))||any(greaterThan(paneUv,vec2(1.)))){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}vec2 st=patternScreenUv(vUv)*(uResolution/max(uResolution.y,1.)),np=st*9.+uViewOffset*.6,nn=vec2(noise(np),noise(np+vec2(23.7)))-vec2(.5);vec3 r=bend(i,normalize(vec3(nn*.32,1.)));vec3 sh=gh+i*((uSourceZ-gh.z)/safeZ(i.z)),bh=gh+r*((uSourceZ-gh.z)/safeZ(r.z));vec2 ru=vUv+projectUv(bh)-projectUv(sh),center=mix(vUv,ru,clamp(uStrength,0.,1.)),rad=vec2(.008)*uSceneZoom*clamp(uStrength,0.,1.);vec4 c=vec4(0.);c+=sampleScene(center+vec2(-rad.x,-rad.y));c+=sampleScene(center+vec2(0.,-rad.y));c+=sampleScene(center+vec2(rad.x,-rad.y));c+=sampleScene(center+vec2(-rad.x,0.));c+=sampleScene(center);c+=sampleScene(center+vec2(rad.x,0.));c+=sampleScene(center+vec2(-rad.x,rad.y));c+=sampleScene(center+vec2(0.,rad.y));c+=sampleScene(center+vec2(rad.x,rad.y));c/=9.;c.a=1.;gl_FragColor=c;
#include <colorspace_fragment>
}
`,h=`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`,g=`uniform sampler2D uRegionAtlas;
uniform vec2 uRegionAtlasSize,uRegionGridSize;
varying vec2 vUv;
float regionLuminance(vec3 c){return dot(c,vec3(.299,.587,.114));}
float distanceColor(vec3 a,vec3 b){vec3 d=a-b;float y=dot(d,vec3(.299,.587,.114));return dot(d,d)+.35*y*y;}
float sideColorCost(vec4 votes,vec3 target,vec3 c0,vec3 c1,vec3 c2,vec3 c3){return votes.x*distanceColor(c0,target)+votes.y*distanceColor(c1,target)+votes.z*distanceColor(c2,target)+votes.w*distanceColor(c3,target);}
vec4 atlasSampleData(vec2 cell,vec2 local){vec2 uv=(cell*8.+local+.5)/uRegionAtlasSize;return texture2D(uRegionAtlas,uv);}
vec3 atlasSample(vec2 cell,vec2 local){return atlasSampleData(cell,local).rgb;}
float nearestLabel(vec3 color,vec3 c0,vec3 c1,vec3 c2,vec3 c3){float d0=distanceColor(color,c0),d1=distanceColor(color,c1),d2=distanceColor(color,c2),d3=distanceColor(color,c3);if(d0<=d1&&d0<=d2&&d0<=d3)return 0.;if(d1<=d2&&d1<=d3)return 1.;if(d2<=d3)return 2.;return 3.;}
float majorityLabel(vec4 votes){float label=0.,count=votes.x;if(votes.y>count){label=1.;count=votes.y;}if(votes.z>count){label=2.;count=votes.z;}if(votes.w>count)label=3.;return label;}
void main(){
vec2 pixel=floor(gl_FragCoord.xy);float paletteIndex=mod(pixel.x,5.);vec3 cell=vec3(floor(pixel.x/5.),pixel.y,0.);
vec3 darkest=vec3(1.),lightest=vec3(0.);float minLum=2.,maxLum=-1.;
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec3 c=atlasSample(cell.xy,vec2(float(x),float(y)));float l=regionLuminance(c);
if(l<minLum){minLum=l;darkest=c;}if(l>maxLum){maxLum=l;lightest=c;}
}
vec3 third=darkest;float thirdDistance=-1.;
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec3 c=atlasSample(cell.xy,vec2(float(x),float(y)));float d=min(distanceColor(c,darkest),distanceColor(c,lightest));
if(d>thirdDistance){thirdDistance=d;third=c;}
}
vec3 fourth=darkest;float fourthDistance=-1.;
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec3 c=atlasSample(cell.xy,vec2(float(x),float(y)));float d=min(distanceColor(c,darkest),min(distanceColor(c,lightest),distanceColor(c,third)));
if(d>fourthDistance){fourthDistance=d;fourth=c;}
}
vec3 c0=darkest,c1=lightest,c2=third,c3=fourth;
for(int iteration=0;iteration<2;iteration++){
vec3 sum0=vec3(0.),sum1=vec3(0.),sum2=vec3(0.),sum3=vec3(0.);float n0=0.,n1=0.,n2=0.,n3=0.;
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec3 c=atlasSample(cell.xy,vec2(float(x),float(y)));
float d0=distanceColor(c,c0),d1=distanceColor(c,c1),d2=distanceColor(c,c2),d3=distanceColor(c,c3);
if(d0<=d1&&d0<=d2&&d0<=d3){sum0+=c;n0+=1.;}
else if(d1<=d2&&d1<=d3){sum1+=c;n1+=1.;}
else if(d2<=d3){sum2+=c;n2+=1.;}
else{sum3+=c;n3+=1.;}
}
if(n0>0.)c0=sum0/n0;if(n1>0.)c1=sum1/n1;if(n2>0.)c2=sum2/n2;if(n3>0.)c3=sum3/n3;
}
vec4 metadata=vec4(0.);
if(paletteIndex>3.5){
vec4 wholeVotes=vec4(0.);float cellEvidence=0.;
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec4 sampleData=atlasSampleData(cell.xy,vec2(float(x),float(y)));vec3 c=sampleData.rgb;float label=nearestLabel(c,c0,c1,c2,c3);
wholeVotes+=vec4(step(abs(label-vec4(0.,1.,2.,3.)),vec4(.1)));
cellEvidence+=sampleData.a;
}
float singleMajority=max(max(wholeVotes.x,wholeVotes.y),max(wholeVotes.z,wholeVotes.w))/64.;
float bestCost=1e20,bestConfidence=0.,bestAxis=0.,bestCut=4.,bestA=0.,bestB=0.;
for(int axis=0;axis<2;axis++)for(int candidateIndex=0;candidateIndex<5;candidateIndex++){
float cut=4.;
if(candidateIndex==1)cut=3.;else if(candidateIndex==2)cut=5.;else if(candidateIndex==3)cut=2.;else if(candidateIndex==4)cut=6.;
vec4 votesA=vec4(0.),votesB=vec4(0.),weightedVotesA=vec4(0.),weightedVotesB=vec4(0.);
for(int y=0;y<8;y++)for(int x=0;x<8;x++){
vec4 sampleData=atlasSampleData(cell.xy,vec2(float(x),float(y)));vec3 c=sampleData.rgb;float label=nearestLabel(c,c0,c1,c2,c3);
vec4 oneHot=vec4(step(abs(label-vec4(0.,1.,2.,3.)),vec4(.1)));float sampleWeight=1.+3.*sampleData.a;
float coordinate=axis==0?float(x)+.5:float(y)+.5;
if(coordinate<cut){votesA+=oneHot;weightedVotesA+=oneHot*sampleWeight;}
else{votesB+=oneHot;weightedVotesB+=oneHot*sampleWeight;}
}
float labelA=0.,labelB=0.,pairCost=1e20,pairVotes=0.;
for(int a=0;a<4;a++)for(int b=0;b<4;b++){
if(a!=b&&votesA[a]>0.&&votesB[b]>0.){
vec3 colorA=a==0?c0:(a==1?c1:(a==2?c2:c3));
vec3 colorB=b==0?c0:(b==1?c1:(b==2?c2:c3));
float candidateCost=sideColorCost(weightedVotesA,colorA,c0,c1,c2,c3)+sideColorCost(weightedVotesB,colorB,c0,c1,c2,c3);
if(candidateCost<pairCost){pairCost=candidateCost;labelA=float(a);labelB=float(b);pairVotes=votesA[a]+votesB[b];}
}
}
if(pairCost<1e20){
float accuracy=pairVotes/64.,gain=accuracy-singleMajority;
float oldConfidence=smoothstep(.55,.75,accuracy)*smoothstep(.015625,.078125,gain);
float confidence=.85+.15*oldConfidence;
confidence=mix(confidence,1.,smoothstep(.02,.06,cellEvidence/64.));
if(pairCost<bestCost){bestCost=pairCost;bestConfidence=confidence;bestAxis=float(axis);bestCut=cut;bestA=labelA;bestB=labelB;}
}
}
metadata=vec4((bestAxis*8.+bestCut)/255.,bestA/255.,bestB/255.,bestConfidence);
}
vec3 result=c0;
if(paletteIndex>.5&&paletteIndex<1.5)result=c1;
else if(paletteIndex>1.5&&paletteIndex<2.5)result=c2;
else if(paletteIndex>2.5&&paletteIndex<3.5)result=c3;
gl_FragColor=paletteIndex>3.5?metadata:vec4(result,1.);
}
`,_=`uniform sampler2D uRegionPalette,uPreviousHistory;
uniform vec2 uRegionAtlasSize,uRegionGridSize,uRegionPaletteSize;
uniform float uHistoryBlend;
varying vec2 vUv;
vec3 paletteColor(vec2 cell,float label){vec2 uv=(vec2(cell.x*5.+label,cell.y)+.5)/uRegionPaletteSize;return texture2D(uRegionPalette,uv).rgb;}
void main(){
vec2 pixel=floor(vUv*uRegionAtlasSize),cell=floor(pixel/8.),local=(mod(pixel,8.)+.5)/8.;
vec2 metadataUv=(vec2(cell.x*5.+4.,cell.y)+.5)/uRegionPaletteSize;
vec4 metadata=texture2D(uRegionPalette,metadataUv);
float packed=floor(metadata.r*255.+.5),axis=floor(packed/8.),cut=packed-axis*8.;
float coordinate=axis<.5?local.x:local.y;
float label=coordinate<cut/8.?floor(metadata.g*255.+.5):floor(metadata.b*255.+.5);
float confidence=clamp(metadata.a,0.,1.);
vec4 current=vec4(paletteColor(cell,label)*confidence,confidence);
if(uHistoryBlend>=.9999){gl_FragColor=current;return;}
vec4 previous=texture2D(uPreviousHistory,vUv);
gl_FragColor=mix(previous,current,clamp(uHistoryBlend,0.,1.));
}
`;function v(v){let{renderer:y,scene:b,frameScene:te,camera:x,petDepth:S,uniforms:C,depthUniforms:w,emptyTex:ne,regionHistoryUniform:T,state:E}=v,D=new n,O=new r({uniforms:C,vertexShader:h,fragmentShader:f}),k=new l(new d(2,2),O);D.add(k);let A=new n,j=new r({uniforms:C,vertexShader:h,fragmentShader:f,defines:{REGION_ATLAS:1},toneMapped:!1}),M=new l(new d(2,2),j);A.add(M);let N=new n,P={uRegionAtlas:{value:null},uRegionAtlasSize:C.uRegionAtlasSize,uRegionGridSize:C.uRegionGridSize},F=new r({uniforms:P,vertexShader:h,fragmentShader:g,toneMapped:!1}),I=new l(new d(2,2),F);N.add(I);let L=new o(-1,1,1,-1,0,1),R=new i(1,1,{colorSpace:``,type:e,depthBuffer:!0});R.depthTexture=new a(1,1,s),R.depthTexture.format=ee,C.uSceneDepth.value=R.depthTexture;let z=new i(104,104,{colorSpace:``,type:e,depthBuffer:!1,minFilter:t,magFilter:t}),B=new i(65,13,{colorSpace:``,type:u,depthBuffer:!1,minFilter:t,magFilter:t}),V=[0,1].map(()=>new i(104,104,{colorSpace:``,type:e,depthBuffer:!1,minFilter:t,magFilter:t})),H=new n,U={uRegionPalette:{value:B.texture},uPreviousHistory:{value:V[0].texture},uRegionAtlasSize:C.uRegionAtlasSize,uRegionGridSize:C.uRegionGridSize,uRegionPaletteSize:C.uRegionPaletteSize,uHistoryBlend:{value:1}},W=new r({uniforms:U,vertexShader:h,fragmentShader:_,toneMapped:!1}),G=new l(new d(2,2),W);H.add(G),C.uRegionPalette.value=B.texture,P.uRegionAtlas.value=z.texture;let K=``,q=``,J=0,Y=!1;function X(){let e=Math.max(1,Math.ceil(C.uGridSize.value)),t=Math.max(1,C.uGridSize.value*C.uPaneSize.value.y/Math.max(C.uPaneSize.value.x,.001)),n=Math.ceil(t),r=e*8,i=n*8,a=e*5,o=n,s=Math.max(r,i,a,o)<=y.capabilities.maxTextureSize,c=C.uRegionAvailable.value!==+!!s||s&&(z.width!==r||z.height!==i||B.width!==a||B.height!==o||V[0].width!==r||V[0].height!==i)||C.uRegionGridSize.value.x!==C.uGridSize.value||C.uRegionGridSize.value.y!==t;s&&(z.width!==r||z.height!==i)&&z.setSize(r,i),s&&(B.width!==a||B.height!==o)&&B.setSize(a,o);for(let e of V)s&&(e.width!==r||e.height!==i)&&e.setSize(r,i);C.uRegionAvailable.value=+!!s,C.uRegionAtlasSize.value.set(s?r:z.width,s?i:z.height),C.uRegionGridSize.value.set(C.uGridSize.value,t),C.uRegionPaletteSize.value.set(s?a:B.width,s?o:B.height),P.uRegionAtlas.value=z.texture,C.uRegionPalette.value=B.texture,C.uRegionAtlas.value=z.texture,c&&(K=``)}function Z(){let{mode:e,active:t,activeIsVideo:n,effect:r,style:i}=E(),a=C.uContentViewProjection.value.elements.map(e=>e.toFixed(4)).join(`,`),o=S.matrixWorld.elements.map(e=>e.toFixed(4)).join(`,`),s=w.uDepthMap.value;return[e,t.uuid,s.uuid,a,o,C.uGridSize.value,C.uStrength.value,C.uCurvature.value,C.uMosaicStyle.value,C.uPaneSize.value.x,C.uPaneSize.value.y,w.uDepthScale.value,w.uDepthBias.value,R.width,R.height,z.width,z.height].join(`|`)}function re(){let{mode:e,active:t,effect:n,style:r}=E(),i=S.matrixWorld.elements.map(e=>e.toFixed(4)).join(`,`),a=w.uDepthMap.value;return[e,n,r,t.uuid,a.uuid,C.uRegionAvailable.value,C.uGridSize.value,C.uMosaicStyle.value,C.uStrength.value,C.uRegionStrength.value,C.uCurvature.value,C.uGlassZ.value,C.uSourceZ.value,C.uPaneSize.value.x,C.uPaneSize.value.y,i,w.uDepthScale.value,w.uDepthBias.value,R.width,R.height,z.width,z.height,B.width,B.height].join(`|`)}function ie(){let e=y.getRenderTarget(),t=y.getClearColor(new c),n=y.getClearAlpha();y.setClearColor(0,0);for(let e of V)y.setRenderTarget(e),y.clear(!0,!1,!1);y.setRenderTarget(e),y.setClearColor(t,n),J=0,Y=!1,T.value=V[0].texture}function Q(e){let{mode:t,effect:n,style:r}=E(),i=re();if(i!==q&&(ie(),q=i),!((t===`final`||t===`glass`)&&n===`mosaic`&&r===`0`&&C.uRegionAvailable.value>.5&&C.uRegionStrength.value>.001&&C.uStrength.value>.001))return;let a=1-J;U.uPreviousHistory.value=V[J].texture,U.uHistoryBlend.value=Y?1-Math.exp(-e/.07):1,y.setRenderTarget(V[a]),y.render(H,L),y.setRenderTarget(null),J=a,Y=!0,T.value=V[J].texture}function ae(){let{effect:e,style:t,activeIsVideo:n}=E();if(!(e===`mosaic`&&t===`0`&&C.uRegionAvailable.value>.5&&C.uRegionStrength.value>.001&&C.uStrength.value>.001))return;let r=Z();(n||r!==K)&&(y.setRenderTarget(z),y.render(A,L),y.setRenderTarget(B),y.render(N,L),y.setRenderTarget(null),K=r)}function oe(e,t=null){let{mode:n}=E();if(n===`final`||n===`glass`){y.setRenderTarget(R),y.render(b,x),C.uSceneTexture.value=R.texture,ae(),Q(e),y.setRenderTarget(t),y.render(D,L);let n=y.autoClear;y.autoClear=!1,y.clearDepth(),y.render(te,x),y.autoClear=n}else Q(e),y.setRenderTarget(t),y.render(b,x)}let $=!1;return{render:oe,setEffect(e){O.fragmentShader=e===`reeded`?p:e===`frosted`?m:f,O.needsUpdate=!0},resize(e,t){R.setSize(e,t),X()},invalidate(){K=``,q=``,Y=!1},dispose(){if(!$){$=!0,R.dispose(),z.dispose(),B.dispose(),V.forEach(e=>e.dispose());for(let e of[k,M,I,G])e.geometry.dispose();for(let e of[O,j,F,W])e.dispose();D.clear(),A.clear(),N.clear(),H.clear()}}}}export{v as createOriginalGlassRenderer};
+1
View File
@@ -0,0 +1 @@
import{a as e,d as t,h as n,l as r,n as i,o as a,s as o,u as s}from"./index-BYcEjj7E.js";var c=class{renderer;background;targetA=new n(1,1,{type:e});targetB=new n(1,1,{type:e});blitMaterial=new o({toneMapped:!1,depthTest:!1,depthWrite:!1});blit=new a(new s(2,2),this.blitMaterial);blitScene=new t;blitCamera=new r(-1,1,1,-1,0,1);disposed=!1;constructor(e,t){this.renderer=e,this.background=t,this.blitScene.add(this.blit)}resize(e,t){for(let n of[this.targetA,this.targetB])(n.width!==e||n.height!==t)&&n.setSize(e,t)}composite(e,t){this.renderer.setRenderTarget(null),this.renderer.setScissorTest(!1);let n=this.renderer.getClearColor(new i),r=this.renderer.getClearAlpha();this.renderer.setClearColor(this.background,1),this.renderer.clear(),this.renderer.setClearColor(n,r),this.renderer.setScissorTest(!0);try{for(let n=0;n<e.length;n++){let r=e[n];this.renderer.setViewport(r.x,t-r.y-r.height,r.width,r.height),this.renderer.setScissor(r.x,t-r.y-r.height,r.width,r.height),this.blitMaterial.map=n===0?this.targetA.texture:this.targetB.texture,this.renderer.render(this.blitScene,this.blitCamera)}}finally{this.renderer.setScissorTest(!1),this.renderer.setViewport(0,0,this.renderer.domElement.clientWidth,t)}}dispose(){this.disposed||(this.disposed=!0,this.renderer.getContext().isContextLost()||(this.renderer.setRenderTarget(null),this.renderer.setScissorTest(!1)),this.targetA.dispose(),this.targetB.dispose(),this.blit.geometry.dispose(),this.blitMaterial.dispose(),this.blitScene.clear())}};export{c as PhysicalGlassComparison};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4173
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-4317
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
*{box-sizing:border-box}body{margin:0;background:#0d1117;color:#e6edf3;font:14px system-ui,sans-serif}main{min-height:100vh;display:flex;flex-direction:column}.stage{position:relative;flex:1;min-height:60vh;background:#111923}#canvas-wrap{position:absolute;top:0;right:0;bottom:0;left:0;touch-action:none}canvas{width:100%;height:100%;display:block;cursor:grab}canvas.is-dragging{cursor:grabbing}#empty{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:#82909f;pointer-events:none;font-size:1rem}#hint{position:absolute;bottom:12px;left:50%;transform:translate(-50%);margin:0;padding:8px 12px;border-radius:20px;background:#0008;color:#c8d1dc;transition:opacity .5s}aside{padding:18px;display:grid;gap:12px;background:#161d27;border-top:1px solid #293544;min-width:0;overflow:hidden}h1{font-size:1.1rem;margin:0 0 4px}label{display:grid;gap:5px;color:#aeb9c6}input,select,button{font:inherit;color:inherit;background:#202b38;border:1px solid #3b4a5c;border-radius:7px;padding:9px}#test-photo{min-width:0;width:100%;max-width:100%}input[type=range]{padding:0;accent-color:#72b6ff}.upload{position:relative;cursor:pointer;text-align:center;background:#25364a;border-radius:7px;padding:10px;color:#dcecff}.upload input[type=file]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border:0}.upload:focus-within{outline:2px solid #8bc5ff;outline-offset:2px}output{float:right;color:#8bc5ff}button{cursor:pointer}#status{min-height:20px;margin:0;color:#ffbe79}#depth-provenance{margin:0;color:#aeb9c6;font-size:12px}#depth-debug{max-width:100%;overflow-x:auto;white-space:pre-wrap;word-break:break-word;font-size:11px}@media (min-width:720px){main{height:100dvh;min-height:0;flex-direction:row}.stage{min-height:0}aside{width:280px;flex:0 0 280px;align-content:start;border-top:0;border-left:1px solid #293544;overflow-y:auto}}.visual-column{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.reference-strip{position:relative;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;width:100%;height:200px;flex:0 0 200px;padding:10px 12px;background:#0d1117;border-bottom:1px solid #293544;overflow:hidden}.reference-strip[hidden]{display:none}.reference-strip figure{display:grid;grid-template-rows:minmax(0,1fr) auto;gap:5px;min-width:0;min-height:0;margin:0;padding:6px;border:1px solid #293544;border-radius:8px;background:#111923}.reference-strip img{display:block;width:100%;height:100%;min-width:0;min-height:0;object-fit:contain}.reference-strip figcaption{text-align:center;color:#aeb9c6;font-size:12px}#reference-status{position:absolute;left:50%;bottom:16px;transform:translate(-50%);margin:0;padding:5px 8px;border-radius:6px;background:#000b;color:#ffbe79;font-size:12px;white-space:nowrap}#reference-status[hidden]{display:none}@media (min-width:720px){.visual-column{min-height:0}.reference-strip{height:clamp(180px,30vh,300px);flex-basis:clamp(180px,30vh,300px)}}details{border:1px solid #344255;border-radius:8px;padding:10px 12px;background:#111923}details summary{cursor:pointer;color:#dce6f2;font-weight:600}details[open] summary{margin-bottom:8px}.details-content{display:grid;gap:12px;padding-top:4px}.control-note,.control-help{margin:0;color:#9eacbb;font-size:12px}.control-help{margin-top:-7px}.core-optics{gap:12px}
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

+1
View File
@@ -0,0 +1 @@
var e=`/assets/reference1-D4rK2azX.png`;export{e as default};
-1
View File
@@ -1 +0,0 @@
const e="/assets/reference1-D4rK2azX.png";export{e as default};
-1
View File
@@ -1 +0,0 @@
const e="/assets/reference2-6y7JVIce.png";export{e as default};
+1
View File
@@ -0,0 +1 @@
var e=`/assets/reference2-6y7JVIce.png`;export{e as default};
-56
View File
@@ -1,56 +0,0 @@
{
"width": 415,
"height": 739,
"convention": "0 = far/background, 1 = near/foreground",
"source": "Hand-authored procedural Gaussian ellipses for local testing; not AI-estimated or physically measured.",
"landmarks": [
{
"name": "nose",
"x": 235,
"y": 310,
"depth": 0.98
},
{
"name": "left-eye",
"x": 198,
"y": 278,
"depth": 0.68
},
{
"name": "right-eye",
"x": 249,
"y": 273,
"depth": 0.68
},
{
"name": "face",
"x": 221,
"y": 267,
"depth": 0.6
},
{
"name": "left-ear",
"x": 158,
"y": 213,
"depth": 0.43
},
{
"name": "right-ear",
"x": 262,
"y": 206,
"depth": 0.43
},
{
"name": "body",
"x": 139,
"y": 419,
"depth": 0.3
},
{
"name": "muzzle",
"x": 224,
"y": 305,
"depth": 0.82
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 813 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 840 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 728 KiB

Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+257 -81
View File
@@ -1,16 +1,242 @@
<!doctype html>
<html lang="ko">
<html lang="ko" data-theme="wood">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pet Behind Glass</title>
<script type="module" crossorigin src="/assets/index-DOC1JJ07.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-JVljdMQi.css">
<meta name="theme-color" content="#e6dfcf">
<meta name="description" content="우리집 멍멍이, 할머니 집 유리문 너머로">
<title>Behind Glass · 할머니집 창가</title>
<link rel="preload" href="/fonts/BMKkubulim.woff" as="font" type="font/woff" crossorigin>
<script type="module" crossorigin src="/assets/index-BYcEjj7E.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CAEr_JzP.css">
</head>
<body>
<div id="app">
<main>
<div class="visual-column">
<input hidden id="image" type="file" accept="image/jpeg,image/png,image/webp" aria-label="멍멍이 사진 선택">
<main id="experience" data-flow="entry">
<section id="pet-stage" class="stage" aria-label="유리문 너머의 멍멍이">
<div id="canvas-wrap" tabindex="0" aria-label="사진 미리보기. 드래그하거나 방향키로 시점을 움직이세요" aria-describedby="scene-instructions"><div id="empty" hidden></div><span id="door-handle" aria-hidden="true" hidden>↔</span></div>
<div id="guide-scene-target" aria-hidden="true"></div>
<div id="door-controls" hidden><button id="door-toggle" type="button">문 열기</button></div>
<button id="gyro-recenter" type="button" hidden aria-describedby="gyro-status">중앙 맞추기</button>
<button id="photo-adjust-open" type="button" hidden>사진 위치 조절</button>
<section id="photo-adjust-panel" hidden aria-label="사진 위치 조절">
<div class="photo-adjust-heading"><strong>사진 위치 조절 중</strong><button id="photo-adjust-done" type="button">완료</button></div>
<label for="photo-adjust-zoom">확대 <output id="photo-adjust-value">1.00</output><input id="photo-adjust-zoom" type="range" min="1" max="3" step="0.05" value="1"></label>
<p id="photo-adjust-help">사진을 확대한 뒤 드래그해서 위치를 맞춰주세요</p>
<button id="photo-adjust-reset" type="button">처음 구도로</button>
</section>
<p id="frame-notice" class="frame-notice" role="status" hidden></p>
<p id="hint" aria-hidden="true"></p>
</section>
<div id="comparison-overlay" hidden aria-label="동기화 유리 렌더러 비교">
<div id="comparison-label-a" class="comparison-label">A · 기존 코드</div>
<div id="comparison-label-b" class="comparison-label">굴절 보정 B · 맞춤 굴절</div>
<div id="comparison-metrics"><p id="comparison-timing">첫 프레임을 준비하는 중…</p><p id="comparison-memory"></p><p>GPU 시간·메모리 미측정</p></div>
</div>
<header id="scene-toolbar" class="scene-toolbar">
<a class="scene-brand" href="#app" aria-label="Behind Glass">behind glass</a>
<button id="scene-menu-open" type="button" hidden aria-haspopup="dialog" aria-controls="scene-menu" aria-expanded="false">메뉴</button>
<button id="sample-home" type="button" hidden aria-label="첫 화면으로 가기" title="첫 화면으로 가기" aria-haspopup="dialog" aria-controls="home-dialog"><svg viewBox="0 0 24 24" width="23" height="23" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m3 10 9-7 9 7M5 9v12h5v-7h4v7h5V9"/></svg></button>
<div class="scene-actions"><button id="credits-open" type="button" aria-haspopup="dialog" aria-controls="credits-dialog">만든 사람</button></div>
<div id="scene-motion" class="scene-motion" hidden>
<button id="gyro" type="button" aria-pressed="false" aria-describedby="gyro-status gyro-privacy"><span id="gyro-label">기울기 연결</span></button>
<p id="gyro-notice" role="status" aria-live="polite" hidden></p>
<p id="gyro-status" class="sr-only" role="status" aria-live="polite">기울기는 꺼져 있어요. 드래그로 둘러볼 수 있어요.</p>
</div>
</header>
<section id="welcome" class="welcome" aria-labelledby="welcome-title">
<div class="entry-door" aria-hidden="true"><span></span></div>
<p class="entry-kicker">behind glass</p>
<h1 id="welcome-title">할머니 집에<br><span id="welcome-name">멍멍이가 왔어요</span></h1>
<button id="entry-name-edit" class="entry-samples" type="button" hidden>이름 바꾸기</button>
<div id="entry-name-step" class="entry-step">
<div class="pet-name-field entry-name-field">
<label for="entry-pet-name">멍멍이 이름이 뭐예요?</label>
<input id="entry-pet-name" type="text" autocomplete="off" maxlength="40" spellcheck="false" placeholder="예시 : 장준" aria-describedby="entry-name-help">
<small id="entry-name-help">비워두어도 괜찮아요 · 이 브라우저에만 저장돼요</small>
<small id="entry-name-notice" class="name-storage-notice" role="status" hidden>이름은 지금만 적용돼요. 이 브라우저에 저장하지 못했어요.</small>
</div>
<button id="entry-name-next" class="primary-upload" type="button">다음</button>
<button id="entry-name-skip" class="entry-samples" type="button">이름 없이 둘러보기</button>
</div>
<div id="entry-photo-step" class="entry-step" hidden>
<button id="entry-photo-button" class="upload primary-upload" type="button" data-pick-photo>멍멍이 사진 고르기</button>
<button id="entry-samples" class="entry-samples" type="button">샘플로 먼저 둘러보기</button>
</div>
<div id="entry-notices" hidden role="group" aria-label="사진 선택 도움말">
<button id="notice-prev" type="button" aria-label="이전 안내">‹</button>
<div class="notice-slides">
<p id="photo-tip" class="entry-notice-slide" data-active="true">정면 사진이면 더 귀여워요</p>
<p id="privacy-tip" class="entry-notice-slide" data-active="false" aria-hidden="true">사진은 이 브라우저 안에서만 처리해요</p>
<p id="paste-tip" class="entry-notice-slide" data-active="false" aria-hidden="true" hidden>복사한 사진도 붙여넣을 수 있어요<br><span>Ctrl+V · ⌘V</span></p>
</div>
<button id="notice-next" type="button" aria-label="다음 안내">›</button>
<span id="notice-position" aria-hidden="true">1 / 2</span>
<span id="notice-status" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></span>
</div>
<p class="arrival-count" data-arrival-count hidden></p>
</section>
<section id="flow-overlay" class="flow-overlay" hidden aria-labelledby="flow-title" aria-busy="false">
<div id="journey" class="journey" hidden>
<div class="journey-scenery" aria-hidden="true"><span class="journey-sun"></span><span class="journey-cloud"></span><span class="journey-hill"></span><span class="journey-tree"></span></div>
<svg class="journey-house" viewBox="0 0 140 160" aria-hidden="true" focusable="false">
<path d="M19 65h103v86H19z" fill="#e5d6b8" stroke="#735943" stroke-width="3"/>
<path d="M9 66 71 18l61 48z" fill="#87775c" stroke="#645640" stroke-width="4" stroke-linejoin="round"/>
<path d="M105 37V17h12v31" fill="#ae8870" stroke="#735943" stroke-width="3"/>
<path d="M76 88h30v63H76z" fill="#98734e" stroke="#715239" stroke-width="3"/>
<path d="M29 82h31v37H29z" fill="#c1d1bd" stroke="#7d684b" stroke-width="4"/>
<path d="M44 82v37M29 100h31" stroke="#7d684b" stroke-width="3"/>
<circle cx="99" cy="122" r="2.5" fill="#f3d495"/>
<path d="M11 152h121" stroke="#867858" stroke-width="5" stroke-linecap="round"/>
</svg>
<div class="journey-road" aria-hidden="true"></div>
<div class="journey-traveler">
<div class="journey-passenger"><img id="flow-photo" class="flow-photo" alt="선택한 멍멍이 사진" hidden></div>
</div>
<div class="journey-car">
<svg class="journey-car-body" viewBox="0 0 300 180" aria-hidden="true" focusable="false">
<path d="M48 104 62 55q4-15 20-15h88q13 0 20 15l27 49" fill="#d6e0ce" stroke="#785c42" stroke-width="5" stroke-linejoin="round"/>
<path d="M170 52h4q5 0 9 10l15 31h-28z" fill="#f3f1df" stroke="#8e9c8d" stroke-width="3"/>
<path d="M31 101h181l22 9h25q21 0 23 20v22H18v-27q0-20 13-24z" fill="#bc7250" stroke="#78513d" stroke-width="5" stroke-linejoin="round"/>
<path d="M27 129h245M156 104v42" stroke="#934e38" stroke-width="3"/>
<path d="M173 115h14" stroke="#f7dbb4" stroke-width="4" stroke-linecap="round"/>
<path d="M18 146h21m217 0h29" stroke="#e2cbb2" stroke-width="8" stroke-linecap="round"/>
<rect x="263" y="116" width="14" height="12" rx="3" fill="#ffe5a3"/>
<rect x="21" y="116" width="7" height="10" rx="2" fill="#e6a092"/>
<g class="journey-wheel"><circle cx="66" cy="150" r="24" fill="#514b43"/><circle cx="66" cy="150" r="12" fill="#d4cab3"/><path d="M66 141v18m-9-9h18" stroke="#8a806f" stroke-width="3"/></g>
<g class="journey-wheel"><circle cx="231" cy="150" r="24" fill="#514b43"/><circle cx="231" cy="150" r="12" fill="#d4cab3"/><path d="M231 141v18m-9-9h18" stroke="#8a806f" stroke-width="3"/></g>
</svg>
</div>
</div>
<h2 id="flow-title" role="status" aria-live="polite">멍멍이를 할머니 집으로 데려가는 중…</h2>
<p id="flow-detail" aria-live="polite"></p>
<div id="flow-actions" class="flow-actions"><button id="flow-retry" type="button" hidden>다시 시도하기</button><button id="flow-flat" type="button" hidden>평면 사진으로 보기</button><button class="upload" type="button" data-pick-photo>다른 사진 고르기</button><button id="flow-samples" type="button" hidden>다른 샘플 고르기</button></div>
</section>
</main>
<dialog id="home-dialog" aria-labelledby="home-title" aria-describedby="home-detail">
<h2 id="home-title">정말 첫 화면으로 갈까요?</h2>
<p id="home-detail">돌아오려면 사진을 다시 골라야 해요.</p>
<div class="home-actions"><button id="home-cancel" type="button" autofocus>계속 볼게요</button><button id="home-confirm" type="button">첫 화면으로</button></div>
</dialog>
<dialog id="scene-menu" aria-labelledby="scene-menu-title">
<div class="scene-menu-heading"><h2 id="scene-menu-title">메뉴</h2><button id="scene-menu-close" type="button" autofocus>닫기</button></div>
<div class="scene-menu-items">
<button id="export-open" type="button" hidden aria-haspopup="dialog" aria-controls="export-dialog">내려받기</button>
<button id="scene-photo-change" class="upload" type="button" hidden data-pick-photo>사진 바꾸기</button>
<button id="settings-open" type="button" hidden aria-haspopup="dialog" aria-controls="settings-dialog">설정</button>
<button id="scene-zoom-reset" type="button" hidden>전체 보기</button>
<button id="comparison-exit" type="button" hidden>비교 끝내기</button>
<button id="scene-guide-replay" type="button" hidden>사용법 다시 보기</button>
</div>
</dialog>
<dialog id="sample-dialog" aria-labelledby="sample-title" aria-describedby="sample-description">
<div class="sample-heading"><h2 id="sample-title">어떤 멍멍이를 만나볼까요?</h2><button id="sample-close" type="button" aria-label="샘플 사진 선택 닫기">닫기</button></div>
<p id="sample-description">사진을 고른 뒤 아래 버튼을 눌러주세요</p>
<div class="sample-choices" role="group" aria-label="샘플 사진">
<button type="button" data-sample-choice="dog5" aria-pressed="true"><img src="/assets/samples/dog5.jpg" alt="" loading="lazy"><span>누렁이 · 정면 인사</span><small>가장 추천해요</small></button>
<button type="button" data-sample-choice="dog1" aria-pressed="false"><img src="/assets/samples/dog1.jpg" alt="" loading="lazy"><span>호두 · 저녁 얼굴</span><small>얼굴 사진 추천</small></button>
<button type="button" data-sample-choice="dog2" aria-pressed="false"><img src="/assets/samples/dog2.jpg" alt="" loading="lazy"><span>두부 · 나무 아래</span></button>
<button type="button" data-sample-choice="dog3" aria-pressed="false"><img src="/assets/samples/dog3.jpg" alt="" loading="lazy"><span>보리 · 낮잠</span></button>
<button type="button" data-sample-choice="dog4" aria-pressed="false"><img src="/assets/samples/dog4.jpg" alt="" loading="lazy"><span>콩이 · 저녁 산책</span></button>
</div>
<div class="sample-footer"><button id="sample-confirm" type="button">이 사진으로 둘러보기</button></div>
</dialog>
<dialog id="render-recovery" role="alertdialog" aria-labelledby="render-recovery-title" aria-describedby="render-recovery-detail">
<h2 id="render-recovery-title">화면을 잠시 그리지 못했어요</h2>
<p id="render-recovery-detail">그래픽 연결이 끊겼어요. 브라우저가 복구하면 같은 사진으로 돌아가요.<br>계속 이 화면이면 새로고침해 주세요.</p>
<button id="render-reload" type="button" autofocus>새로고침</button>
<p class="control-help">새로고침하면 사진을 다시 선택해야 해요</p>
</dialog>
<dialog id="mesh-render-error" role="alertdialog" aria-labelledby="mesh-error-title">
<h2 id="mesh-error-title">B 유리를 그리지 못했어요</h2>
<p>사진과 설정은 그대로예요. B를 다시 시도하거나 A 진단 모드를 직접 선택할 수 있어요.</p>
<button id="mesh-retry" type="button" autofocus>B 다시 시도</button>
<button id="mesh-use-a" type="button">A 진단 모드로 보기</button>
</dialog>
<dialog id="first-guide" aria-hidden="true"></dialog>
<dialog id="export-dialog" aria-labelledby="export-title">
<div class="export-heading"><h2 id="export-title">현재 구도 저장</h2><button id="export-close" type="button">닫기</button></div>
<div class="export-choices"><button id="export-photo" type="button">굴절사진만 저장</button><button id="export-scene" type="button">방까지 함께 저장</button></div>
<p id="export-status" role="status" aria-live="polite">마음에 드는 현재 구도를 저장해 보세요</p>
<img id="export-preview" alt="저장 미리보기" hidden>
<div class="export-actions"><a id="export-download" hidden download>PNG 내려받기</a><button id="export-share" type="button" hidden>공유하기</button></div>
</dialog>
<dialog id="credits-dialog" aria-labelledby="credits-title">
<div class="credits-heading"><h2 id="credits-title">만든 사람</h2><button id="credits-close" type="button" aria-label="만든 사람 닫기">닫기</button></div>
<dl class="credits-info"><dt>제작</dt><dd>horoli</dd><dt>버그 제보</dt><dd><a href="mailto:sunha321@gmail.com">sunha321@gmail.com</a></dd></dl>
<a class="credits-licenses" href="/THIRD-PARTY-NOTICES.txt" target="_blank" rel="noopener">리소스·라이선스 안내</a>
</dialog>
<dialog id="settings-dialog" aria-labelledby="settings-title">
<aside aria-label="사진 꾸미기">
<div class="editor-heading"><h2 id="settings-title">창가 설정</h2><button id="settings-close" type="button" aria-label="설정 닫기">닫기</button></div>
<p class="arrival-count" data-arrival-count hidden></p>
<button id="frame-retry" type="button" hidden>나무 창틀 다시 불러오기</button>
<button id="house-retry" type="button" hidden>집 주변 공간 다시 불러오기</button>
<div class="pet-name-field settings-name-field">
<label for="settings-pet-name">멍멍이 이름</label>
<input id="settings-pet-name" type="text" autocomplete="off" maxlength="40" spellcheck="false" placeholder="예시 : 장준" aria-describedby="settings-name-help">
<small id="settings-name-help">언제든 바꿀 수 있어요 · 최대 20자</small>
<small id="settings-name-notice" class="name-storage-notice" role="status" hidden>이름은 지금만 적용돼요. 이 브라우저에 저장하지 못했어요.</small>
</div>
<label class="theme-setting" for="theme-select">테마<select id="theme-select"><option value="wood">할머니집 창가</option><option value="booth">통통 포토부스</option></select></label>
<div id="tool-tabs" role="tablist" aria-label="사진 도구">
<button id="tab-glass" role="tab" aria-controls="panel-glass" aria-selected="true" data-tool="glass"><span aria-hidden="true">▦</span> 유리</button>
<button id="tab-photo" role="tab" aria-controls="panel-photo" aria-selected="false" tabindex="-1" data-tool="photo"><span aria-hidden="true">▧</span> 사진</button>
<button id="tab-view" role="tab" aria-controls="panel-view" aria-selected="false" tabindex="-1" data-tool="view">시점 조절</button>
</div>
<section id="panel-glass" class="tool-panel" role="tabpanel" aria-labelledby="tab-glass" data-panel="glass">
<div id="pattern-cards" role="group" aria-label="유리 무늬">
<button type="button" class="pattern-card" data-pattern="2" aria-pressed="true"><span class="pattern-swatch swatch-gobang" aria-hidden="true"></span><span class="pattern-name">고방유리</span><span class="pattern-detail">볼록한 조각</span></button>
<button type="button" class="pattern-card" data-pattern="0" aria-pressed="false"><span class="pattern-swatch swatch-color" aria-hidden="true"></span><span class="pattern-name">사각 색면</span><span class="pattern-detail">컬러 모자이크</span></button>
<button type="button" class="pattern-card" data-pattern="1" aria-pressed="false"><span class="pattern-swatch swatch-fine" aria-hidden="true"></span><span class="pattern-name">촘촘 유리</span><span class="pattern-detail">작은 조각들</span></button>
</div>
<label class="sr-only" aria-hidden="true" for="mosaic-style">유리 패턴<select id="mosaic-style" tabindex="-1"><option value="2" selected>고방유리 · 표면 굴절</option><option value="0">Ref1 옵션 2 · 사각 색면</option><option value="1">기존 촘촘 유리</option></select></label>
<p class="control-help">고방유리의 크기와 굴곡을 바꿔보세요.</p>
<label class="range-control" for="grid-size"><span>유리 무늬 크기 <output id="gridOut">20</output></span><input id="grid-size" type="range" min="10" max="24" step="1" value="20"><span class="range-ends" aria-hidden="true"><span>큼직하게</span><span>촘촘하게</span></span></label>
<div id="curvature-controls" class="core-optics"><label class="inline-control" for="curvature">유리 굴곡<select id="curvature"><option value="1" selected>볼록</option><option value="-1">오목</option></select></label></div>
<label class="range-control" for="photo-glass-distance"><span>유리와의 거리 <output id="photoGlassDistanceOut">16%</output></span><input id="photo-glass-distance" type="range" min="0" max="21" step="1" value="16"><span class="range-ends" aria-hidden="true"><span>가까이</span><span>조금 멀리</span></span></label>
</section>
<section id="panel-photo" class="tool-panel" role="tabpanel" aria-labelledby="tab-photo" data-panel="photo" hidden>
<div class="section-intro compact"><h2>사진 구도</h2><p>마음에 드는 위치로 직접 맞춰보세요</p></div>
<label class="range-control" for="photo-zoom"><span>사진 확대 <output id="photoZoomOut">1.00</output></span><input id="photo-zoom" type="range" min="1" max="3" step="0.05" value="1"></label>
<label class="range-control" for="photo-pan-x"><span>가로 위치 <output id="photoPanXOut">0.00</output></span><input id="photo-pan-x" type="range" min="-1" max="1" step="0.01" value="0"></label>
<label class="range-control" for="photo-pan-y"><span>세로 위치 <output id="photoPanYOut">0.00</output></span><input id="photo-pan-y" type="range" min="-1" max="1" step="0.01" value="0"></label>
<p class="control-help">사진을 확대한 뒤 가로·세로 위치를 움직일 수 있어요</p>
<button id="photo-reset" class="quiet-button">처음 구도로</button>
</section>
<section id="panel-view" class="tool-panel" role="tabpanel" aria-labelledby="tab-view" data-panel="view" hidden>
<button id="guide-replay" type="button">사용법 다시 보기</button>
<label class="range-control" for="camera-offset"><span>움직임 범위 <output id="cameraOut">0.75</output></span><input id="camera-offset" type="range" min="0.35" max="0.90" step="0.01" value="0.75"><span class="range-ends" aria-hidden="true"><span>살짝</span><span>넓게</span></span></label>
<button id="reset" type="button">정면으로</button>
<p id="scene-instructions" class="control-help">드래그·방향키로 둘러보고, 휠·두 손가락으로 확대해요. 손잡이를 왼쪽으로 밀면 열리고 오른쪽으로 밀면 닫혀요. 문 버튼으로도 조작할 수 있어요.</p>
<p id="gyro-privacy" class="control-help">버튼을 누르면 필요할 때 브라우저가 센서 접근을 물어요. 센서값은 이 기기 안에서만 사용해요</p>
</section>
</aside>
</dialog>
<!-- Internal control adapters preserve renderer diagnostics and retry state, not settings UI. -->
<div id="internal-controls" hidden inert aria-hidden="true">
<label class="range-control" for="camera-composition"><span>창가 구도</span><select id="camera-composition" aria-describedby="camera-composition-note"><option value="wall">벽 옆에서 보기 · 실험</option><option value="legacy">기존 구도 · 넓은 방 보기</option></select></label>
<p id="camera-composition-note" class="control-help" role="status">기존 구도로 바로 돌아갈 수 있어요. 선택은 이 브라우저에 저장돼요.</p>
<details id="sample-browser" class="sample-browser"><summary>샘플로 둘러보기 <span aria-hidden="true">+</span></summary><div class="details-content">
<div class="sample-strip" aria-hidden="true"><img src="/assets/samples/dog5.jpg" alt="" loading="lazy"><img src="/assets/samples/dog1.jpg" alt="" loading="lazy"><img src="/assets/samples/dog2.jpg" alt="" loading="lazy"><img src="/assets/samples/dog3.jpg" alt="" loading="lazy"><img src="/assets/samples/dog4.jpg" alt="" loading="lazy"></div>
<label for="test-photo">샘플 사진<select id="test-photo"><option value="dog5">누렁이 · 정면 인사</option><option value="dog1">호두 · 저녁 얼굴</option><option value="dog2">두부 · 나무 아래</option><option value="dog3">보리 · 낮잠</option><option value="dog4">콩이 · 저녁 산책</option></select></label>
<button id="sample" class="tactile-button">이 사진으로 해보기</button>
</div></details>
<details id="developer-tools"><summary>개발 도구 <span aria-hidden="true">+</span></summary><div id="developer-content" class="details-content">
<label for="glass-renderer">유리 렌더러<select id="glass-renderer"><option value="b" selected>B · 맞춤 메시 굴절 (기본)</option><option value="a">A · 기존 코드 (진단)</option></select></label>
<p id="door-notice" class="control-note">나무 문 손잡이를 왼쪽으로 밀어 열 수 있어요.</p>
<p id="renderer-notice" class="control-note" role="status">기본 B · 실제 고방 메시 + 맞춤 굴절</p>
<section class="comparison-control" aria-labelledby="comparison-title">
<h3 id="comparison-title">웹 유리 렌더러 비교</h3>
<p class="control-note">A 기존 코드 / 굴절 보정 B · 실제 고방 메시 + 맞춤 굴절. 같은 사진·깊이·시점·확대·거리로 동시에 표시해요. AI 분석은 한 번만 공유해요.</p>
<p class="control-note">화면을 두 번 그려 GPU·메모리 부담이 커져요. 특히 모바일에서는 느려지거나 그래픽 연결이 끊길 수 있어요. 기본 화면은 B만 그려요. A는 별도 진단 모드로 보존돼요.</p>
<button id="comparison-toggle" type="button" aria-pressed="false">비교 켜기 · 추가 렌더 비용</button>
<p id="comparison-state" class="control-note" role="status">고방유리의 Final 또는 No Glass에서 사용할 수 있어요.</p>
<details><summary>비교 조건과 차이</summary><p class="control-note">공통: 사진·128×128 깊이 메시, 격자·굴곡, IOR 1.5, 전면 요철·뒷면 위치, 거리, 카메라와 확대. B는 닫힌 실제 메시의 진입점·법선과 뒷면·옆면을 사용해요. 조명 위치와 방 배경 방향을 맞췄지만 반사 계산은 달라요.</p><p class="control-note">B: Three.js r169 MeshPhysicalMaterial의 맞춤 확장. 실제 유리 표면에서 굴절하고 유리를 나올 때 다시 굴절한 뒤 사진의 128×128 깊이 삼각형과 만나는 곳을 찾아요. 얇은 유리를 임의로 두껍게 만들지 않아요. 표준 재질만 사용한 결과가 아니며, 사진 색은 교차한 위치에서 직접 읽고 반사는 기존 물리 재질을 사용해요. 일부 GPU에서 직사각 조명을 지원하지 않으면 B에 환경광만 사용했다고 표시해요. 전체 장면의 다중 반사·산란을 추적하는 렌더러는 아니에요. A의 깊이 보정·가장자리 완화·광택 0.30과 동일한 결과를 만드는 방식도 아니에요. 다른 유리 무늬나 진단 화면으로 바꾸면 비교가 종료돼요.</p><p class="control-note">CPU 제출 시간은 GPU 시간이 아니에요. 화면 간격은 A+B를 함께 그린 이 브라우저의 값이고, 각 방식 단독 FPS가 아니에요. GPU 메모리 용량은 측정하지 않아요. 시각·속도 우열은 실제 기기에서 확인하세요.</p></details>
</section>
<p id="region-fixed-note" class="control-note">고방유리 · 표면 요철에 따른 굴절 · 색면 처리 없음</p>
<div id="reference-strip" class="reference-strip" hidden role="group" aria-label="레퍼런스 비교 이미지">
<figure aria-labelledby="reference-caption-1">
<img id="reference-image-1" alt="">
@@ -22,93 +248,43 @@
</figure>
<p id="reference-status" role="status" aria-live="polite" hidden>레퍼런스 이미지를 불러오지 못했습니다.</p>
</div>
<section class="stage">
<div id="canvas-wrap"><div id="empty">반려동물 사진 또는 영상을 선택하세요</div></div>
<p id="hint">화면을 드래그해서 시점을 움직여보세요</p>
</section>
</div>
<aside>
<h1>Pet Behind Glass</h1>
<label class="upload">사진 선택<input id="image" type="file" accept="image/jpeg,image/png,image/webp"></label>
<button id="ai-depth" data-testid="ai-depth" disabled>AI 깊이 생성</button>
<p id="depth-provenance" hidden aria-live="polite"></p>
<label>테스트 사진<select id="test-photo">
<option value="sample">기존 샘플 · 흰 강아지 얼굴</option>
<option value="dog1">멍멍이 1 · 잔디 위 흰 포메라니안</option>
<option value="dog2">멍멍이 2 · 실내에 엎드린 검정·갈색 강아지</option>
<option value="dog3">멍멍이 3 · 후드 옷을 입은 강아지 두 마리</option>
<option value="dog4">멍멍이 4 · 벤치 위 갈색 닥스훈트</option>
</select></label>
<button id="sample">선택한 테스트 사진 불러오기</button>
<p id="region-fixed-note" class="control-note">Ref1 기준 · 색면 단순화 1 · 굴절 1 (고정)</p>
<label>Grid Size <output id="gridOut">13</output>
<input id="grid-size" type="range" min="10" max="24" step="1" value="13">
</label>
<small class="control-help">큰 얼굴 12–16 · 작은 얼굴/여러 마리 20–24</small>
<div id="gloss-controls" class="core-optics">
<label>셀 굴곡 (−1 오목 · +1 볼록) <output id="curvatureOut">1.00</output>
<input id="curvature" type="range" min="-1" max="1" step="0.01" value="1">
</label>
<small class="control-help">|굴곡| 0.65–1 권장</small>
<label>광택 강도 <output id="glossOut">0.30</output>
<input id="gloss-strength" type="range" min="0" max="0.4" step="0.01" value="0.30">
</label>
</div>
<label>Camera Offset <output id="cameraOut">0.50</output>
<input id="camera-offset" type="range" min="0" max="0.9" step="0.01" value="0.5">
</label>
<small class="control-help">권장 0.35–0.65 · 시점 이동 범위</small>
<button id="reset">시점 초기화</button>
<button id="gyro">Gyroscope: OFF</button>
<details>
<summary>사진 구도</summary>
<div class="details-content">
<label>사진 확대 <output id="photoZoomOut">1.00</output>
<input id="photo-zoom" type="range" min="1" max="3" step="0.05" value="1">
</label>
<label>사진 가로 위치 <output id="photoPanXOut">0.00</output>
<input id="photo-pan-x" type="range" min="-1" max="1" step="0.01" value="0">
</label>
<label>사진 세로 위치 <output id="photoPanYOut">0.00</output>
<input id="photo-pan-y" type="range" min="-1" max="1" step="0.01" value="0">
</label>
<button id="photo-reset">사진 구도 초기화</button>
</div>
</details>
<details>
<summary>개발 비교</summary>
<div class="details-content">
<label class="upload">동영상 선택<input id="video" type="file" accept="video/mp4,video/webm"></label>
<label class="upload">Depth 이미지 선택<input id="depth-image" type="file" accept="image/png,image/webp"></label>
<label>View Mode<select id="view-mode">
<p class="control-note">AI 깊이: Depth Anything V2 Small · 입력 770</p>
<details id="depth-inspection">
<summary>최대 깊이 영역 확인</summary>
<p class="control-note">모델 최대 1: 분홍 · 8비트 depth=1: 청록 · 모델 depth≥0.99: 노랑</p>
<canvas id="depth-inspection-canvas" aria-label="최대 깊이 영역 · 사진을 클릭해 값 확인"></canvas>
<pre id="depth-inspection-summary">사진과 깊이를 불러오세요.</pre>
<div class="depth-coordinates">
<label for="depth-inspection-x">픽셀 X<input id="depth-inspection-x" type="number" min="0" value="0"></label>
<label for="depth-inspection-y">픽셀 Y<input id="depth-inspection-y" type="number" min="0" value="0"></label>
</div>
<pre id="depth-inspection-point"></pre>
<small class="control-note">1은 사진별 정규화 최댓값이며 실제 거리나 코 검출 결과가 아닙니다. 간격은 깊이맵 기준 장면 단위입니다.</small>
</details>
<label class="upload" for="video">동영상 선택<input id="video" type="file" accept="video/mp4,video/webm"></label>
<label class="upload" for="depth-image">Depth 이미지 선택<input id="depth-image" type="file" accept="image/png,image/webp"></label>
<label for="view-mode">View Mode<select id="view-mode">
<option value="final">Final</option><option value="noglass">No Glass</option><option value="rgb">RGB</option><option value="depth">Depth</option><option value="glass">Glass Only</option>
</select></label>
<label>Glass<select id="effect">
<label for="effect">Glass<select id="effect">
<option value="mosaic">Mosaic</option><option value="reeded">Reeded</option><option value="frosted">Frosted</option>
</select></label>
<label>Mosaic 스타일<select id="mosaic-style">
<option value="0">조각 유리</option><option value="1">촘촘 유리</option>
</select></label>
<label>Depth Scale <output id="depthOut">0.25</output>
<label for="depth-scale">Depth Scale <output id="depthOut">0.25</output>
<input id="depth-scale" type="range" min="0" max="0.4" step="0.01" value="0.25">
</label>
<label>조명 가로 <output id="lightXOut">0.60</output>
<label for="light-x">조명 가로 <output id="lightXOut">0.60</output>
<input id="light-x" type="range" min="-2" max="2" step="0.05" value="0.6">
</label>
<label>조명 세로 <output id="lightYOut">0.80</output>
<label for="light-y">조명 세로 <output id="lightYOut">0.80</output>
<input id="light-y" type="range" min="-2" max="2" step="0.05" value="0.8">
</label>
<label>View sensitivity <output id="sensitivityOut">1.00</output>
<label for="sensitivity">View sensitivity <output id="sensitivityOut">1.00</output>
<input id="sensitivity" type="range" min="0.2" max="2" step="0.05" value="1">
</label>
</div>
</details>
<p id="status" role="status"></p>
</aside>
</main>
</div></details>
<section class="processing-card" aria-label="사진 처리 상태"><div class="processing-copy"><p id="status" role="status" aria-live="polite">사진을 선택하면 AI가 입체감을 준비해요</p><p id="depth-provenance" hidden></p></div><button id="ai-depth" data-testid="ai-depth" class="quiet-button" disabled>AI 입체감 만들기</button></section>
</div>
</div>
</body>
</html>
+105
View File
@@ -0,0 +1,105 @@
Baemin Kkubulim font licensing
Official font source: https://www.woowahan.com/fonts
Official copyright and license page: https://www.woowahan.com/fonts/license
OFL text source: https://openfontlicense.org/documents/OFL.txt
Retrieved 2026-10-06. Font-specific introductory notice and copyright/RFN statement below are copied from Woowa Brothers. Full OFL body is copied from SIL's official text.
The intellectual property rights of Hanna, Hanna Air, Hanna Pro, Jua, Dohyeon, Yeonsung, KirangHaerang, Euljiro, and Euljiro 10 Years Later, Baemin Geullim, Baemin Euljiro OraeOrae, BM Kkubulim fonts are held by Woowa Brothers.
Fonts distributed free of charge by Baedal Minjok (Hanna, Jua, Dohyeon, Yeonsung, KirangHaerang, Hanna Air, Hanna Pro, Euljiro, Euljiro 10 Years Later, Baemin Geullim, Baemin Euljiro OraeOrae, BM kkubulim) can be modified and hanged freely and used by both individual and corporate users for commercial or non-profit purposes.
However, please note that sales of the font file (OTF/TTF) is prohibited.
Images of prints, advertisements (including online advertisements), and other products using free fonts distributed by Baemin (Hanna, Jua, Dohyeon, Yeonsung, KirangHaerang, Hanna Air, Hanna Pro, Euljiro, Euljiro 10 Years Later, Baemin Geullim, Baemin Euljiro OraeOrae, BM Kkubulim) may be used for the company’s data collection and research purposes. If you wish to opt out of such use, please feel free to contact our Customer Service Center (+82-1600-0987 / CS@woowahan.com).
Provided that the full text of the "Intellectual Property Guide and License" below is shown, the fonts may be bundled, redistributed, or sold with other software.
Copyright © 2013, Woowa Brothers Corporation (https://www.woowahan.com), with Reserved Font Name BM HANNA 11yrs old, BM HANNA 11yrs old OTF, BMHANNAAir_otf, BMHANNAAir_ttf, BMHANNAPro_otf, BMHANNAPro_ttf, BM JUA_TTF, BM JUA_OTF, BM DoHyeon, BM DoHyeon OTF, BM YEONSUNG, BM YEONSUNG OTF, BM KIRANGHAERANG, BM KIRANGHAERANG OTF, BMEULJIRO, BMEULJIROTTF, BM EULJITO 10 YEARS LATER, BM EULJITO 10 YEARS LATER TTF, BMEuljirooraeoraeOTF, BMkkubulimTTF, BMkkubulim.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) Kamran Ahmed
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Hugging Face
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright © 2010-2024 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+222
View File
@@ -0,0 +1,222 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
MIT License
Copyright (c) 2020 Daniel Gatis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+182
View File
@@ -0,0 +1,182 @@
{
"name": "u2netp",
"revision": "sha256:309c8469258dda742793dce0ebea8e6dd393174f89934733ecc8b14c76f4ddd8",
"bytes": 4574861,
"sha256": "309c8469258dda742793dce0ebea8e6dd393174f89934733ecc8b14c76f4ddd8",
"md5": "8e83ca70e441ab06c318d82300c84806",
"source": {
"url": "https://github.com/danielgatis/rembg/releases/download/v0.0.0/u2netp.onnx",
"release": "v0.0.0",
"releaseAssetId": 85857953,
"assetUpdatedAt": "2022-11-24T19:22:32Z",
"publisher": "danielgatis",
"rembgRevision": "202e42649a8492a7c49f808de36608a7d1cbbfe3",
"upstreamU2NetRevision": "ac7e1c817ecab7c7dff5ce6b1abba61cd213ff29"
},
"tensorContract": {
"input": [
{
"name": "input.1",
"isTensor": true,
"type": "float32",
"shape": [
1,
3,
320,
320
]
}
],
"outputs": [
{
"name": "1959",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1960",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1961",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1962",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1963",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1964",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
},
{
"name": "1965",
"isTensor": true,
"type": "float32",
"shape": [
1,
1,
320,
320
]
}
],
"selectedOutput": "1959",
"resize": [
320,
320
],
"layout": "NCHW",
"rgbScaling": "divide by max(maxRGB,1e-6)",
"mean": [
0.485,
0.456,
0.406
],
"std": [
0.229,
0.224,
0.225
],
"alphaPostprocess": "finite min/max normalize first output; uint8; restore canonical size"
},
"conversionHistory": {
"status": "NOT FULLY VERIFIED",
"known": {
"1": [
6
],
"2": [
"pytorch"
],
"3": [
"1.9"
],
"8": [
[
[
2,
0,
11
]
]
]
},
"missing": [
"exact original checkpoint hash",
"export script/revision used for this release artifact",
"fully reproducible conversion chain"
]
},
"license": {
"upstreamU2Net": "Apache-2.0",
"rembgCode": "MIT",
"redistributionStatus": "NOT CLEARED FOR INTEGRATION: upstream license and publisher source verified, exact conversion chain remains unverified",
"licenseFiles": [
"u2net-LICENSE",
"rembg-LICENSE.txt"
]
},
"qualityGate": "FAIL",
"browserRuntimeGate": "NOT RUN",
"localCpuEvidence": "u2netp-runtime-results.json",
"report": "model-gate.md",
"deploymentScope": {
"mode": "owner-private experiment",
"requiresExplicitMaskReviewForEveryPhoto": true,
"strictQualityGate": "failed",
"browserMobileGate": "not run",
"userApprovalDate": "2026-10-07",
"limitations": [
"general foreground model, not dog-only semantic segmentation",
"body regions may disappear and background may remain",
"not a person-photo blocking mechanism"
],
"downloadBytesMeaning": "Uncompressed model payload bytes, not measured network transfer bytes"
}
}
BIN
View File
Binary file not shown.