Files
tinyusdz/web/demo/main.js
2025-08-26 13:13:38 +09:00

287 lines
7.8 KiB
JavaScript

import * as THREE from 'three';
import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
import { GUI } from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.module.js';
import { TinyUSDZLoader } from 'tinyusdz/TinyUSDZLoader.js'
import { TinyUSDZLoaderUtils } from 'tinyusdz/TinyUSDZLoaderUtils.js'
function checkMemory64Support() {
try {
// Try creating a 64-bit memory
const memory = new WebAssembly.Memory({
initial: 1,
maximum: 65536,
index: 'i64' // This specifies 64-bit indexing
});
return true;
} catch (e) {
return false;
}
}
// Loading bar elements
const loadingContainer = document.createElement('div');
loadingContainer.id = 'loading-container';
loadingContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
font-family: Arial, sans-serif;
color: white;
`;
const loadingText = document.createElement('div');
loadingText.id = 'loading-text';
loadingText.textContent = 'Loading...';
loadingText.style.cssText = `
font-size: 24px;
margin-bottom: 20px;
`;
const progressBarContainer = document.createElement('div');
progressBarContainer.style.cssText = `
width: 300px;
height: 20px;
background: #333;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
`;
const progressBar = document.createElement('div');
progressBar.id = 'progress-bar';
progressBar.style.cssText = `
width: 0%;
height: 100%;
background: linear-gradient(90deg, #4CAF50, #8BC34A);
border-radius: 10px;
transition: width 0.3s ease;
`;
const progressText = document.createElement('div');
progressText.id = 'progress-text';
progressText.textContent = '0%';
progressText.style.cssText = `
font-size: 14px;
color: #ccc;
`;
progressBarContainer.appendChild(progressBar);
loadingContainer.appendChild(loadingText);
loadingContainer.appendChild(progressBarContainer);
loadingContainer.appendChild(progressText);
document.body.appendChild(loadingContainer);
// Function to update loading progress
function updateLoadingProgress(progress, message = 'Loading...') {
loadingText.textContent = message;
progressBar.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}%`;
}
// Function to hide loading screen
function hideLoadingScreen() {
loadingContainer.style.display = 'none';
}
const gui = new GUI();
let ui_state = {}
ui_state['rot_scale'] = 1.0;
ui_state['defaultMtl'] = TinyUSDZLoaderUtils.createDefaultMaterial();
ui_state['envMapIntensity'] = 3.14; // pi is good for pisaHDR;
ui_state['ambient'] = 0.4;
let ambientLight = new THREE.AmbientLight(0x404040, ui_state['ambient']);
ui_state['camera_z'] = 3.14; // TODO: Compute best fit from scene's bbox.
ui_state['needsMtlUpdate'] = false;
ui_state['renderer'] = null;
// Create a parameters object
const params = {
envMapIntensity: ui_state['envMapIntensity'],
rotationSpeed: ui_state['rot_scale'],
camera_z: ui_state['camera_z'],
take_screenshot: takeScreenshot
};
// Add controls
gui.add(params, 'envMapIntensity', 0, 20, 0.1).name('envMapIntensity').onChange((value) => {
ui_state['envMapIntensity'] = value;
ui_state['needsMtlUpdate'] = true;
});
gui.add(params, 'camera_z', 0, 20).name('Camera Z').onChange((value) => {
ui_state['camera_z'] = value;
});
gui.add(params, 'rotationSpeed', 0, 10).name('Rotation Speed').onChange((value) => {
ui_state['rot_scale'] = value;
});
gui.add(params, 'take_screenshot').name('Take Screenshot');
function takeScreenshot() {
const renderer = ui_state['renderer'];
const quality = 0.92; // JPEG quality, if you want to use JPEG format
const img = renderer.domElement.toDataURL('image/jpeg', quality)
console.log('Screenshot taken:', img);
return img;
}
async function loadScenes() {
updateLoadingProgress(20, 'Initializing TinyUSDZLoader...');
// Create loader with optional memory limit
// Default: 2GB for WASM32, 8GB for WASM64
// const loader = new TinyUSDZLoader(null, { maxMemoryLimitMB: 512 }); // Set 512MB limit
const loader = new TinyUSDZLoader();
// it is recommended to call init() before loadAsync()
// (wait loading/compiling wasm module in the early stage))
//await loader.init();
const useMemory64 = checkMemory64Support();
console.log('64-bit memory support:', useMemory64);
await loader.init({useMemory64});
// You can set memory limit for USD loading.
// The limit is only effective to USD loading.
// No limit for asset data(e.g. textures) and Three.js data, etc.
loader.setMaxMemoryLimitMB(250);
// Use zstd compressed tinyusdz.wasm to save the bandwidth.
//await loader.init({useZstdCompressedWasm: true});
const suzanne_filename = "./assets/suzanne-pbr.usda";
const texcat_filename = "./assets/texture-cat-plane.usdz";
const cookie_filename = "./assets/UsdCookie.usdz";
//const usd_filename = "./assets/suzanne-pbr.usda";
const usd_filename = "./assets/suzanne-subd-lv6.usdc";
var threeScenes = []
const usd_scenes = await Promise.all([
//loader.loadAsync(texcat_filename),
loader.loadAsync(usd_filename),
//loader.loadAsync(suzanne_filename),
]);
hideLoadingScreen();
const defaultMtl = ui_state['defaultMtl'];
const options = {
overrideMaterial: false, // override USD material with defaultMtl(default 'false')
envMap: defaultMtl.envMap, // reuse envmap from defaultMtl
envMapIntensity: ui_state['envMapIntensity'], // default envmap intensity
}
var offset = -(usd_scenes.length - 1) * 1.5;
for (const usd_scene of usd_scenes) {
const usdRootNode = usd_scene.getDefaultRootNode();
const threeNode = TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options);
if (usd_scene.getURI().includes('UsdCookie')) {
// Add exra scaling
threeNode.scale.x *= 2.5;
threeNode.scale.y *= 2.5;
threeNode.scale.z *= 2.5;
}
threeNode.position.x += offset;
offset += 3.0;
threeScenes.push(threeNode);
}
return threeScenes;
}
const scene = new THREE.Scene();
async function initScene() {
const envmap = await new HDRCubeTextureLoader()
.setPath('assets/textures/cube/pisaHDR/')
.loadAsync(['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'])
scene.background = envmap;
scene.environment = envmap;
// Assign envmap to material
// Otherwise some material parameters like clarcoat will not work properly.
ui_state['defaultMtl'].envMap = envmap;
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = ui_state['camera_z'];
const renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true, // for screenshot
alpha: true, // Enable transparency
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
ui_state['renderer'] = renderer; // Store renderer in ui_state
document.body.appendChild(renderer.domElement);
const rootNodes = await loadScenes();
for (const rootNode of rootNodes) {
scene.add(rootNode);
}
function animate() {
for (const rootNode of rootNodes) {
rootNode.rotation.y += 0.01 * ui_state['rot_scale'];
rootNode.rotation.x += 0.02 * ui_state['rot_scale'];
}
camera.position.z = ui_state['camera_z'];
if (ui_state['needsMtlUpdate']) {
// TODO: Cache materials in the scene.
scene.traverse((object) => {
if (object.material) {
if (Object.prototype.hasOwnProperty.call(object.material, 'envMapIntensity')) {
object.material.envMapIntensity = ui_state['envMapIntensity'];
object.material.needsUpdate = true;
}
}
});
ui_state['needsMtlUpdate'] = false;
}
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
}
initScene();