Spaces:
Running
Running
Download src/studio.ts from fffiloni/openusd-explorer: direct link, hf CLI and curl.
- Browser
- Download file 3.24 kB
-
https://huggingface.co/spaces/fffiloni/openusd-explorer/resolve/main/src/studio.ts
- Command line
-
hf download hf://spaces/fffiloni/openusd-explorer/src/studio.ts
-
curl -L -o studio.ts https://huggingface.co/spaces/fffiloni/openusd-explorer/resolve/main/src/studio.ts
3.24 kB
| import * as THREE from 'three'; | |
| import {RoomEnvironment} from 'three/addons/environments/RoomEnvironment.js'; | |
| import {bodyMatrices,controllableJoints,homeProgress} from './vendor/kinematics.js'; | |
| export const STUDIO_DIRECTION=[1,0.65,1.4] as const; | |
| export type StudioLighting={exposure?:number;environment?:number;hemisphere?:number;key?:number}; | |
| /** Shared studio, with optional batch lighting tuned for thumbnails on a pale canvas. */ | |
| export function createStudio(renderer:THREE.WebGLRenderer,lighting:StudioLighting={}){ | |
| renderer.setClearColor(0,0); | |
| renderer.outputColorSpace=THREE.SRGBColorSpace; | |
| renderer.toneMapping=THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure=lighting.exposure??1.35; | |
| const scene=new THREE.Scene(); | |
| const pmrem=new THREE.PMREMGenerator(renderer),room=new RoomEnvironment(); | |
| const environment=pmrem.fromScene(room,0.04); | |
| scene.environment=environment.texture; | |
| scene.environmentIntensity=lighting.environment??1; | |
| room.dispose();pmrem.dispose(); | |
| scene.add(new THREE.HemisphereLight(0xffffff,0xdce0ef,lighting.hemisphere??2)); | |
| const light=new THREE.DirectionalLight(0xffffff,lighting.key??3); | |
| light.position.set(3,5,4);scene.add(light); | |
| return {scene,dispose:()=>environment.dispose()}; | |
| } | |
| export function setHomePose(model:any):Record<string,number>{ | |
| const values:Record<string,number>={}; | |
| for(const joint of controllableJoints(model.articulation))values[joint.id]=homeProgress(joint); | |
| if(model.articulation)for(const [id,matrix] of Object.entries(bodyMatrices(model.articulation,values))){ | |
| const body=model.bodies.get(id); | |
| if(body){body.matrixAutoUpdate=false;body.matrix.set(...matrix as number[]);body.matrixWorldNeedsUpdate=true;} | |
| } | |
| model.scene.updateMatrixWorld(true); | |
| return values; | |
| } | |
| export function objectBounds(object:THREE.Object3D){ | |
| object.updateMatrixWorld(true); | |
| const box=new THREE.Box3().setFromObject(object); | |
| const size=box.getSize(new THREE.Vector3()),center=box.getCenter(new THREE.Vector3()); | |
| if(box.isEmpty()||![...size,...center].every(Number.isFinite))throw new Error('Empty or non-finite model bounds.'); | |
| return {box,center,radius:Math.max(size.length()/2,0.01)}; | |
| } | |
| /** Fits all eight world bounds corners to an orthographic square, including thin objects. */ | |
| export function thumbnailCamera(object:THREE.Object3D,padding=1.12){ | |
| const {box,center,radius}=objectBounds(object); | |
| const camera=new THREE.OrthographicCamera(-1,1,1,-1,radius/1000,radius*20); | |
| camera.position.copy(center).add(new THREE.Vector3(...STUDIO_DIRECTION).normalize().multiplyScalar(radius*4)); | |
| camera.lookAt(center);camera.updateMatrixWorld(true); | |
| const projected=new THREE.Box3(); | |
| for(const x of [box.min.x,box.max.x])for(const y of [box.min.y,box.max.y])for(const z of [box.min.z,box.max.z]){ | |
| projected.expandByPoint(new THREE.Vector3(x,y,z).applyMatrix4(camera.matrixWorldInverse)); | |
| } | |
| const middle=projected.getCenter(new THREE.Vector3()); | |
| const extent=Math.max(projected.max.x-projected.min.x,projected.max.y-projected.min.y,radius/100)*padding/2; | |
| camera.left=middle.x-extent;camera.right=middle.x+extent; | |
| camera.bottom=middle.y-extent;camera.top=middle.y+extent; | |
| camera.updateProjectionMatrix(); | |
| return camera; | |
| } | |