Spaces:
Running
Running
Viewer: start camera facing the soup can's label
Browse files
viewer.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as THREE from 'three';
|
|
| 2 |
import { OrbitControls } from './vendor/OrbitControls.js';
|
| 3 |
const cache=new Map();
|
| 4 |
export async function loadGeometry(asset){if(cache.has(asset.id))return cache.get(asset.id);const r=await fetch(asset.previewUrl);if(!r.ok)throw new Error('Preview not ready');const bytes=await r.arrayBuffer();const data=JSON.parse(new Uint8Array(bytes)[0]===31?await new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).text():new TextDecoder().decode(bytes));data.assetId=asset.id;cache.set(asset.id,data);if(cache.size>12)cache.delete(cache.keys().next().value);return data}
|
| 5 |
-
function makeScene(){const scene=new THREE.Scene();scene.add(new THREE.HemisphereLight(0xffffff,0x7c8490,1.7));for(const [x,y,z,power,shadow] of [[
|
| 6 |
const textureLoader=new THREE.TextureLoader(),textures=new Map();function texture(url){const key=url;if(!textures.has(key)){const t=textureLoader.load(url);t.colorSpace=THREE.SRGBColorSpace;t.anisotropy=4;textures.set(key,t)}return textures.get(key)}let showHulls=false,showCom=false;
|
| 7 |
function addOverlays(group,data,extent){const hulls=new THREE.Group(),com=new THREE.Group();
|
| 8 |
for(const h of data.collisions||[]){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(h.positions,3));g.setIndex(h.indices);g.computeVertexNormals();
|
|
@@ -18,7 +18,7 @@ group.traverse(m=>{if(m.isMesh&&m.userData.visual){m.material.transparent=xray;m
|
|
| 18 |
function makeModel(data){const group=new THREE.Group();for(const p of data.meshes){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(p.positions,3));g.setIndex(p.indices);g.computeVertexNormals();const color=new THREE.Color().setRGB(...p.color);const opts={color,roughness:.55,metalness:.05,side:THREE.DoubleSide};if(p.uvs&&p.texture!=null){g.setAttribute('uv',new THREE.Float32BufferAttribute(p.uvs,2));opts.color=new THREE.Color(1,1,1);opts.map=texture(data.textureUrls[p.texture])}const vm=new THREE.Mesh(g,new THREE.MeshStandardMaterial(opts));vm.userData.visual=true;vm.castShadow=true;vm.receiveShadow=true;group.add(vm)}const box=new THREE.Box3().setFromObject(group),size=box.getSize(new THREE.Vector3()),center=box.getCenter(new THREE.Vector3());const scale=2/Math.max(size.x,size.y,size.z,.0001);group.position.set(-center.x*scale,-box.min.y*scale,-center.z*scale);group.scale.setScalar(scale);addOverlays(group,data,Math.max(size.x,size.y,size.z));return {group,height:size.y*scale}}
|
| 19 |
function dispose(group){group?.traverse(m=>{m.geometry?.dispose();if(m.material){m.material.map=null;m.material.dispose()}})}
|
| 20 |
const canvas=document.querySelector('#canvas'),host=document.querySelector('#viewport');const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:true});renderer.setPixelRatio(Math.min(devicePixelRatio,2));renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.25;renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFShadowMap;const scene=makeScene(),camera=new THREE.PerspectiveCamera(38,1,.01,100),controls=new OrbitControls(camera,canvas);controls.enableDamping=true;controls.minDistance=.7;controls.maxDistance=10;controls.maxPolarAngle=Math.PI*.54;const grid=new THREE.GridHelper(10,50,0xc6cdd6,0xe1e5ea);grid.material.transparent=true;grid.material.opacity=.4;grid.position.y=-.005;scene.add(grid);const floor=new THREE.Mesh(new THREE.PlaneGeometry(20,20),new THREE.ShadowMaterial({opacity:.34}));floor.rotation.x=-Math.PI/2;floor.receiveShadow=true;scene.add(floor);let current=null,height=2,wire=false,request=0;
|
| 21 |
-
function reset(){camera.position.set(0.
|
| 22 |
export function clear(){++request;if(current){scene.remove(current);dispose(current);current=null}renderer.clear()}
|
| 23 |
export async function show(asset){clear();const req=request;const data=await loadGeometry(asset);if(req!==request)return;const m=makeModel(data);current=m.group;height=m.height;current.traverse(o=>{if(o.isMesh&&o.userData.visual)o.material.wireframe=wire});scene.add(current);reset();resize();renderer.render(scene,camera)}
|
| 24 |
let thumbRenderer;const thumbs=new Map();export async function thumbnail(asset){if(thumbs.has(asset.id))return thumbs.get(asset.id);thumbRenderer??=new THREE.WebGLRenderer({alpha:true,antialias:true,preserveDrawingBuffer:true});thumbRenderer.setSize(320,240);thumbRenderer.toneMapping=THREE.ACESFilmicToneMapping;const data=await loadGeometry(asset),s=makeScene(),m=makeModel(data);s.add(m.group);const c=new THREE.PerspectiveCamera(38,4/3,.01,100);c.position.set(2.8,2.2,3.4);c.lookAt(0,m.height*.45,0);thumbRenderer.render(s,c);const url=thumbRenderer.domElement.toDataURL('image/png');dispose(m.group);thumbs.set(asset.id,url);return url}
|
|
|
|
| 2 |
import { OrbitControls } from './vendor/OrbitControls.js';
|
| 3 |
const cache=new Map();
|
| 4 |
export async function loadGeometry(asset){if(cache.has(asset.id))return cache.get(asset.id);const r=await fetch(asset.previewUrl);if(!r.ok)throw new Error('Preview not ready');const bytes=await r.arrayBuffer();const data=JSON.parse(new Uint8Array(bytes)[0]===31?await new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).text():new TextDecoder().decode(bytes));data.assetId=asset.id;cache.set(asset.id,data);if(cache.size>12)cache.delete(cache.keys().next().value);return data}
|
| 5 |
+
function makeScene(){const scene=new THREE.Scene();scene.add(new THREE.HemisphereLight(0xffffff,0x7c8490,1.7));for(const [x,y,z,power,shadow] of [[3.74,5.28,-3.73,3.2,true],[1.63,3,3.22,1.4,false]]){const l=new THREE.DirectionalLight(0xffffff,power);l.position.set(x,y,z);if(shadow){l.castShadow=true;l.shadow.mapSize.set(4096,4096);const c=l.shadow.camera;c.left=-4.5;c.right=4.5;c.top=4.5;c.bottom=-4.5;c.near=.5;c.far=16;l.shadow.bias=-.0004;l.shadow.normalBias=.02;l.shadow.radius=3.5;l.shadow.blurSamples=16}scene.add(l)}return scene}
|
| 6 |
const textureLoader=new THREE.TextureLoader(),textures=new Map();function texture(url){const key=url;if(!textures.has(key)){const t=textureLoader.load(url);t.colorSpace=THREE.SRGBColorSpace;t.anisotropy=4;textures.set(key,t)}return textures.get(key)}let showHulls=false,showCom=false;
|
| 7 |
function addOverlays(group,data,extent){const hulls=new THREE.Group(),com=new THREE.Group();
|
| 8 |
for(const h of data.collisions||[]){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(h.positions,3));g.setIndex(h.indices);g.computeVertexNormals();
|
|
|
|
| 18 |
function makeModel(data){const group=new THREE.Group();for(const p of data.meshes){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(p.positions,3));g.setIndex(p.indices);g.computeVertexNormals();const color=new THREE.Color().setRGB(...p.color);const opts={color,roughness:.55,metalness:.05,side:THREE.DoubleSide};if(p.uvs&&p.texture!=null){g.setAttribute('uv',new THREE.Float32BufferAttribute(p.uvs,2));opts.color=new THREE.Color(1,1,1);opts.map=texture(data.textureUrls[p.texture])}const vm=new THREE.Mesh(g,new THREE.MeshStandardMaterial(opts));vm.userData.visual=true;vm.castShadow=true;vm.receiveShadow=true;group.add(vm)}const box=new THREE.Box3().setFromObject(group),size=box.getSize(new THREE.Vector3()),center=box.getCenter(new THREE.Vector3());const scale=2/Math.max(size.x,size.y,size.z,.0001);group.position.set(-center.x*scale,-box.min.y*scale,-center.z*scale);group.scale.setScalar(scale);addOverlays(group,data,Math.max(size.x,size.y,size.z));return {group,height:size.y*scale}}
|
| 19 |
function dispose(group){group?.traverse(m=>{m.geometry?.dispose();if(m.material){m.material.map=null;m.material.dispose()}})}
|
| 20 |
const canvas=document.querySelector('#canvas'),host=document.querySelector('#viewport');const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:true});renderer.setPixelRatio(Math.min(devicePixelRatio,2));renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.25;renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFShadowMap;const scene=makeScene(),camera=new THREE.PerspectiveCamera(38,1,.01,100),controls=new OrbitControls(camera,canvas);controls.enableDamping=true;controls.minDistance=.7;controls.maxDistance=10;controls.maxPolarAngle=Math.PI*.54;const grid=new THREE.GridHelper(10,50,0xc6cdd6,0xe1e5ea);grid.material.transparent=true;grid.material.opacity=.4;grid.position.y=-.005;scene.add(grid);const floor=new THREE.Mesh(new THREE.PlaneGeometry(20,20),new THREE.ShadowMaterial({opacity:.34}));floor.rotation.x=-Math.PI/2;floor.receiveShadow=true;scene.add(floor);let current=null,height=2,wire=false,request=0;
|
| 21 |
+
function reset(){camera.position.set(-0.76,2.2,-4.33);controls.target.set(0,height*.45,0);controls.update()}function resize(){const w=host.clientWidth,h=host.clientHeight;if(w&&h){renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix()}}new ResizeObserver(resize).observe(host);document.querySelector('#reset-view').onclick=reset;document.querySelector('#wireframe').onclick=()=>{wire=!wire;current?.traverse(m=>{if(m.isMesh&&m.userData.visual)m.material.wireframe=wire});document.querySelector('#wireframe').setAttribute('aria-pressed',String(wire));document.querySelector('#wireframe').classList.toggle('active',wire)};renderer.setAnimationLoop(()=>{if(current&&!document.hidden&&!document.querySelector('#gallery').hidden){controls.update();renderer.render(scene,camera)}});
|
| 22 |
export function clear(){++request;if(current){scene.remove(current);dispose(current);current=null}renderer.clear()}
|
| 23 |
export async function show(asset){clear();const req=request;const data=await loadGeometry(asset);if(req!==request)return;const m=makeModel(data);current=m.group;height=m.height;current.traverse(o=>{if(o.isMesh&&o.userData.visual)o.material.wireframe=wire});scene.add(current);reset();resize();renderer.render(scene,camera)}
|
| 24 |
let thumbRenderer;const thumbs=new Map();export async function thumbnail(asset){if(thumbs.has(asset.id))return thumbs.get(asset.id);thumbRenderer??=new THREE.WebGLRenderer({alpha:true,antialias:true,preserveDrawingBuffer:true});thumbRenderer.setSize(320,240);thumbRenderer.toneMapping=THREE.ACESFilmicToneMapping;const data=await loadGeometry(asset),s=makeScene(),m=makeModel(data);s.add(m.group);const c=new THREE.PerspectiveCamera(38,4/3,.01,100);c.position.set(2.8,2.2,3.4);c.lookAt(0,m.height*.45,0);thumbRenderer.render(s,c);const url=thumbRenderer.domElement.toDataURL('image/png');dispose(m.group);thumbs.set(asset.id,url);return url}
|