Spaces:
Running
Running
Update viewer.js
Browse files
viewer.js
CHANGED
|
@@ -55,43 +55,54 @@ function traverse(entity, callback) {
|
|
| 55 |
}
|
| 56 |
}
|
| 57 |
|
| 58 |
-
/
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
|
|
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
"script",
|
| 72 |
-
{ url: "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js" }
|
| 73 |
-
);
|
| 74 |
-
app.assets.add(asset);
|
| 75 |
-
|
| 76 |
-
const done = () => {
|
| 77 |
-
window.__orbitScriptsLoaded = true;
|
| 78 |
-
resolve(true);
|
| 79 |
-
};
|
| 80 |
-
const fail = (e) => {
|
| 81 |
-
console.error("[viewer.js] orbit-camera.js failed to load", e);
|
| 82 |
-
reject(e);
|
| 83 |
-
};
|
| 84 |
-
|
| 85 |
-
asset.once("load", done);
|
| 86 |
-
asset.once("error", fail);
|
| 87 |
-
app.assets.load(asset);
|
| 88 |
-
});
|
| 89 |
|
| 90 |
try {
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
} catch (e) {
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
let pc;
|
|
@@ -112,76 +123,58 @@ let sogsUrl, glbUrl, presentoirUrl;
|
|
| 112 |
let color_bg_hex, color_bg, espace_expo_bool;
|
| 113 |
|
| 114 |
export async function initializeViewer(config, instanceId) {
|
| 115 |
-
// Ce module est importé avec un query param unique par interface.js,
|
| 116 |
-
// donc 1 instance par import. On garde tout de même ce garde-fou.
|
| 117 |
if (viewerInitialized) return;
|
| 118 |
|
| 119 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
| 120 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
| 121 |
|
| 122 |
-
//
|
| 123 |
sogsUrl = config.sogs_json_url;
|
| 124 |
-
glbUrl =
|
| 125 |
-
config.glb_url
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
? config.presentoir_url
|
| 131 |
-
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
|
| 132 |
minZoom = parseFloat(config.minZoom || "1");
|
| 133 |
maxZoom = parseFloat(config.maxZoom || "20");
|
| 134 |
minAngle = parseFloat(config.minAngle || "-45");
|
| 135 |
maxAngle = parseFloat(config.maxAngle || "90");
|
| 136 |
-
minAzimuth =
|
| 137 |
-
|
| 138 |
-
maxAzimuth =
|
| 139 |
-
config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
|
| 140 |
minPivotY = parseFloat(config.minPivotY || "0");
|
| 141 |
-
minY = config.minY !== undefined ? parseFloat(config.minY) : 0;
|
| 142 |
-
|
| 143 |
-
modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
|
| 144 |
-
modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
|
| 145 |
-
modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
|
| 146 |
-
modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
|
| 147 |
-
modelRotationX =
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
const cameraXPhone =
|
| 165 |
-
config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
|
| 166 |
-
const cameraYPhone =
|
| 167 |
-
config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
|
| 168 |
-
const cameraZPhone =
|
| 169 |
-
config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
|
| 170 |
-
|
| 171 |
-
color_bg_hex =
|
| 172 |
-
config.canvas_background !== undefined ? config.canvas_background : "#FFFFFF";
|
| 173 |
-
espace_expo_bool =
|
| 174 |
-
config.espace_expo_bool !== undefined ? config.espace_expo_bool : false;
|
| 175 |
color_bg = hexToRgbaArray(color_bg_hex);
|
| 176 |
|
| 177 |
chosenCameraX = isMobile ? cameraXPhone : cameraX;
|
| 178 |
chosenCameraY = isMobile ? cameraYPhone : cameraY;
|
| 179 |
chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
|
| 180 |
|
| 181 |
-
//
|
| 182 |
const canvasId = "canvas-" + instanceId;
|
| 183 |
const progressDialog = document.getElementById("progress-dialog-" + instanceId);
|
| 184 |
-
const progressIndicator = document.getElementById("progress-indicator-" + instanceId);
|
| 185 |
const viewerContainer = document.getElementById("viewer-container-" + instanceId);
|
| 186 |
|
| 187 |
let oldCanvas = document.getElementById(canvasId);
|
|
@@ -195,79 +188,37 @@ export async function initializeViewer(config, instanceId) {
|
|
| 195 |
canvas.setAttribute("tabindex", "0");
|
| 196 |
viewerContainer.insertBefore(canvas, progressDialog);
|
| 197 |
|
|
|
|
| 198 |
canvas.style.touchAction = "none";
|
| 199 |
canvas.style.webkitTouchCallout = "none";
|
| 200 |
canvas.addEventListener("gesturestart", (e) => e.preventDefault());
|
| 201 |
canvas.addEventListener("gesturechange", (e) => e.preventDefault());
|
| 202 |
canvas.addEventListener("gestureend", (e) => e.preventDefault());
|
| 203 |
canvas.addEventListener("dblclick", (e) => e.preventDefault());
|
| 204 |
-
canvas.addEventListener(
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
},
|
| 209 |
-
{ passive: false }
|
| 210 |
-
);
|
| 211 |
-
canvas.addEventListener(
|
| 212 |
-
"wheel",
|
| 213 |
-
(e) => {
|
| 214 |
-
e.preventDefault();
|
| 215 |
-
},
|
| 216 |
-
{ passive: false }
|
| 217 |
-
);
|
| 218 |
|
| 219 |
-
// --- Clavier : bloquer le scroll
|
| 220 |
-
const scrollKeys = new Set([
|
| 221 |
-
"ArrowUp",
|
| 222 |
-
"ArrowDown",
|
| 223 |
-
"ArrowLeft",
|
| 224 |
-
"ArrowRight",
|
| 225 |
-
"PageUp",
|
| 226 |
-
"PageDown",
|
| 227 |
-
"Home",
|
| 228 |
-
"End",
|
| 229 |
-
" ",
|
| 230 |
-
"Space",
|
| 231 |
-
"Spacebar"
|
| 232 |
-
]);
|
| 233 |
let isPointerOverCanvas = false;
|
| 234 |
const focusCanvas = () => canvas.focus({ preventScroll: true });
|
| 235 |
|
| 236 |
-
const onPointerEnter = () => {
|
| 237 |
-
|
| 238 |
-
focusCanvas();
|
| 239 |
-
};
|
| 240 |
-
const onPointerLeave = () => {
|
| 241 |
-
isPointerOverCanvas = false;
|
| 242 |
-
// rendre immédiatement le scroll à la page sans recliquer
|
| 243 |
-
if (document.activeElement === canvas) canvas.blur();
|
| 244 |
-
};
|
| 245 |
|
| 246 |
canvas.addEventListener("pointerenter", onPointerEnter);
|
| 247 |
canvas.addEventListener("pointerleave", onPointerLeave);
|
| 248 |
canvas.addEventListener("mouseenter", onPointerEnter);
|
| 249 |
canvas.addEventListener("mouseleave", onPointerLeave);
|
| 250 |
-
|
| 251 |
canvas.addEventListener("mousedown", focusCanvas);
|
| 252 |
-
canvas.addEventListener(
|
| 253 |
-
|
| 254 |
-
() => {
|
| 255 |
-
focusCanvas();
|
| 256 |
-
},
|
| 257 |
-
{ passive: false }
|
| 258 |
-
);
|
| 259 |
-
|
| 260 |
-
const onCanvasBlur = () => {
|
| 261 |
-
isPointerOverCanvas = false;
|
| 262 |
-
};
|
| 263 |
-
canvas.addEventListener("blur", onCanvasBlur);
|
| 264 |
|
| 265 |
const onKeyDownCapture = (e) => {
|
| 266 |
if (!isPointerOverCanvas) return;
|
| 267 |
-
if (scrollKeys.has(e.key) || scrollKeys.has(e.code))
|
| 268 |
-
e.preventDefault(); // bloque le scroll page
|
| 269 |
-
// ne pas stopPropagation -> PlayCanvas reçoit bien les touches
|
| 270 |
-
}
|
| 271 |
};
|
| 272 |
window.addEventListener("keydown", onKeyDownCapture, true);
|
| 273 |
|
|
@@ -278,7 +229,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 278 |
window.pc = pc;
|
| 279 |
}
|
| 280 |
|
| 281 |
-
//
|
| 282 |
const device = await pc.createGraphicsDevice(canvas, {
|
| 283 |
deviceTypes: ["webgl2"],
|
| 284 |
glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
|
|
@@ -291,7 +242,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 291 |
opts.graphicsDevice = device;
|
| 292 |
opts.mouse = new pc.Mouse(canvas);
|
| 293 |
opts.touch = new pc.TouchDevice(canvas);
|
| 294 |
-
opts.keyboard = new pc.Keyboard(canvas);
|
| 295 |
opts.componentSystems = [
|
| 296 |
pc.RenderComponentSystem,
|
| 297 |
pc.CameraComponentSystem,
|
|
@@ -323,49 +274,39 @@ export async function initializeViewer(config, instanceId) {
|
|
| 323 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
|
| 324 |
);
|
| 325 |
|
| 326 |
-
// Nettoyage complet à la destruction
|
| 327 |
app.on("destroy", () => {
|
| 328 |
resizeObserver.disconnect();
|
| 329 |
if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
|
| 330 |
-
|
| 331 |
window.removeEventListener("keydown", onKeyDownCapture, true);
|
| 332 |
-
|
| 333 |
canvas.removeEventListener("pointerenter", onPointerEnter);
|
| 334 |
canvas.removeEventListener("pointerleave", onPointerLeave);
|
| 335 |
canvas.removeEventListener("mouseenter", onPointerEnter);
|
| 336 |
canvas.removeEventListener("mouseleave", onPointerLeave);
|
| 337 |
canvas.removeEventListener("mousedown", focusCanvas);
|
| 338 |
canvas.removeEventListener("touchstart", focusCanvas);
|
| 339 |
-
canvas.removeEventListener("blur", onCanvasBlur);
|
| 340 |
});
|
| 341 |
|
| 342 |
-
// Assets
|
| 343 |
-
// ⚠️ On NE charge PAS orbit-camera.js via AssetListLoader pour éviter les
|
| 344 |
-
// redéfinitions multiples de scripts. On l'assure via ensureOrbitScripts().
|
| 345 |
const assets = {
|
| 346 |
sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
|
| 347 |
glb: new pc.Asset("glb", "container", { url: glbUrl }),
|
| 348 |
presentoir: new pc.Asset("presentoir", "container", { url: presentoirUrl })
|
| 349 |
};
|
| 350 |
-
|
| 351 |
for (const key in assets) app.assets.add(assets[key]);
|
| 352 |
|
| 353 |
const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
|
| 354 |
loader.load(async () => {
|
| 355 |
-
//
|
| 356 |
-
await
|
| 357 |
|
| 358 |
app.start();
|
| 359 |
progressDialog.style.display = "none";
|
| 360 |
|
|
|
|
| 361 |
modelEntity = new pc.Entity("model");
|
| 362 |
modelEntity.addComponent("gsplat", { asset: assets.sogs });
|
| 363 |
modelEntity.setLocalPosition(modelX, modelY, modelZ);
|
| 364 |
-
modelEntity.setLocalEulerAngles(
|
| 365 |
-
modelRotationX,
|
| 366 |
-
modelRotationY,
|
| 367 |
-
modelRotationZ
|
| 368 |
-
);
|
| 369 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 370 |
app.root.addChild(modelEntity);
|
| 371 |
|
|
@@ -373,11 +314,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 373 |
app.root.addChild(glbEntity);
|
| 374 |
|
| 375 |
const presentoirEntity = assets.presentoir.resource.instantiateRenderEntity();
|
| 376 |
-
presentoirEntity.setLocalScale(
|
| 377 |
-
presentoirScaleX,
|
| 378 |
-
presentoirScaleY,
|
| 379 |
-
presentoirScaleZ
|
| 380 |
-
);
|
| 381 |
app.root.addChild(presentoirEntity);
|
| 382 |
|
| 383 |
if (!espace_expo_bool) {
|
|
@@ -390,21 +327,17 @@ export async function initializeViewer(config, instanceId) {
|
|
| 390 |
|
| 391 |
traverse(presentoirEntity, (node) => {
|
| 392 |
if (node.render && node.render.meshInstances) {
|
| 393 |
-
for (let mi of node.render.meshInstances)
|
| 394 |
-
mi.material = matSol;
|
| 395 |
-
}
|
| 396 |
}
|
| 397 |
});
|
| 398 |
-
|
| 399 |
traverse(glbEntity, (node) => {
|
| 400 |
if (node.render && node.render.meshInstances) {
|
| 401 |
-
for (let mi of node.render.meshInstances)
|
| 402 |
-
mi.material = matSol;
|
| 403 |
-
}
|
| 404 |
}
|
| 405 |
});
|
| 406 |
}
|
| 407 |
|
|
|
|
| 408 |
cameraEntity = new pc.Entity("camera");
|
| 409 |
cameraEntity.addComponent("camera", {
|
| 410 |
clearColor: new pc.Color(color_bg),
|
|
@@ -431,40 +364,28 @@ export async function initializeViewer(config, instanceId) {
|
|
| 431 |
cameraEntity.script.create("orbitCameraInputMouse");
|
| 432 |
cameraEntity.script.create("orbitCameraInputTouch");
|
| 433 |
cameraEntity.script.create("orbitCameraInputKeyboard", {
|
| 434 |
-
attributes: {
|
| 435 |
-
forwardSpeed: 1.2, // vertical (relatif à la distance)
|
| 436 |
-
strafeSpeed: 1.2 // gauche/droite (relatif à la distance)
|
| 437 |
-
}
|
| 438 |
});
|
| 439 |
app.root.addChild(cameraEntity);
|
| 440 |
|
| 441 |
-
app.resizeCanvas(
|
| 442 |
-
viewerContainer.clientWidth,
|
| 443 |
-
viewerContainer.clientHeight
|
| 444 |
-
);
|
| 445 |
app.once("update", () => resetViewerCamera());
|
| 446 |
|
| 447 |
-
// Tooltips
|
| 448 |
try {
|
| 449 |
if (config.tooltips_url) {
|
| 450 |
-
import("./tooltips.js")
|
| 451 |
-
.
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
moveDuration: config.tooltipMoveDuration || 0.6
|
| 459 |
-
});
|
| 460 |
-
})
|
| 461 |
-
.catch(() => {
|
| 462 |
-
/* optional */
|
| 463 |
});
|
|
|
|
| 464 |
}
|
| 465 |
-
} catch (
|
| 466 |
-
// optional
|
| 467 |
-
}
|
| 468 |
|
| 469 |
viewerInitialized = true;
|
| 470 |
});
|
|
|
|
| 55 |
}
|
| 56 |
}
|
| 57 |
|
| 58 |
+
/**
|
| 59 |
+
* Charge la source d'orbit-camera.js une seule fois (cache global),
|
| 60 |
+
* puis l'évalue POUR CHAQUE APP afin que pc.createScript enregistre
|
| 61 |
+
* les types dans le ScriptRegistry de l'application courante.
|
| 62 |
+
*/
|
| 63 |
+
const ORBIT_URL = "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js";
|
| 64 |
+
async function registerOrbitScriptsForApp(app) {
|
| 65 |
+
// Si cette app possède déjà les scripts, on ne fait rien
|
| 66 |
+
try {
|
| 67 |
+
if (app?.scripts?.get && app.scripts.get("orbitCamera")) return;
|
| 68 |
+
} catch (_) {}
|
| 69 |
+
|
| 70 |
+
// Cache global de la source
|
| 71 |
+
if (!window.__ORBIT_SOURCE_PROMISE__) {
|
| 72 |
+
window.__ORBIT_SOURCE_PROMISE__ = fetch(ORBIT_URL, { cache: "no-store", credentials: "omit" })
|
| 73 |
+
.then(r => {
|
| 74 |
+
if (!r.ok) throw new Error("Failed to fetch orbit-camera.js");
|
| 75 |
+
return r.text();
|
| 76 |
+
});
|
| 77 |
}
|
| 78 |
+
const src = await window.__ORBIT_SOURCE_PROMISE__;
|
| 79 |
|
| 80 |
+
// Binder l’“application courante” sur CETTE app pendant l’évaluation
|
| 81 |
+
const prevGetApp = pc.Application.getApplication ? pc.Application.getApplication.bind(pc.Application) : null;
|
| 82 |
+
const prevApp = pc.app;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
try {
|
| 85 |
+
if (pc.Application.getApplication) {
|
| 86 |
+
pc.Application.getApplication = () => app;
|
| 87 |
+
}
|
| 88 |
+
pc.app = app;
|
| 89 |
+
|
| 90 |
+
// Évaluation globale : pc.createScript enregistre dans le registry de "app"
|
| 91 |
+
(0, eval)(src);
|
| 92 |
+
|
| 93 |
} catch (e) {
|
| 94 |
+
console.error("[viewer.js] orbit-camera.js eval error:", e);
|
| 95 |
+
} finally {
|
| 96 |
+
if (prevGetApp) pc.Application.getApplication = prevGetApp;
|
| 97 |
+
pc.app = prevApp;
|
| 98 |
}
|
| 99 |
+
|
| 100 |
+
// Sécurité : vérifier que le type est bien dispo
|
| 101 |
+
try {
|
| 102 |
+
if (!(app?.scripts?.get && app.scripts.get("orbitCamera"))) {
|
| 103 |
+
console.warn("[viewer.js] orbitCamera script type not found on app after eval");
|
| 104 |
+
}
|
| 105 |
+
} catch (_) {}
|
| 106 |
}
|
| 107 |
|
| 108 |
let pc;
|
|
|
|
| 123 |
let color_bg_hex, color_bg, espace_expo_bool;
|
| 124 |
|
| 125 |
export async function initializeViewer(config, instanceId) {
|
|
|
|
|
|
|
| 126 |
if (viewerInitialized) return;
|
| 127 |
|
| 128 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
| 129 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
| 130 |
|
| 131 |
+
// Params
|
| 132 |
sogsUrl = config.sogs_json_url;
|
| 133 |
+
glbUrl = (config.glb_url !== undefined)
|
| 134 |
+
? config.glb_url
|
| 135 |
+
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
|
| 136 |
+
presentoirUrl = (config.presentoir_url !== undefined)
|
| 137 |
+
? config.presentoir_url
|
| 138 |
+
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
|
|
|
|
|
|
|
| 139 |
minZoom = parseFloat(config.minZoom || "1");
|
| 140 |
maxZoom = parseFloat(config.maxZoom || "20");
|
| 141 |
minAngle = parseFloat(config.minAngle || "-45");
|
| 142 |
maxAngle = parseFloat(config.maxAngle || "90");
|
| 143 |
+
minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
|
| 144 |
+
maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
|
|
|
|
|
|
|
| 145 |
minPivotY = parseFloat(config.minPivotY || "0");
|
| 146 |
+
minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
|
| 147 |
+
|
| 148 |
+
modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
|
| 149 |
+
modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
|
| 150 |
+
modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
|
| 151 |
+
modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
|
| 152 |
+
modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
|
| 153 |
+
modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
|
| 154 |
+
modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
|
| 155 |
+
|
| 156 |
+
presentoirScaleX = (config.presentoirScaleX !== undefined) ? parseFloat(config.presentoirScaleX) : 0;
|
| 157 |
+
presentoirScaleY = (config.presentoirScaleY !== undefined) ? parseFloat(config.presentoirScaleY) : 0;
|
| 158 |
+
presentoirScaleZ = (config.presentoirScaleZ !== undefined) ? parseFloat(config.presentoirScaleZ) : 0;
|
| 159 |
+
|
| 160 |
+
const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
|
| 161 |
+
const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
|
| 162 |
+
const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
|
| 163 |
+
const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
|
| 164 |
+
const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
|
| 165 |
+
const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
|
| 166 |
+
|
| 167 |
+
color_bg_hex = (config.canvas_background !== undefined) ? config.canvas_background : "#FFFFFF";
|
| 168 |
+
espace_expo_bool = (config.espace_expo_bool !== undefined) ? config.espace_expo_bool : false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
color_bg = hexToRgbaArray(color_bg_hex);
|
| 170 |
|
| 171 |
chosenCameraX = isMobile ? cameraXPhone : cameraX;
|
| 172 |
chosenCameraY = isMobile ? cameraYPhone : cameraY;
|
| 173 |
chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
|
| 174 |
|
| 175 |
+
// Canvas
|
| 176 |
const canvasId = "canvas-" + instanceId;
|
| 177 |
const progressDialog = document.getElementById("progress-dialog-" + instanceId);
|
|
|
|
| 178 |
const viewerContainer = document.getElementById("viewer-container-" + instanceId);
|
| 179 |
|
| 180 |
let oldCanvas = document.getElementById(canvasId);
|
|
|
|
| 188 |
canvas.setAttribute("tabindex", "0");
|
| 189 |
viewerContainer.insertBefore(canvas, progressDialog);
|
| 190 |
|
| 191 |
+
// Gestes / molette
|
| 192 |
canvas.style.touchAction = "none";
|
| 193 |
canvas.style.webkitTouchCallout = "none";
|
| 194 |
canvas.addEventListener("gesturestart", (e) => e.preventDefault());
|
| 195 |
canvas.addEventListener("gesturechange", (e) => e.preventDefault());
|
| 196 |
canvas.addEventListener("gestureend", (e) => e.preventDefault());
|
| 197 |
canvas.addEventListener("dblclick", (e) => e.preventDefault());
|
| 198 |
+
canvas.addEventListener("touchstart", (e) => {
|
| 199 |
+
if (e.touches.length > 1) e.preventDefault();
|
| 200 |
+
}, { passive: false });
|
| 201 |
+
canvas.addEventListener("wheel", (e) => { e.preventDefault(); }, { passive: false });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
// --- Clavier : bloquer le scroll uniquement quand la souris est au-dessus ---
|
| 204 |
+
const scrollKeys = new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","PageUp","PageDown","Home","End"," ","Space","Spacebar"]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
let isPointerOverCanvas = false;
|
| 206 |
const focusCanvas = () => canvas.focus({ preventScroll: true });
|
| 207 |
|
| 208 |
+
const onPointerEnter = () => { isPointerOverCanvas = true; focusCanvas(); };
|
| 209 |
+
const onPointerLeave = () => { isPointerOverCanvas = false; if (document.activeElement === canvas) canvas.blur(); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
canvas.addEventListener("pointerenter", onPointerEnter);
|
| 212 |
canvas.addEventListener("pointerleave", onPointerLeave);
|
| 213 |
canvas.addEventListener("mouseenter", onPointerEnter);
|
| 214 |
canvas.addEventListener("mouseleave", onPointerLeave);
|
|
|
|
| 215 |
canvas.addEventListener("mousedown", focusCanvas);
|
| 216 |
+
canvas.addEventListener("touchstart", () => { focusCanvas(); }, { passive: false });
|
| 217 |
+
canvas.addEventListener("blur", () => { isPointerOverCanvas = false; });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
const onKeyDownCapture = (e) => {
|
| 220 |
if (!isPointerOverCanvas) return;
|
| 221 |
+
if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) e.preventDefault();
|
|
|
|
|
|
|
|
|
|
| 222 |
};
|
| 223 |
window.addEventListener("keydown", onKeyDownCapture, true);
|
| 224 |
|
|
|
|
| 229 |
window.pc = pc;
|
| 230 |
}
|
| 231 |
|
| 232 |
+
// App
|
| 233 |
const device = await pc.createGraphicsDevice(canvas, {
|
| 234 |
deviceTypes: ["webgl2"],
|
| 235 |
glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
|
|
|
|
| 242 |
opts.graphicsDevice = device;
|
| 243 |
opts.mouse = new pc.Mouse(canvas);
|
| 244 |
opts.touch = new pc.TouchDevice(canvas);
|
| 245 |
+
opts.keyboard = new pc.Keyboard(canvas);
|
| 246 |
opts.componentSystems = [
|
| 247 |
pc.RenderComponentSystem,
|
| 248 |
pc.CameraComponentSystem,
|
|
|
|
| 274 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
|
| 275 |
);
|
| 276 |
|
|
|
|
| 277 |
app.on("destroy", () => {
|
| 278 |
resizeObserver.disconnect();
|
| 279 |
if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
|
|
|
|
| 280 |
window.removeEventListener("keydown", onKeyDownCapture, true);
|
|
|
|
| 281 |
canvas.removeEventListener("pointerenter", onPointerEnter);
|
| 282 |
canvas.removeEventListener("pointerleave", onPointerLeave);
|
| 283 |
canvas.removeEventListener("mouseenter", onPointerEnter);
|
| 284 |
canvas.removeEventListener("mouseleave", onPointerLeave);
|
| 285 |
canvas.removeEventListener("mousedown", focusCanvas);
|
| 286 |
canvas.removeEventListener("touchstart", focusCanvas);
|
|
|
|
| 287 |
});
|
| 288 |
|
| 289 |
+
// Assets (on ne met PAS orbit-camera ici)
|
|
|
|
|
|
|
| 290 |
const assets = {
|
| 291 |
sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
|
| 292 |
glb: new pc.Asset("glb", "container", { url: glbUrl }),
|
| 293 |
presentoir: new pc.Asset("presentoir", "container", { url: presentoirUrl })
|
| 294 |
};
|
|
|
|
| 295 |
for (const key in assets) app.assets.add(assets[key]);
|
| 296 |
|
| 297 |
const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
|
| 298 |
loader.load(async () => {
|
| 299 |
+
// Enregistrer les scripts orbit pour CETTE app
|
| 300 |
+
await registerOrbitScriptsForApp(app);
|
| 301 |
|
| 302 |
app.start();
|
| 303 |
progressDialog.style.display = "none";
|
| 304 |
|
| 305 |
+
// Modèle
|
| 306 |
modelEntity = new pc.Entity("model");
|
| 307 |
modelEntity.addComponent("gsplat", { asset: assets.sogs });
|
| 308 |
modelEntity.setLocalPosition(modelX, modelY, modelZ);
|
| 309 |
+
modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 311 |
app.root.addChild(modelEntity);
|
| 312 |
|
|
|
|
| 314 |
app.root.addChild(glbEntity);
|
| 315 |
|
| 316 |
const presentoirEntity = assets.presentoir.resource.instantiateRenderEntity();
|
| 317 |
+
presentoirEntity.setLocalScale(presentoirScaleX, presentoirScaleY, presentoirScaleZ);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
app.root.addChild(presentoirEntity);
|
| 319 |
|
| 320 |
if (!espace_expo_bool) {
|
|
|
|
| 327 |
|
| 328 |
traverse(presentoirEntity, (node) => {
|
| 329 |
if (node.render && node.render.meshInstances) {
|
| 330 |
+
for (let mi of node.render.meshInstances) mi.material = matSol;
|
|
|
|
|
|
|
| 331 |
}
|
| 332 |
});
|
|
|
|
| 333 |
traverse(glbEntity, (node) => {
|
| 334 |
if (node.render && node.render.meshInstances) {
|
| 335 |
+
for (let mi of node.render.meshInstances) mi.material = matSol;
|
|
|
|
|
|
|
| 336 |
}
|
| 337 |
});
|
| 338 |
}
|
| 339 |
|
| 340 |
+
// Caméra + scripts d’input
|
| 341 |
cameraEntity = new pc.Entity("camera");
|
| 342 |
cameraEntity.addComponent("camera", {
|
| 343 |
clearColor: new pc.Color(color_bg),
|
|
|
|
| 364 |
cameraEntity.script.create("orbitCameraInputMouse");
|
| 365 |
cameraEntity.script.create("orbitCameraInputTouch");
|
| 366 |
cameraEntity.script.create("orbitCameraInputKeyboard", {
|
| 367 |
+
attributes: { forwardSpeed: 1.2, strafeSpeed: 1.2 }
|
|
|
|
|
|
|
|
|
|
| 368 |
});
|
| 369 |
app.root.addChild(cameraEntity);
|
| 370 |
|
| 371 |
+
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
|
|
|
|
|
|
|
|
|
| 372 |
app.once("update", () => resetViewerCamera());
|
| 373 |
|
| 374 |
+
// Tooltips (optionnel)
|
| 375 |
try {
|
| 376 |
if (config.tooltips_url) {
|
| 377 |
+
import("./tooltips.js").then((tooltipsModule) => {
|
| 378 |
+
tooltipsModule.initializeTooltips({
|
| 379 |
+
app,
|
| 380 |
+
cameraEntity,
|
| 381 |
+
modelEntity,
|
| 382 |
+
tooltipsUrl: config.tooltips_url,
|
| 383 |
+
defaultVisible: !!config.showTooltipsDefault,
|
| 384 |
+
moveDuration: config.tooltipMoveDuration || 0.6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
});
|
| 386 |
+
}).catch(() => {});
|
| 387 |
}
|
| 388 |
+
} catch (_) {}
|
|
|
|
|
|
|
| 389 |
|
| 390 |
viewerInitialized = true;
|
| 391 |
});
|