Spaces:
Running
Running
Update deplacement_dans_env/viewer_pr_env.js
Browse files
deplacement_dans_env/viewer_pr_env.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
-
// viewer_pr_env.js — robuste (
|
| 2 |
// ============================================================================
|
| 3 |
-
// -
|
| 4 |
-
// -
|
| 5 |
-
//
|
| 6 |
-
// -
|
|
|
|
| 7 |
// - Player capsule créé seulement si Ammo OK, puis caméra reparentée
|
| 8 |
-
// -
|
| 9 |
-
// -
|
| 10 |
// ============================================================================
|
| 11 |
|
| 12 |
/* -------------------------------------------
|
|
@@ -90,8 +91,52 @@ function isMobileUA() {
|
|
| 90 |
return isIOS || isAndroid;
|
| 91 |
}
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
async function loadAmmoWithTimeout(baseUrl, timeoutMs = 4000) {
|
| 94 |
-
// Config URLs
|
| 95 |
try {
|
| 96 |
pc.WasmModule.setConfig('Ammo', {
|
| 97 |
glueUrl: `${baseUrl}ammo.wasm.js`,
|
|
@@ -102,7 +147,7 @@ async function loadAmmoWithTimeout(baseUrl, timeoutMs = 4000) {
|
|
| 102 |
console.warn('[Ammo] setConfig failed (ignored):', e);
|
| 103 |
}
|
| 104 |
|
| 105 |
-
// 1)
|
| 106 |
try {
|
| 107 |
const maybePromise = pc.WasmModule.getInstance('Ammo', `${baseUrl}ammo.wasm.js`);
|
| 108 |
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
@@ -118,7 +163,7 @@ async function loadAmmoWithTimeout(baseUrl, timeoutMs = 4000) {
|
|
| 118 |
console.warn('[Ammo] promise API failed:', e);
|
| 119 |
}
|
| 120 |
|
| 121 |
-
// 2)
|
| 122 |
let resolved = false;
|
| 123 |
try {
|
| 124 |
step('Ammo: waiting (callback API)…');
|
|
@@ -126,10 +171,7 @@ async function loadAmmoWithTimeout(baseUrl, timeoutMs = 4000) {
|
|
| 126 |
new Promise((resolve) => pc.WasmModule.getInstance('Ammo', () => { resolved = true; resolve(); })),
|
| 127 |
new Promise((_, rej) => setTimeout(() => rej(new Error('Ammo load timeout (callback)')), timeoutMs))
|
| 128 |
]);
|
| 129 |
-
if (resolved) {
|
| 130 |
-
step('Ammo: ready (callback API)');
|
| 131 |
-
return true;
|
| 132 |
-
}
|
| 133 |
} catch (e) {
|
| 134 |
console.warn('[Ammo] callback API failed:', e);
|
| 135 |
}
|
|
@@ -237,18 +279,24 @@ export async function initializeViewer(config, instanceId) {
|
|
| 237 |
canvas.addEventListener('blur', onCanvasBlur);
|
| 238 |
|
| 239 |
// Empêche les touches scroll pendant survol
|
| 240 |
-
const scrollKeys = new Set(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','PageUp','PageDown','Home','End',' ','Space','Spacebar']);
|
| 241 |
const onKeyDownCapture = (e) => { if (!isPointerOverCanvas) return; if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) e.preventDefault(); };
|
| 242 |
window.addEventListener('keydown', onKeyDownCapture, true);
|
| 243 |
|
| 244 |
if (progressDialog) progressDialog.style.display = 'block';
|
| 245 |
|
| 246 |
-
// ---- Import PlayCanvas ----
|
| 247 |
if (!pc) {
|
| 248 |
-
step('B: importing PlayCanvas…');
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
|
| 254 |
// ---- Crée l’Application (démarrage non-bloquant) ----
|
|
@@ -329,28 +377,18 @@ export async function initializeViewer(config, instanceId) {
|
|
| 329 |
const assets = [];
|
| 330 |
let sogAsset = null, glbAsset = null;
|
| 331 |
|
| 332 |
-
if (sogUrl) {
|
| 333 |
-
|
| 334 |
-
app.assets.add(sogAsset); assets.push(sogAsset);
|
| 335 |
-
}
|
| 336 |
-
if (glbUrl) {
|
| 337 |
-
glbAsset = new pc.Asset('glb', 'container', { url: glbUrl });
|
| 338 |
-
app.assets.add(glbAsset); assets.push(glbAsset);
|
| 339 |
-
}
|
| 340 |
|
| 341 |
step('F: requesting assets', { sog: !!sogUrl, glb: !!glbUrl });
|
| 342 |
|
| 343 |
await new Promise((resolve) => {
|
| 344 |
if (!assets.length) return resolve();
|
| 345 |
-
|
| 346 |
const loader = new pc.AssetListLoader(assets, app.assets);
|
| 347 |
let done = false;
|
| 348 |
const finish = (label) => { if (!done) { done = true; step('F+: assets loaded (' + label + ')'); resolve(); } };
|
| 349 |
-
|
| 350 |
loader.on('error', (e) => { console.warn('[viewer] Asset load error:', e); finish('with errors'); });
|
| 351 |
loader.load(() => finish('ok'));
|
| 352 |
-
|
| 353 |
-
// garde-fou timeout
|
| 354 |
setTimeout(() => { if (!done) { console.warn('[viewer] Asset load timeout — continuing'); finish('timeout'); } }, 10000);
|
| 355 |
});
|
| 356 |
|
|
@@ -366,13 +404,8 @@ export async function initializeViewer(config, instanceId) {
|
|
| 366 |
|
| 367 |
// ---- Instancier le GLB d’environnement ----
|
| 368 |
envEntity = glbAsset && glbAsset.resource ? glbAsset.resource.instantiateRenderEntity() : null;
|
| 369 |
-
if (envEntity) {
|
| 370 |
-
|
| 371 |
-
app.root.addChild(envEntity);
|
| 372 |
-
step('I: env GLB instanced');
|
| 373 |
-
} else {
|
| 374 |
-
console.warn('[viewer] No environment GLB — physics will be skipped.');
|
| 375 |
-
}
|
| 376 |
|
| 377 |
// ---- Matériau "fond uni" si pas d'espace expo ----
|
| 378 |
if (!espace_expo_bool && envEntity) {
|
|
@@ -391,25 +424,18 @@ export async function initializeViewer(config, instanceId) {
|
|
| 391 |
}
|
| 392 |
|
| 393 |
// ---- Physique : chargement Ammo en parallèle, colliders après 1ère frame ----
|
| 394 |
-
let physicsReady = false;
|
| 395 |
if (wantPhysics) {
|
| 396 |
(async () => {
|
| 397 |
try {
|
| 398 |
-
const ok = await loadAmmoWithTimeout(ammoBaseUrl, isMobileUA() ?
|
| 399 |
if (!ok) { step('P: Ammo unavailable — rendering without physics'); return; }
|
| 400 |
|
| 401 |
-
// Pose des colliders mesh après 1ère frame pour garantir meshInstances
|
| 402 |
if (envEntity) {
|
| 403 |
app.once('postrender', () => {
|
| 404 |
const applyStaticMesh = (e) => {
|
| 405 |
-
if (e.render && !e.collision) {
|
| 406 |
-
|
| 407 |
-
}
|
| 408 |
-
if (!e.rigidbody) {
|
| 409 |
-
try { e.addComponent('rigidbody', { type: 'static', friction: 0.6, restitution: 0.0 }); } catch (err) { console.warn('[phys] add rigidbody failed:', err); }
|
| 410 |
-
} else if (e.rigidbody && e.rigidbody.type !== 'static') {
|
| 411 |
-
e.rigidbody.type = 'static';
|
| 412 |
-
}
|
| 413 |
};
|
| 414 |
const stack=[envEntity];
|
| 415 |
while (stack.length){ const n=stack.pop(); applyStaticMesh(n); n.children?.forEach(c=>stack.push(c)); }
|
|
@@ -417,9 +443,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 417 |
});
|
| 418 |
}
|
| 419 |
|
| 420 |
-
// Crée Player capsule et reparent caméra
|
| 421 |
createPlayerCapsuleAndAttachCamera(app, config);
|
| 422 |
-
physicsReady = true;
|
| 423 |
step('Q: physics ON (player created)');
|
| 424 |
} catch (e) {
|
| 425 |
console.warn('[phys] init failed:', e);
|
|
@@ -458,7 +482,6 @@ export async function initializeViewer(config, instanceId) {
|
|
| 458 |
Helpers: création Player capsule + cam
|
| 459 |
-------------------------------------------- */
|
| 460 |
function createPlayerCapsuleAndAttachCamera(app, config) {
|
| 461 |
-
// Si déjà présent, ne pas recréer
|
| 462 |
if (playerEntity && playerEntity.rigidbody) { step('Q+: player already exists'); return; }
|
| 463 |
|
| 464 |
playerEntity = new pc.Entity('Player');
|
|
@@ -477,14 +500,12 @@ function createPlayerCapsuleAndAttachCamera(app, config) {
|
|
| 477 |
linearDamping: 0.15,
|
| 478 |
angularDamping: 0.999
|
| 479 |
});
|
| 480 |
-
playerEntity.rigidbody.angularFactor = new pc.Vec3(0, 0, 0);
|
| 481 |
} catch (e) { console.warn('[phys] add rigidbody failed:', e); }
|
| 482 |
|
| 483 |
-
// Spawn
|
| 484 |
playerEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 485 |
app.root.addChild(playerEntity);
|
| 486 |
|
| 487 |
-
// Caméra -> enfant Player (yeux)
|
| 488 |
const eyes = config.eyesOffsetY !== undefined ? parseFloat(config.eyesOffsetY) : Math.max(0.1, capsuleHeight * 0.9);
|
| 489 |
try {
|
| 490 |
cameraEntity.reparent(playerEntity);
|
|
@@ -492,7 +513,6 @@ function createPlayerCapsuleAndAttachCamera(app, config) {
|
|
| 492 |
cameraEntity.setLocalEulerAngles(0, 0, 0);
|
| 493 |
} catch (e) { console.warn('[phys] camera reparent failed:', e); }
|
| 494 |
|
| 495 |
-
// Gravité selon freeFly
|
| 496 |
const freeFly = !!config.freeFly;
|
| 497 |
app.scene.gravity = freeFly ? new pc.Vec3(0,0,0) : new pc.Vec3(0,-9.81,0);
|
| 498 |
}
|
|
|
|
| 1 |
+
// viewer_pr_env.js — robuste v2 (multi‑CDN PlayCanvas import)
|
| 2 |
// ============================================================================
|
| 3 |
+
// - Boot non-bloquant : l'app démarre tout de suite (caméra au root)
|
| 4 |
+
// - Import PlayCanvas **robuste** : ESM multi‑CDN (esm.run → jsDelivr → unpkg)
|
| 5 |
+
// puis fallback **UMD** (playcanvas.js) si ESM bloqué par CSP/iframe
|
| 6 |
+
// - Ammo (WASM) en parallèle, timeout + double API (promise/callback)
|
| 7 |
+
// - Colliders mesh ajoutés après 1ʳᵉ frame (postrender)
|
| 8 |
// - Player capsule créé seulement si Ammo OK, puis caméra reparentée
|
| 9 |
+
// - GSplat/GLB chargés avec logs + timeout ; DPR dynamique ; logs jalons
|
| 10 |
+
// - Ne charge pas le script de contrôle caméra (à ajouter côté scène)
|
| 11 |
// ============================================================================
|
| 12 |
|
| 13 |
/* -------------------------------------------
|
|
|
|
| 91 |
return isIOS || isAndroid;
|
| 92 |
}
|
| 93 |
|
| 94 |
+
async function importPlayCanvasRobust(timeoutMs = 6000) {
|
| 95 |
+
const sourcesESM = [
|
| 96 |
+
'https://esm.run/playcanvas',
|
| 97 |
+
'https://cdn.jsdelivr.net/npm/playcanvas@latest/build/playcanvas.mjs',
|
| 98 |
+
'https://unpkg.com/playcanvas@latest/build/playcanvas.mjs'
|
| 99 |
+
];
|
| 100 |
+
|
| 101 |
+
// Essais ESM
|
| 102 |
+
for (const url of sourcesESM) {
|
| 103 |
+
try {
|
| 104 |
+
console.log('[PC-IMPORT] Try ESM:', url);
|
| 105 |
+
const mod = await Promise.race([
|
| 106 |
+
import(/* @vite-ignore */ url),
|
| 107 |
+
new Promise((_, rej) => setTimeout(() => rej(new Error('ESM timeout')), timeoutMs))
|
| 108 |
+
]);
|
| 109 |
+
if (mod) {
|
| 110 |
+
console.log('[PC-IMPORT] ESM OK:', url);
|
| 111 |
+
return mod; // module ESM
|
| 112 |
+
}
|
| 113 |
+
} catch (e) {
|
| 114 |
+
console.warn('[PC-IMPORT] ESM failed:', url, e);
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// Fallback UMD
|
| 119 |
+
const sourcesUMD = [
|
| 120 |
+
'https://cdn.jsdelivr.net/npm/playcanvas@latest/build/playcanvas.js',
|
| 121 |
+
'https://unpkg.com/playcanvas@latest/build/playcanvas.js'
|
| 122 |
+
];
|
| 123 |
+
for (const url of sourcesUMD) {
|
| 124 |
+
try {
|
| 125 |
+
console.log('[PC-IMPORT] Try UMD:', url);
|
| 126 |
+
await new Promise((resolve, reject) => {
|
| 127 |
+
const s = document.createElement('script');
|
| 128 |
+
s.src = url; s.async = true; s.onload = resolve; s.onerror = reject;
|
| 129 |
+
document.head.appendChild(s);
|
| 130 |
+
});
|
| 131 |
+
if (window.pc) { console.log('[PC-IMPORT] UMD OK:', url); return window.pc; }
|
| 132 |
+
} catch (e) {
|
| 133 |
+
console.warn('[PC-IMPORT] UMD failed:', url, e);
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
throw new Error('PlayCanvas import failed on all sources');
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
async function loadAmmoWithTimeout(baseUrl, timeoutMs = 4000) {
|
|
|
|
| 140 |
try {
|
| 141 |
pc.WasmModule.setConfig('Ammo', {
|
| 142 |
glueUrl: `${baseUrl}ammo.wasm.js`,
|
|
|
|
| 147 |
console.warn('[Ammo] setConfig failed (ignored):', e);
|
| 148 |
}
|
| 149 |
|
| 150 |
+
// 1) API Promise
|
| 151 |
try {
|
| 152 |
const maybePromise = pc.WasmModule.getInstance('Ammo', `${baseUrl}ammo.wasm.js`);
|
| 153 |
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
|
|
| 163 |
console.warn('[Ammo] promise API failed:', e);
|
| 164 |
}
|
| 165 |
|
| 166 |
+
// 2) API callback
|
| 167 |
let resolved = false;
|
| 168 |
try {
|
| 169 |
step('Ammo: waiting (callback API)…');
|
|
|
|
| 171 |
new Promise((resolve) => pc.WasmModule.getInstance('Ammo', () => { resolved = true; resolve(); })),
|
| 172 |
new Promise((_, rej) => setTimeout(() => rej(new Error('Ammo load timeout (callback)')), timeoutMs))
|
| 173 |
]);
|
| 174 |
+
if (resolved) { step('Ammo: ready (callback API)'); return true; }
|
|
|
|
|
|
|
|
|
|
| 175 |
} catch (e) {
|
| 176 |
console.warn('[Ammo] callback API failed:', e);
|
| 177 |
}
|
|
|
|
| 279 |
canvas.addEventListener('blur', onCanvasBlur);
|
| 280 |
|
| 281 |
// Empêche les touches scroll pendant survol
|
| 282 |
+
const scrollKeys = new Set(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','PageUp','PageDown','Home','End',' ','Space','Space','Spacebar']);
|
| 283 |
const onKeyDownCapture = (e) => { if (!isPointerOverCanvas) return; if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) e.preventDefault(); };
|
| 284 |
window.addEventListener('keydown', onKeyDownCapture, true);
|
| 285 |
|
| 286 |
if (progressDialog) progressDialog.style.display = 'block';
|
| 287 |
|
| 288 |
+
// ---- Import PlayCanvas (robuste) ----
|
| 289 |
if (!pc) {
|
| 290 |
+
step('B: importing PlayCanvas (robust)…');
|
| 291 |
+
try {
|
| 292 |
+
const modOrPc = await importPlayCanvasRobust( mobile ? 8000 : 6000 );
|
| 293 |
+
pc = modOrPc && modOrPc.Application ? modOrPc : (window.pc || modOrPc);
|
| 294 |
+
window.pc = pc; // debug convenience
|
| 295 |
+
step('B+: PlayCanvas imported OK');
|
| 296 |
+
} catch (e) {
|
| 297 |
+
console.error('[PC-IMPORT] FAILED:', e);
|
| 298 |
+
throw e; // surface d’erreurs l’affichera
|
| 299 |
+
}
|
| 300 |
}
|
| 301 |
|
| 302 |
// ---- Crée l’Application (démarrage non-bloquant) ----
|
|
|
|
| 377 |
const assets = [];
|
| 378 |
let sogAsset = null, glbAsset = null;
|
| 379 |
|
| 380 |
+
if (sogUrl) { sogAsset = new pc.Asset('gsplat', 'gsplat', { url: sogUrl }); app.assets.add(sogAsset); assets.push(sogAsset); }
|
| 381 |
+
if (glbUrl) { glbAsset = new pc.Asset('glb', 'container', { url: glbUrl }); app.assets.add(glbAsset); assets.push(glbAsset); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
step('F: requesting assets', { sog: !!sogUrl, glb: !!glbUrl });
|
| 384 |
|
| 385 |
await new Promise((resolve) => {
|
| 386 |
if (!assets.length) return resolve();
|
|
|
|
| 387 |
const loader = new pc.AssetListLoader(assets, app.assets);
|
| 388 |
let done = false;
|
| 389 |
const finish = (label) => { if (!done) { done = true; step('F+: assets loaded (' + label + ')'); resolve(); } };
|
|
|
|
| 390 |
loader.on('error', (e) => { console.warn('[viewer] Asset load error:', e); finish('with errors'); });
|
| 391 |
loader.load(() => finish('ok'));
|
|
|
|
|
|
|
| 392 |
setTimeout(() => { if (!done) { console.warn('[viewer] Asset load timeout — continuing'); finish('timeout'); } }, 10000);
|
| 393 |
});
|
| 394 |
|
|
|
|
| 404 |
|
| 405 |
// ---- Instancier le GLB d’environnement ----
|
| 406 |
envEntity = glbAsset && glbAsset.resource ? glbAsset.resource.instantiateRenderEntity() : null;
|
| 407 |
+
if (envEntity) { envEntity.name = 'ENV_GLTF'; app.root.addChild(envEntity); step('I: env GLB instanced'); }
|
| 408 |
+
else { console.warn('[viewer] No environment GLB — physics will be skipped.'); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
// ---- Matériau "fond uni" si pas d'espace expo ----
|
| 411 |
if (!espace_expo_bool && envEntity) {
|
|
|
|
| 424 |
}
|
| 425 |
|
| 426 |
// ---- Physique : chargement Ammo en parallèle, colliders après 1ère frame ----
|
|
|
|
| 427 |
if (wantPhysics) {
|
| 428 |
(async () => {
|
| 429 |
try {
|
| 430 |
+
const ok = await loadAmmoWithTimeout(ammoBaseUrl, isMobileUA() ? 8000 : 5000).catch(() => false);
|
| 431 |
if (!ok) { step('P: Ammo unavailable — rendering without physics'); return; }
|
| 432 |
|
|
|
|
| 433 |
if (envEntity) {
|
| 434 |
app.once('postrender', () => {
|
| 435 |
const applyStaticMesh = (e) => {
|
| 436 |
+
if (e.render && !e.collision) { try { e.addComponent('collision', { type: 'mesh' }); } catch (err) { console.warn('[phys] add collision failed:', err); } }
|
| 437 |
+
if (!e.rigidbody) { try { e.addComponent('rigidbody', { type: 'static', friction: 0.6, restitution: 0.0 }); } catch (err) { console.warn('[phys] add rigidbody failed:', err); } }
|
| 438 |
+
else if (e.rigidbody && e.rigidbody.type !== 'static') { e.rigidbody.type = 'static'; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 439 |
};
|
| 440 |
const stack=[envEntity];
|
| 441 |
while (stack.length){ const n=stack.pop(); applyStaticMesh(n); n.children?.forEach(c=>stack.push(c)); }
|
|
|
|
| 443 |
});
|
| 444 |
}
|
| 445 |
|
|
|
|
| 446 |
createPlayerCapsuleAndAttachCamera(app, config);
|
|
|
|
| 447 |
step('Q: physics ON (player created)');
|
| 448 |
} catch (e) {
|
| 449 |
console.warn('[phys] init failed:', e);
|
|
|
|
| 482 |
Helpers: création Player capsule + cam
|
| 483 |
-------------------------------------------- */
|
| 484 |
function createPlayerCapsuleAndAttachCamera(app, config) {
|
|
|
|
| 485 |
if (playerEntity && playerEntity.rigidbody) { step('Q+: player already exists'); return; }
|
| 486 |
|
| 487 |
playerEntity = new pc.Entity('Player');
|
|
|
|
| 500 |
linearDamping: 0.15,
|
| 501 |
angularDamping: 0.999
|
| 502 |
});
|
| 503 |
+
playerEntity.rigidbody.angularFactor = new pc.Vec3(0, 0, 0);
|
| 504 |
} catch (e) { console.warn('[phys] add rigidbody failed:', e); }
|
| 505 |
|
|
|
|
| 506 |
playerEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
| 507 |
app.root.addChild(playerEntity);
|
| 508 |
|
|
|
|
| 509 |
const eyes = config.eyesOffsetY !== undefined ? parseFloat(config.eyesOffsetY) : Math.max(0.1, capsuleHeight * 0.9);
|
| 510 |
try {
|
| 511 |
cameraEntity.reparent(playerEntity);
|
|
|
|
| 513 |
cameraEntity.setLocalEulerAngles(0, 0, 0);
|
| 514 |
} catch (e) { console.warn('[phys] camera reparent failed:', e); }
|
| 515 |
|
|
|
|
| 516 |
const freeFly = !!config.freeFly;
|
| 517 |
app.scene.gravity = freeFly ? new pc.Vec3(0,0,0) : new pc.Vec3(0,-9.81,0);
|
| 518 |
}
|