Spaces:
Running
Running
Update viewer.js
Browse files
viewer.js
CHANGED
|
@@ -61,7 +61,7 @@ function traverse(entity, callback) {
|
|
| 61 |
}
|
| 62 |
|
| 63 |
/* -------------------------------------------
|
| 64 |
-
Chargement unique de
|
| 65 |
-------------------------------------------- */
|
| 66 |
|
| 67 |
async function ensureOrbitScriptsLoaded() {
|
|
@@ -73,14 +73,15 @@ async function ensureOrbitScriptsLoaded() {
|
|
| 73 |
|
| 74 |
window.__PLY_ORBIT_LOADING__ = new Promise((resolve, reject) => {
|
| 75 |
const s = document.createElement("script");
|
| 76 |
-
|
|
|
|
| 77 |
s.async = true;
|
| 78 |
s.onload = () => {
|
| 79 |
window.__PLY_ORBIT_LOADED__ = true;
|
| 80 |
resolve();
|
| 81 |
};
|
| 82 |
s.onerror = (e) => {
|
| 83 |
-
console.error("[viewer.js] Failed to load
|
| 84 |
reject(e);
|
| 85 |
};
|
| 86 |
document.head.appendChild(s);
|
|
@@ -97,6 +98,7 @@ let pc;
|
|
| 97 |
export let app = null;
|
| 98 |
let cameraEntity = null;
|
| 99 |
let modelEntity = null;
|
|
|
|
| 100 |
let viewerInitialized = false;
|
| 101 |
let resizeObserver = null;
|
| 102 |
let resizeTimeout = null;
|
|
@@ -349,28 +351,23 @@ export async function initializeViewer(config, instanceId) {
|
|
| 349 |
canvas.removeEventListener("blur", onCanvasBlur);
|
| 350 |
});
|
| 351 |
|
| 352 |
-
// --- Enregistre les assets en
|
| 353 |
-
// Phase 1 : .sog (priorité) ⇒ time-to-first-pixel
|
| 354 |
const sogAsset = new pc.Asset("gsplat", "gsplat", { url: sogUrl });
|
| 355 |
-
app.assets.add(sogAsset);
|
| 356 |
-
|
| 357 |
-
// Phase 2 (déférée) : glb + présentoir
|
| 358 |
const glbAsset = new pc.Asset("glb", "container", { url: glbUrl });
|
| 359 |
-
|
| 360 |
app.assets.add(glbAsset);
|
| 361 |
-
app.assets.add(presentoirAsset);
|
| 362 |
|
| 363 |
-
// Assure
|
| 364 |
await ensureOrbitScriptsLoaded();
|
| 365 |
|
| 366 |
-
// ----------
|
| 367 |
await new Promise((resolve, reject) => {
|
| 368 |
-
const
|
| 369 |
-
|
| 370 |
-
|
| 371 |
});
|
| 372 |
|
| 373 |
-
app.start(); // démarre l'update loop
|
| 374 |
progressDialog.style.display = "none";
|
| 375 |
|
| 376 |
// --- Modèle principal (GSplat via .sog) ---
|
|
@@ -381,7 +378,18 @@ export async function initializeViewer(config, instanceId) {
|
|
| 381 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 382 |
app.root.addChild(modelEntity);
|
| 383 |
|
| 384 |
-
// ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
cameraEntity = new pc.Entity("camera");
|
| 386 |
cameraEntity.addComponent("camera", {
|
| 387 |
clearColor: new pc.Color(color_bg),
|
|
@@ -392,9 +400,11 @@ export async function initializeViewer(config, instanceId) {
|
|
| 392 |
cameraEntity.lookAt(modelEntity.getPosition());
|
| 393 |
cameraEntity.addComponent("script");
|
| 394 |
|
|
|
|
| 395 |
cameraEntity.script.create("orbitCamera", {
|
| 396 |
attributes: {
|
| 397 |
-
focusEntity:
|
|
|
|
| 398 |
inertiaFactor: 0.2,
|
| 399 |
distanceMax: maxZoom,
|
| 400 |
distanceMin: minZoom,
|
|
@@ -418,6 +428,8 @@ export async function initializeViewer(config, instanceId) {
|
|
| 418 |
|
| 419 |
// Taille initiale
|
| 420 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
|
|
|
|
|
|
| 421 |
app.once("update", () => resetViewerCamera());
|
| 422 |
|
| 423 |
// ---------- Perf dynamique : DPR temporairement réduit pendant interaction ----------
|
|
@@ -425,42 +437,33 @@ export async function initializeViewer(config, instanceId) {
|
|
| 425 |
const clamped = Math.max(0.5, Math.min(val, maxDevicePixelRatio));
|
| 426 |
if (app.graphicsDevice.maxPixelRatio !== clamped) {
|
| 427 |
app.graphicsDevice.maxPixelRatio = clamped;
|
| 428 |
-
// Force un resize pour appliquer immédiatement
|
| 429 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 430 |
}
|
| 431 |
};
|
| 432 |
|
| 433 |
const bumpInteraction = () => {
|
| 434 |
-
// baisse DPR pendant l'interaction
|
| 435 |
setDpr(interactDpr);
|
| 436 |
if (idleTimer) clearTimeout(idleTimer);
|
| 437 |
idleTimer = setTimeout(() => {
|
| 438 |
-
// restaure DPR max quand l'utilisateur s'arrête
|
| 439 |
setDpr(Math.min(window.devicePixelRatio || 1, maxDevicePixelRatio));
|
| 440 |
}, idleRestoreDelay);
|
| 441 |
};
|
| 442 |
|
| 443 |
-
// Hooks d'interaction
|
| 444 |
const interactionEvents = ["mousedown", "mousemove", "mouseup", "wheel", "touchstart", "touchmove", "keydown"];
|
| 445 |
interactionEvents.forEach((ev) => {
|
| 446 |
canvas.addEventListener(ev, bumpInteraction, { passive: true });
|
| 447 |
});
|
| 448 |
|
| 449 |
-
// ----------
|
| 450 |
-
// On charge le reste sans bloquer l’affichage initial
|
| 451 |
setTimeout(async () => {
|
| 452 |
try {
|
|
|
|
|
|
|
| 453 |
await new Promise((resolve) => {
|
| 454 |
-
const loader2 = new pc.AssetListLoader([
|
| 455 |
loader2.load(() => resolve());
|
| 456 |
});
|
| 457 |
|
| 458 |
-
// Sol / environnement
|
| 459 |
-
const glbEntity = glbAsset.resource ? glbAsset.resource.instantiateRenderEntity() : null;
|
| 460 |
-
if (glbEntity) {
|
| 461 |
-
app.root.addChild(glbEntity);
|
| 462 |
-
}
|
| 463 |
-
|
| 464 |
const presentoirEntity =
|
| 465 |
presentoirAsset.resource ? presentoirAsset.resource.instantiateRenderEntity() : null;
|
| 466 |
if (presentoirEntity) {
|
|
@@ -468,6 +471,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 468 |
app.root.addChild(presentoirEntity);
|
| 469 |
}
|
| 470 |
|
|
|
|
| 471 |
if (!espace_expo_bool) {
|
| 472 |
const matSol = new pc.StandardMaterial();
|
| 473 |
matSol.blendType = pc.BLEND_NONE;
|
|
@@ -484,19 +488,16 @@ export async function initializeViewer(config, instanceId) {
|
|
| 484 |
});
|
| 485 |
}
|
| 486 |
|
| 487 |
-
if (
|
| 488 |
-
traverse(
|
| 489 |
if (node.render && node.render.meshInstances) {
|
| 490 |
for (const mi of node.render.meshInstances) mi.material = matSol;
|
| 491 |
}
|
| 492 |
});
|
| 493 |
-
|
| 494 |
-
// ////// MODIFIE A LA MANO FAIRE GAFFE //////
|
| 495 |
-
glbEntity.setLocalScale(10, 10, 10);
|
| 496 |
}
|
| 497 |
}
|
| 498 |
|
| 499 |
-
// Tooltips (optionnels)
|
| 500 |
try {
|
| 501 |
if (config.tooltips_url) {
|
| 502 |
import("./tooltips.js")
|
|
@@ -528,10 +529,27 @@ export async function initializeViewer(config, instanceId) {
|
|
| 528 |
export function resetViewerCamera() {
|
| 529 |
try {
|
| 530 |
if (!cameraEntity || !modelEntity || !app) return;
|
| 531 |
-
const
|
| 532 |
-
if (!
|
| 533 |
|
| 534 |
const modelPos = modelEntity.getPosition();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
const tempEnt = new pc.Entity();
|
| 536 |
tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 537 |
tempEnt.lookAt(modelPos);
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
/* -------------------------------------------
|
| 64 |
+
Chargement unique de ctrl_camera_pr_env.js
|
| 65 |
-------------------------------------------- */
|
| 66 |
|
| 67 |
async function ensureOrbitScriptsLoaded() {
|
|
|
|
| 73 |
|
| 74 |
window.__PLY_ORBIT_LOADING__ = new Promise((resolve, reject) => {
|
| 75 |
const s = document.createElement("script");
|
| 76 |
+
// IMPORTANT : charger le script free-camera + collisions (pas l'ancienne orbit-camera)
|
| 77 |
+
s.src = "https://mikafil-viewer-sgos.static.hf.space/deplacement_dans_env/ctrl_camera_pr_env.js";
|
| 78 |
s.async = true;
|
| 79 |
s.onload = () => {
|
| 80 |
window.__PLY_ORBIT_LOADED__ = true;
|
| 81 |
resolve();
|
| 82 |
};
|
| 83 |
s.onerror = (e) => {
|
| 84 |
+
console.error("[viewer.js] Failed to load ctrl_camera_pr_env.js", e);
|
| 85 |
reject(e);
|
| 86 |
};
|
| 87 |
document.head.appendChild(s);
|
|
|
|
| 98 |
export let app = null;
|
| 99 |
let cameraEntity = null;
|
| 100 |
let modelEntity = null;
|
| 101 |
+
let envEntity = null; // <<< GLB instancié (focusEntity collisions)
|
| 102 |
let viewerInitialized = false;
|
| 103 |
let resizeObserver = null;
|
| 104 |
let resizeTimeout = null;
|
|
|
|
| 351 |
canvas.removeEventListener("blur", onCanvasBlur);
|
| 352 |
});
|
| 353 |
|
| 354 |
+
// --- Enregistre et charge les assets en 1 phase pour SOG + GLB (focusEntity prêt avant la caméra) ---
|
|
|
|
| 355 |
const sogAsset = new pc.Asset("gsplat", "gsplat", { url: sogUrl });
|
|
|
|
|
|
|
|
|
|
| 356 |
const glbAsset = new pc.Asset("glb", "container", { url: glbUrl });
|
| 357 |
+
app.assets.add(sogAsset);
|
| 358 |
app.assets.add(glbAsset);
|
|
|
|
| 359 |
|
| 360 |
+
// Assure la free-cam + collisions
|
| 361 |
await ensureOrbitScriptsLoaded();
|
| 362 |
|
| 363 |
+
// ---------- CHARGEMENT SOG + GLB AVANT CREATION CAMERA ----------
|
| 364 |
await new Promise((resolve, reject) => {
|
| 365 |
+
const loader = new pc.AssetListLoader([sogAsset, glbAsset], app.assets);
|
| 366 |
+
loader.load(() => resolve());
|
| 367 |
+
loader.on('error', reject);
|
| 368 |
});
|
| 369 |
|
| 370 |
+
app.start(); // démarre l'update loop dès que possible
|
| 371 |
progressDialog.style.display = "none";
|
| 372 |
|
| 373 |
// --- Modèle principal (GSplat via .sog) ---
|
|
|
|
| 378 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 379 |
app.root.addChild(modelEntity);
|
| 380 |
|
| 381 |
+
// --- Instancier le GLB d’environnement (collision) ---
|
| 382 |
+
envEntity = glbAsset.resource ? glbAsset.resource.instantiateRenderEntity() : null;
|
| 383 |
+
if (envEntity) {
|
| 384 |
+
envEntity.name = "ENV_GLTF";
|
| 385 |
+
app.root.addChild(envEntity);
|
| 386 |
+
// NOTE : évite de changer l'échelle ici, sauf si nécessaire. Si tu DOIS :
|
| 387 |
+
// envEntity.setLocalScale(1,1,1); // garde l'échelle d'import cohérente
|
| 388 |
+
} else {
|
| 389 |
+
console.warn("[viewer.js] GLB resource missing: collisions will fallback on GSplat (aucun mesh).");
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// --- Caméra + scripts d’input (free-cam nommée 'orbitCamera' pour compat) ---
|
| 393 |
cameraEntity = new pc.Entity("camera");
|
| 394 |
cameraEntity.addComponent("camera", {
|
| 395 |
clearColor: new pc.Color(color_bg),
|
|
|
|
| 400 |
cameraEntity.lookAt(modelEntity.getPosition());
|
| 401 |
cameraEntity.addComponent("script");
|
| 402 |
|
| 403 |
+
// >>> focusEntity = GLB en priorité (sinon fallback gsplat) <<<
|
| 404 |
cameraEntity.script.create("orbitCamera", {
|
| 405 |
attributes: {
|
| 406 |
+
focusEntity: envEntity || modelEntity,
|
| 407 |
+
// Attributs hérités pour compat, mais seront interprétés par la free-cam
|
| 408 |
inertiaFactor: 0.2,
|
| 409 |
distanceMax: maxZoom,
|
| 410 |
distanceMin: minZoom,
|
|
|
|
| 428 |
|
| 429 |
// Taille initiale
|
| 430 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 431 |
+
|
| 432 |
+
// IMPORTANT : si la free-cam est active, ne pas "forcer" un reset d'orbite.
|
| 433 |
app.once("update", () => resetViewerCamera());
|
| 434 |
|
| 435 |
// ---------- Perf dynamique : DPR temporairement réduit pendant interaction ----------
|
|
|
|
| 437 |
const clamped = Math.max(0.5, Math.min(val, maxDevicePixelRatio));
|
| 438 |
if (app.graphicsDevice.maxPixelRatio !== clamped) {
|
| 439 |
app.graphicsDevice.maxPixelRatio = clamped;
|
|
|
|
| 440 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 441 |
}
|
| 442 |
};
|
| 443 |
|
| 444 |
const bumpInteraction = () => {
|
|
|
|
| 445 |
setDpr(interactDpr);
|
| 446 |
if (idleTimer) clearTimeout(idleTimer);
|
| 447 |
idleTimer = setTimeout(() => {
|
|
|
|
| 448 |
setDpr(Math.min(window.devicePixelRatio || 1, maxDevicePixelRatio));
|
| 449 |
}, idleRestoreDelay);
|
| 450 |
};
|
| 451 |
|
|
|
|
| 452 |
const interactionEvents = ["mousedown", "mousemove", "mouseup", "wheel", "touchstart", "touchmove", "keydown"];
|
| 453 |
interactionEvents.forEach((ev) => {
|
| 454 |
canvas.addEventListener(ev, bumpInteraction, { passive: true });
|
| 455 |
});
|
| 456 |
|
| 457 |
+
// ---------- CHARGEMENT DIFFÉRÉ : présentoir et tooltips ----------
|
|
|
|
| 458 |
setTimeout(async () => {
|
| 459 |
try {
|
| 460 |
+
const presentoirAsset = new pc.Asset("presentoir", "container", { url: presentoirUrl });
|
| 461 |
+
app.assets.add(presentoirAsset);
|
| 462 |
await new Promise((resolve) => {
|
| 463 |
+
const loader2 = new pc.AssetListLoader([presentoirAsset], app.assets);
|
| 464 |
loader2.load(() => resolve());
|
| 465 |
});
|
| 466 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
const presentoirEntity =
|
| 468 |
presentoirAsset.resource ? presentoirAsset.resource.instantiateRenderEntity() : null;
|
| 469 |
if (presentoirEntity) {
|
|
|
|
| 471 |
app.root.addChild(presentoirEntity);
|
| 472 |
}
|
| 473 |
|
| 474 |
+
// Si pas d'espace expo : recolore GLB & présentoir pour fond uni
|
| 475 |
if (!espace_expo_bool) {
|
| 476 |
const matSol = new pc.StandardMaterial();
|
| 477 |
matSol.blendType = pc.BLEND_NONE;
|
|
|
|
| 488 |
});
|
| 489 |
}
|
| 490 |
|
| 491 |
+
if (envEntity) {
|
| 492 |
+
traverse(envEntity, (node) => {
|
| 493 |
if (node.render && node.render.meshInstances) {
|
| 494 |
for (const mi of node.render.meshInstances) mi.material = matSol;
|
| 495 |
}
|
| 496 |
});
|
|
|
|
|
|
|
|
|
|
| 497 |
}
|
| 498 |
}
|
| 499 |
|
| 500 |
+
// Tooltips (optionnels)
|
| 501 |
try {
|
| 502 |
if (config.tooltips_url) {
|
| 503 |
import("./tooltips.js")
|
|
|
|
| 529 |
export function resetViewerCamera() {
|
| 530 |
try {
|
| 531 |
if (!cameraEntity || !modelEntity || !app) return;
|
| 532 |
+
const camScript = cameraEntity.script && cameraEntity.script.orbitCamera;
|
| 533 |
+
if (!camScript) return;
|
| 534 |
|
| 535 |
const modelPos = modelEntity.getPosition();
|
| 536 |
+
|
| 537 |
+
// Si c'est une FREE-CAM (notre script), ne pas toucher à des champs d'orbite.
|
| 538 |
+
// On se contente éventuellement de réaligner le regard.
|
| 539 |
+
const looksLikeOrbit =
|
| 540 |
+
("pivotPoint" in camScript) ||
|
| 541 |
+
("_distance" in camScript) ||
|
| 542 |
+
("_updatePosition" in camScript);
|
| 543 |
+
|
| 544 |
+
if (!looksLikeOrbit) {
|
| 545 |
+
// Free camera : juste orienter vers le modèle si souhaité
|
| 546 |
+
cameraEntity.lookAt(modelPos);
|
| 547 |
+
return;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
// --- Cas d'une vraie orbit-camera (compat héritée) ---
|
| 551 |
+
const orbitCam = camScript;
|
| 552 |
+
|
| 553 |
const tempEnt = new pc.Entity();
|
| 554 |
tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 555 |
tempEnt.lookAt(modelPos);
|