Spaces:
Running
Running
Update viewer.js
Browse files
viewer.js
CHANGED
|
@@ -11,7 +11,7 @@ let resizeObserver = null;
|
|
| 11 |
let chosenCameraX, chosenCameraY, chosenCameraZ;
|
| 12 |
let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
|
| 13 |
let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
|
| 14 |
-
let
|
| 15 |
|
| 16 |
export async function initializeViewer(config, instanceId) {
|
| 17 |
if (viewerInitialized) return;
|
|
@@ -19,8 +19,12 @@ export async function initializeViewer(config, instanceId) {
|
|
| 19 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
| 20 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
minZoom = parseFloat(config.minZoom || "1");
|
| 25 |
maxZoom = parseFloat(config.maxZoom || "20");
|
| 26 |
minAngle = parseFloat(config.minAngle || "-45");
|
|
@@ -73,9 +77,8 @@ export async function initializeViewer(config, instanceId) {
|
|
| 73 |
canvas.addEventListener('dblclick', e => e.preventDefault());
|
| 74 |
canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
|
| 75 |
|
| 76 |
-
// --- The following line attaches mouse wheel suppression to canvas only ---
|
| 77 |
canvas.addEventListener('wheel', (e) => {
|
| 78 |
-
e.preventDefault();
|
| 79 |
}, { passive: false });
|
| 80 |
|
| 81 |
progressDialog.style.display = 'block';
|
|
@@ -85,7 +88,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 85 |
window.pc = pc;
|
| 86 |
}
|
| 87 |
|
| 88 |
-
// Create
|
| 89 |
const device = await pc.createGraphicsDevice(canvas, {
|
| 90 |
deviceTypes: ["webgl2"],
|
| 91 |
glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
|
|
@@ -96,7 +99,6 @@ export async function initializeViewer(config, instanceId) {
|
|
| 96 |
|
| 97 |
const opts = new pc.AppOptions();
|
| 98 |
opts.graphicsDevice = device;
|
| 99 |
-
// Attach input only to canvas
|
| 100 |
opts.mouse = new pc.Mouse(canvas);
|
| 101 |
opts.touch = new pc.TouchDevice(canvas);
|
| 102 |
opts.componentSystems = [
|
|
@@ -104,15 +106,13 @@ export async function initializeViewer(config, instanceId) {
|
|
| 104 |
pc.CameraComponentSystem,
|
| 105 |
pc.LightComponentSystem,
|
| 106 |
pc.ScriptComponentSystem,
|
| 107 |
-
pc.GSplatComponentSystem,
|
| 108 |
pc.CollisionComponentSystem,
|
| 109 |
pc.RigidbodyComponentSystem
|
| 110 |
];
|
| 111 |
opts.resourceHandlers = [
|
| 112 |
pc.TextureHandler,
|
| 113 |
pc.ContainerHandler,
|
| 114 |
-
pc.ScriptHandler
|
| 115 |
-
pc.GSplatHandler
|
| 116 |
];
|
| 117 |
|
| 118 |
app = new pc.Application(canvas, opts);
|
|
@@ -129,41 +129,71 @@ export async function initializeViewer(config, instanceId) {
|
|
| 129 |
window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
|
| 130 |
app.on('destroy', () => resizeObserver.disconnect());
|
| 131 |
|
| 132 |
-
//
|
| 133 |
-
const
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
};
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
app.start();
|
| 143 |
progressDialog.style.display = 'none';
|
| 144 |
|
| 145 |
-
// Add
|
| 146 |
modelEntity = new pc.Entity('model');
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
modelEntity.setLocalPosition(modelX, modelY, modelZ);
|
| 149 |
modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
| 150 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 151 |
app.root.addChild(modelEntity);
|
| 152 |
|
| 153 |
-
//
|
| 154 |
-
if (assets.glb && assets.glb.resource && assets.glb.resource.instantiateRenderEntity) {
|
| 155 |
-
const glbEntity = assets.glb.resource.instantiateRenderEntity();
|
| 156 |
-
app.root.addChild(glbEntity);
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
cameraEntity = new pc.Entity('camera');
|
| 160 |
-
//
|
| 161 |
-
cameraEntity.addComponent('camera', { clearColor: new pc.Color(1, 1, 1, 1) }); // White RGBA
|
| 162 |
cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 163 |
cameraEntity.lookAt(modelEntity.getPosition());
|
| 164 |
cameraEntity.addComponent('script');
|
| 165 |
|
| 166 |
-
// === MAIN FIX: Pass ALL relevant attributes including minY ===
|
| 167 |
cameraEntity.script.create('orbitCamera', {
|
| 168 |
attributes: {
|
| 169 |
focusEntity: modelEntity,
|
|
@@ -183,10 +213,9 @@ export async function initializeViewer(config, instanceId) {
|
|
| 183 |
app.root.addChild(cameraEntity);
|
| 184 |
|
| 185 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 186 |
-
|
| 187 |
app.once('update', () => resetViewerCamera());
|
| 188 |
|
| 189 |
-
//
|
| 190 |
try {
|
| 191 |
if (config.tooltips_url) {
|
| 192 |
import('./tooltips.js').then(tooltipsModule => {
|
|
@@ -198,18 +227,16 @@ export async function initializeViewer(config, instanceId) {
|
|
| 198 |
defaultVisible: !!config.showTooltipsDefault,
|
| 199 |
moveDuration: config.tooltipMoveDuration || 0.6
|
| 200 |
});
|
| 201 |
-
}).catch(e => {
|
| 202 |
-
// Tooltips optional: fail silently if missing
|
| 203 |
-
});
|
| 204 |
}
|
| 205 |
-
} catch (e) {
|
| 206 |
-
// Tooltips optional, fail silently
|
| 207 |
-
}
|
| 208 |
|
| 209 |
viewerInitialized = true;
|
| 210 |
});
|
|
|
|
| 211 |
}
|
| 212 |
|
|
|
|
| 213 |
export function resetViewerCamera() {
|
| 214 |
try {
|
| 215 |
if (!cameraEntity || !modelEntity || !app) return;
|
|
@@ -251,7 +278,5 @@ export function resetViewerCamera() {
|
|
| 251 |
if (orbitCam._updatePosition) orbitCam._updatePosition();
|
| 252 |
|
| 253 |
tempEnt.destroy();
|
| 254 |
-
} catch (e) {
|
| 255 |
-
// Silent fail
|
| 256 |
-
}
|
| 257 |
}
|
|
|
|
| 11 |
let chosenCameraX, chosenCameraY, chosenCameraZ;
|
| 12 |
let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
|
| 13 |
let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
|
| 14 |
+
let sogsJsonUrl;
|
| 15 |
|
| 16 |
export async function initializeViewer(config, instanceId) {
|
| 17 |
if (viewerInitialized) return;
|
|
|
|
| 19 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
| 20 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
| 21 |
|
| 22 |
+
// SOGS usage: we expect config.sogs_json_url
|
| 23 |
+
sogsJsonUrl = config.sogs_json_url;
|
| 24 |
+
if (!sogsJsonUrl) {
|
| 25 |
+
throw new Error('Missing config.sogs_json_url for optimized SOGS/webp loading.');
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
minZoom = parseFloat(config.minZoom || "1");
|
| 29 |
maxZoom = parseFloat(config.maxZoom || "20");
|
| 30 |
minAngle = parseFloat(config.minAngle || "-45");
|
|
|
|
| 77 |
canvas.addEventListener('dblclick', e => e.preventDefault());
|
| 78 |
canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
|
| 79 |
|
|
|
|
| 80 |
canvas.addEventListener('wheel', (e) => {
|
| 81 |
+
e.preventDefault();
|
| 82 |
}, { passive: false });
|
| 83 |
|
| 84 |
progressDialog.style.display = 'block';
|
|
|
|
| 88 |
window.pc = pc;
|
| 89 |
}
|
| 90 |
|
| 91 |
+
// Create PlayCanvas app
|
| 92 |
const device = await pc.createGraphicsDevice(canvas, {
|
| 93 |
deviceTypes: ["webgl2"],
|
| 94 |
glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
|
|
|
|
| 99 |
|
| 100 |
const opts = new pc.AppOptions();
|
| 101 |
opts.graphicsDevice = device;
|
|
|
|
| 102 |
opts.mouse = new pc.Mouse(canvas);
|
| 103 |
opts.touch = new pc.TouchDevice(canvas);
|
| 104 |
opts.componentSystems = [
|
|
|
|
| 106 |
pc.CameraComponentSystem,
|
| 107 |
pc.LightComponentSystem,
|
| 108 |
pc.ScriptComponentSystem,
|
|
|
|
| 109 |
pc.CollisionComponentSystem,
|
| 110 |
pc.RigidbodyComponentSystem
|
| 111 |
];
|
| 112 |
opts.resourceHandlers = [
|
| 113 |
pc.TextureHandler,
|
| 114 |
pc.ContainerHandler,
|
| 115 |
+
pc.ScriptHandler
|
|
|
|
| 116 |
];
|
| 117 |
|
| 118 |
app = new pc.Application(canvas, opts);
|
|
|
|
| 129 |
window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
|
| 130 |
app.on('destroy', () => resizeObserver.disconnect());
|
| 131 |
|
| 132 |
+
// Load orbit camera scripts
|
| 133 |
+
const orbitAsset = new pc.Asset('orbit', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" });
|
| 134 |
+
app.assets.add(orbitAsset);
|
| 135 |
+
|
| 136 |
+
// Load SOGS json (contains metadata and webp references)
|
| 137 |
+
let sogsData, sogsTextures = {};
|
| 138 |
+
try {
|
| 139 |
+
const jsonResp = await fetch(sogsJsonUrl);
|
| 140 |
+
sogsData = await jsonResp.json();
|
| 141 |
+
if (!Array.isArray(sogsData.images)) throw new Error('sogs_json missing .images array');
|
| 142 |
+
// Preload all webp textures as PlayCanvas textures
|
| 143 |
+
for (let i = 0; i < sogsData.images.length; ++i) {
|
| 144 |
+
const imageInfo = sogsData.images[i];
|
| 145 |
+
let url = imageInfo.path || imageInfo.url;
|
| 146 |
+
if (!/^https?:\/\//.test(url) && sogsJsonUrl) {
|
| 147 |
+
// resolve relative to sogsJsonUrl
|
| 148 |
+
const baseUrl = sogsJsonUrl.split('/').slice(0, -1).join('/');
|
| 149 |
+
url = `${baseUrl}/${url}`;
|
| 150 |
+
}
|
| 151 |
+
// Load texture asset synchronously (block start until all images ready)
|
| 152 |
+
sogsTextures[imageInfo.name || `img${i}`] = await new Promise((resolve, reject) => {
|
| 153 |
+
const asset = new pc.Asset(imageInfo.name || `img${i}`, 'texture', { url });
|
| 154 |
+
app.assets.add(asset);
|
| 155 |
+
asset.ready(resolve.bind(null, asset.resource));
|
| 156 |
+
asset.on('error', reject);
|
| 157 |
+
app.assets.load(asset);
|
| 158 |
+
});
|
| 159 |
+
}
|
| 160 |
+
} catch (e) {
|
| 161 |
+
progressDialog.style.display = 'none';
|
| 162 |
+
alert("Failed to load SOGS JSON/images: " + e.message);
|
| 163 |
+
throw e;
|
| 164 |
+
}
|
| 165 |
|
| 166 |
+
// Continue once orbit script is loaded
|
| 167 |
+
orbitAsset.ready(async () => {
|
| 168 |
app.start();
|
| 169 |
progressDialog.style.display = 'none';
|
| 170 |
|
| 171 |
+
// Add the SOGS/webp point cloud as a "gsplat" entity/component
|
| 172 |
modelEntity = new pc.Entity('model');
|
| 173 |
+
|
| 174 |
+
// Here you would use your SOGS-compatible GSplat renderer component.
|
| 175 |
+
// Let's define a placeholder `gsplat` script which you must replace
|
| 176 |
+
// with your PlayCanvas SOGS/gsplat renderer.
|
| 177 |
+
modelEntity.addComponent('script');
|
| 178 |
+
modelEntity.script.create('gsplat', {
|
| 179 |
+
attributes: {
|
| 180 |
+
sogsData: sogsData,
|
| 181 |
+
textures: sogsTextures
|
| 182 |
+
}
|
| 183 |
+
});
|
| 184 |
+
|
| 185 |
modelEntity.setLocalPosition(modelX, modelY, modelZ);
|
| 186 |
modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
| 187 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 188 |
app.root.addChild(modelEntity);
|
| 189 |
|
| 190 |
+
// Camera entity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
cameraEntity = new pc.Entity('camera');
|
| 192 |
+
cameraEntity.addComponent('camera', { clearColor: new pc.Color(1, 1, 1, 1) }); // White BG
|
|
|
|
| 193 |
cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 194 |
cameraEntity.lookAt(modelEntity.getPosition());
|
| 195 |
cameraEntity.addComponent('script');
|
| 196 |
|
|
|
|
| 197 |
cameraEntity.script.create('orbitCamera', {
|
| 198 |
attributes: {
|
| 199 |
focusEntity: modelEntity,
|
|
|
|
| 213 |
app.root.addChild(cameraEntity);
|
| 214 |
|
| 215 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
|
|
|
| 216 |
app.once('update', () => resetViewerCamera());
|
| 217 |
|
| 218 |
+
// Optional: tooltips
|
| 219 |
try {
|
| 220 |
if (config.tooltips_url) {
|
| 221 |
import('./tooltips.js').then(tooltipsModule => {
|
|
|
|
| 227 |
defaultVisible: !!config.showTooltipsDefault,
|
| 228 |
moveDuration: config.tooltipMoveDuration || 0.6
|
| 229 |
});
|
| 230 |
+
}).catch(e => {});
|
|
|
|
|
|
|
| 231 |
}
|
| 232 |
+
} catch (e) {}
|
|
|
|
|
|
|
| 233 |
|
| 234 |
viewerInitialized = true;
|
| 235 |
});
|
| 236 |
+
app.assets.load(orbitAsset);
|
| 237 |
}
|
| 238 |
|
| 239 |
+
// Reset camera helper, unchanged from PLY version
|
| 240 |
export function resetViewerCamera() {
|
| 241 |
try {
|
| 242 |
if (!cameraEntity || !modelEntity || !app) return;
|
|
|
|
| 278 |
if (orbitCam._updatePosition) orbitCam._updatePosition();
|
| 279 |
|
| 280 |
tempEnt.destroy();
|
| 281 |
+
} catch (e) {}
|
|
|
|
|
|
|
| 282 |
}
|