viewer_sgos / viewer.js
MikaFil's picture
Update viewer.js
30a46e8 verified
raw
history blame
7.53 kB
// viewer.js
// ==============================
let pc;
export let app = null;
let cameraEntity = null;
let modelEntity = null;
let viewerInitialized = false;
let resizeObserver = null;
let chosenCameraX, chosenCameraY, chosenCameraZ;
let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
let plyUrl, glbUrl;
export async function initializeViewer(config, instanceId) {
if (viewerInitialized) return;
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
plyUrl = config.ply_url;
glbUrl = config.glb_url;
minZoom = parseFloat(config.minZoom || "1");
maxZoom = parseFloat(config.maxZoom || "20");
minAngle = parseFloat(config.minAngle || "-45");
maxAngle = parseFloat(config.maxAngle || "90");
minAzimuth = parseFloat(config.minAzimuth || "-360");
maxAzimuth = parseFloat(config.maxAzimuth || "360");
minPivotY = parseFloat(config.minPivotY || "0");
minY = parseFloat(config.minY || "0");
modelX = parseFloat(config.modelX || "0");
modelY = parseFloat(config.modelY || "0");
modelZ = parseFloat(config.modelZ || "0");
modelScale = parseFloat(config.modelScale || "1");
modelRotationX = parseFloat(config.modelRotationX || "0");
modelRotationY = parseFloat(config.modelRotationY || "0");
modelRotationZ = parseFloat(config.modelRotationZ || "0");
const cameraX = parseFloat(config.cameraX || "0");
const cameraY = parseFloat(config.cameraY || "2");
const cameraZ = parseFloat(config.cameraZ || "5");
const cameraXPhone = parseFloat(config.cameraXPhone || cameraX);
const cameraYPhone = parseFloat(config.cameraYPhone || cameraY);
const cameraZPhone = parseFloat(config.cameraZPhone || (cameraZ * 1.5));
chosenCameraX = isMobile ? cameraXPhone : cameraX;
chosenCameraY = isMobile ? cameraYPhone : cameraY;
chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
const canvasId = 'canvas-' + instanceId;
const progressDialog = document.getElementById('progress-dialog-' + instanceId);
const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
const viewerContainer = document.getElementById('viewer-container-' + instanceId);
let oldCanvas = document.getElementById(canvasId);
if (oldCanvas) oldCanvas.remove();
const canvas = document.createElement('canvas');
canvas.id = canvasId;
canvas.className = 'ply-canvas';
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.touchAction = "none";
canvas.setAttribute('tabindex', '0');
viewerContainer.insertBefore(canvas, progressDialog);
if (!pc) {
pc = await import("https://esm.run/playcanvas");
window.pc = pc;
}
const device = await pc.createGraphicsDevice(canvas, {
deviceTypes: ["webgl2"],
glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
antialias: false
});
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
const opts = new pc.AppOptions();
opts.graphicsDevice = device;
opts.mouse = new pc.Mouse(document.body);
opts.touch = new pc.TouchDevice(document.body);
opts.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ScriptComponentSystem,
pc.GSplatComponentSystem,
pc.CollisionComponentSystem,
pc.RigidbodyComponentSystem
];
opts.resourceHandlers = [
pc.TextureHandler,
pc.ContainerHandler,
pc.ScriptHandler,
pc.GSplatHandler
];
app = new pc.Application(canvas, opts);
app.setCanvasFillMode(pc.FILLMODE_NONE);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
});
});
resizeObserver.observe(viewerContainer);
window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
app.on('destroy', () => resizeObserver.disconnect());
if (isIOS) {
const originalRequest = pc.Http.request;
pc.Http.request = function(options) {
if (options && options.url.endsWith('.ply')) options.responseType = 'arraybuffer';
return originalRequest.call(this, options);
};
}
const assets = {
model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
hdr: new pc.Asset('hdr', 'texture', {
url: "https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png"
}, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
};
const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
loader.load(async () => {
app.start();
app.scene.envAtlas = assets.hdr.resource;
modelEntity = new pc.Entity('model');
modelEntity.addComponent('gsplat', { asset: assets.model });
modelEntity.setLocalPosition(modelX, modelY, modelZ);
modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
app.root.addChild(modelEntity);
if (assets.galerie && assets.galerie.resource) {
const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
app.root.addChild(galleryEntity);
}
cameraEntity = new pc.Entity('camera');
cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.2, 0.2, 0.2, 1) });
cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
cameraEntity.lookAt(modelEntity.getPosition());
cameraEntity.addComponent('script');
cameraEntity.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2,
focusEntity: modelEntity,
distanceMax: maxZoom,
distanceMin: minZoom,
pitchAngleMax: maxAngle,
pitchAngleMin: minAngle,
yawAngleMax: maxAzimuth,
yawAngleMin: minAzimuth,
minPivotY: minPivotY,
frameOnStart: false
}
});
cameraEntity.script.create('orbitCameraInputMouse', { attributes: { orbitSensitivity: isMobile ? 0.6 : 0.3 } });
cameraEntity.script.create('orbitCameraInputTouch', { attributes: { orbitSensitivity: 0.6 } });
app.root.addChild(cameraEntity);
app.once('update', () => resetViewerCamera());
app.on('update', () => {
const pos = cameraEntity.getPosition();
if (pos.y < minY) cameraEntity.setPosition(pos.x, minY, pos.z);
});
try {
const tooltipsModule = await import('./tooltips.js');
tooltipsModule.initializeTooltips({
app, cameraEntity, modelEntity,
tooltipsUrl: config.tooltips_url,
defaultVisible: !!config.showTooltipsDefault,
moveDuration: config.tooltipMoveDuration || 0.6
});
} catch {}
progressDialog.style.display = 'none';
viewerInitialized = true;
});
}
export function resetViewerCamera() {
if (!cameraEntity || !modelEntity || !app) return;
cameraEntity.script.orbitCamera.reset();
}
export function cleanupViewer() {
if (app) app.destroy();
app = null;
cameraEntity = null;
modelEntity = null;
viewerInitialized = false;
if (resizeObserver) resizeObserver.disconnect();
resizeObserver = null;
}