Spaces:
Running
Running
Update js_scripts/index.js
Browse files- js_scripts/index.js +310 -114
js_scripts/index.js
CHANGED
@@ -1,6 +1,36 @@
|
|
|
|
|
|
|
|
1 |
(async function() {
|
2 |
-
//
|
3 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
const configUrl = scriptTag.getAttribute("data-config");
|
5 |
let config = {};
|
6 |
if (configUrl) {
|
@@ -24,12 +54,10 @@
|
|
24 |
document.head.appendChild(linkEl);
|
25 |
}
|
26 |
|
27 |
-
// --- Outer scope variables
|
28 |
-
let
|
29 |
-
let
|
30 |
-
let
|
31 |
-
let initialCameraRotation = null;
|
32 |
-
let SPLAT = null; // We'll save the imported SPLAT module here.
|
33 |
|
34 |
// Generate a unique identifier for this widget instance.
|
35 |
const instanceId = Math.random().toString(36).substr(2, 8);
|
@@ -37,31 +65,45 @@
|
|
37 |
// Read configuration values from the JSON file.
|
38 |
const gifUrl = config.gif_url;
|
39 |
const plyUrl = config.ply_url;
|
40 |
-
|
|
|
|
|
41 |
const maxZoom = parseFloat(config.maxZoom || "20");
|
42 |
-
const minAngle = parseFloat(config.minAngle || "
|
43 |
-
const maxAngle = parseFloat(config.maxAngle || "
|
44 |
-
const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -
|
45 |
-
const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) :
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
-
//
|
48 |
-
const
|
49 |
-
const
|
50 |
-
const
|
51 |
-
// Read initial orbit parameters for phone.
|
52 |
-
const initAlphaPhone = config.initAlphaPhone !== undefined ? parseFloat(config.initAlphaPhone) : initAlphaDesktop;
|
53 |
-
const initBetaPhone = config.initBetaPhone !== undefined ? parseFloat(config.initBetaPhone) : initBetaDesktop;
|
54 |
-
const initRadiusPhone = config.initRadiusPhone !== undefined ? parseFloat(config.initRadiusPhone) : initRadiusDesktop;
|
55 |
|
56 |
// Detect if the device is iOS.
|
57 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
58 |
// Also detect Android devices.
|
59 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
60 |
|
61 |
-
// Choose the appropriate
|
62 |
-
const
|
63 |
-
const
|
64 |
-
const
|
65 |
|
66 |
// Determine the aspect ratio.
|
67 |
let aspectPercent = "100%";
|
@@ -234,42 +276,76 @@
|
|
234 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
235 |
});
|
236 |
|
237 |
-
// --- Camera Reset Function ---
|
238 |
function resetCamera() {
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
}
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
true,
|
256 |
-
new SPLAT.Vector3(),
|
257 |
-
chosenInitAlpha,
|
258 |
-
chosenInitBeta,
|
259 |
-
chosenInitRadius
|
260 |
-
);
|
261 |
-
controlsInstance.maxZoom = maxZoom;
|
262 |
-
controlsInstance.minZoom = minZoom;
|
263 |
-
controlsInstance.minAngle = minAngle;
|
264 |
-
controlsInstance.maxAngle = maxAngle;
|
265 |
-
controlsInstance.minAzimuth = minAzimuth;
|
266 |
-
controlsInstance.maxAzimuth = maxAzimuth;
|
267 |
-
controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
|
268 |
-
controlsInstance.update();
|
269 |
}
|
270 |
}
|
271 |
|
272 |
-
resetCameraBtn.addEventListener('click',
|
273 |
console.log("Reset camera button clicked.");
|
274 |
resetCamera();
|
275 |
});
|
@@ -294,70 +370,190 @@
|
|
294 |
}
|
295 |
});
|
296 |
|
297 |
-
// --- Initialize the 3D PLY Viewer ---
|
298 |
async function initializeViewer() {
|
299 |
-
SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
|
300 |
progressDialog.style.display = 'block';
|
301 |
-
const renderer = new SPLAT.WebGLRenderer(canvas);
|
302 |
-
const scene = new SPLAT.Scene();
|
303 |
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
true,
|
313 |
-
new SPLAT.Vector3(),
|
314 |
-
chosenInitAlpha,
|
315 |
-
chosenInitBeta,
|
316 |
-
chosenInitRadius
|
317 |
-
);
|
318 |
-
|
319 |
-
cameraInstance = camera;
|
320 |
-
initialCameraPosition = camera.position.clone();
|
321 |
-
initialCameraRotation = camera.rotation.clone();
|
322 |
-
|
323 |
-
canvas.style.background = config.canvas_background || "#FEFEFD";
|
324 |
|
325 |
-
controlsInstance.maxZoom = maxZoom;
|
326 |
-
controlsInstance.minZoom = minZoom;
|
327 |
-
controlsInstance.minAngle = minAngle;
|
328 |
-
controlsInstance.maxAngle = maxAngle;
|
329 |
-
controlsInstance.minAzimuth = minAzimuth;
|
330 |
-
controlsInstance.maxAzimuth = maxAzimuth;
|
331 |
-
controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
|
332 |
-
|
333 |
-
controlsInstance.update();
|
334 |
-
|
335 |
try {
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
341 |
}
|
342 |
-
|
343 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
344 |
} catch (error) {
|
345 |
-
console.error("Error
|
346 |
-
progressDialog.innerHTML = `<p style="color: red">Error loading
|
347 |
}
|
348 |
-
|
349 |
-
const frame = () => {
|
350 |
-
controlsInstance.update();
|
351 |
-
renderer.render(scene, camera);
|
352 |
-
requestAnimationFrame(frame);
|
353 |
-
};
|
354 |
-
|
355 |
-
const handleResize = () => {
|
356 |
-
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
357 |
-
};
|
358 |
-
|
359 |
-
handleResize();
|
360 |
-
window.addEventListener("resize", handleResize);
|
361 |
-
requestAnimationFrame(frame);
|
362 |
}
|
363 |
-
})();
|
|
|
1 |
+
// Store the current script reference before the async function runs
|
2 |
+
const currentScriptTag = document.currentScript;
|
3 |
+
|
4 |
(async function() {
|
5 |
+
// Import PlayCanvas
|
6 |
+
const pc = await import("https://cdn.skypack.dev/[email protected]");
|
7 |
+
window.pc = pc;
|
8 |
+
|
9 |
+
// Find the script tag using a more reliable method
|
10 |
+
let scriptTag = currentScriptTag;
|
11 |
+
|
12 |
+
// Fallback method if currentScriptTag is null
|
13 |
+
if (!scriptTag) {
|
14 |
+
const scripts = document.getElementsByTagName('script');
|
15 |
+
for (let i = 0; i < scripts.length; i++) {
|
16 |
+
if (scripts[i].src.includes('index.js') && scripts[i].hasAttribute('data-config')) {
|
17 |
+
scriptTag = scripts[i];
|
18 |
+
break;
|
19 |
+
}
|
20 |
+
}
|
21 |
+
|
22 |
+
// If still not found, try the last script on the page
|
23 |
+
if (!scriptTag && scripts.length > 0) {
|
24 |
+
scriptTag = scripts[scripts.length - 1];
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
// Check if we found a script tag
|
29 |
+
if (!scriptTag) {
|
30 |
+
console.error("Could not find the script tag with data-config attribute.");
|
31 |
+
return;
|
32 |
+
}
|
33 |
+
|
34 |
const configUrl = scriptTag.getAttribute("data-config");
|
35 |
let config = {};
|
36 |
if (configUrl) {
|
|
|
54 |
document.head.appendChild(linkEl);
|
55 |
}
|
56 |
|
57 |
+
// --- Outer scope variables ---
|
58 |
+
let cameraEntity = null;
|
59 |
+
let app = null;
|
60 |
+
let modelEntity = null;
|
|
|
|
|
61 |
|
62 |
// Generate a unique identifier for this widget instance.
|
63 |
const instanceId = Math.random().toString(36).substr(2, 8);
|
|
|
65 |
// Read configuration values from the JSON file.
|
66 |
const gifUrl = config.gif_url;
|
67 |
const plyUrl = config.ply_url;
|
68 |
+
|
69 |
+
// Camera constraint parameters
|
70 |
+
const minZoom = parseFloat(config.minZoom || "1");
|
71 |
const maxZoom = parseFloat(config.maxZoom || "20");
|
72 |
+
const minAngle = parseFloat(config.minAngle || "-45");
|
73 |
+
const maxAngle = parseFloat(config.maxAngle || "90");
|
74 |
+
const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -360;
|
75 |
+
const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
|
76 |
+
|
77 |
+
// Model position, scale, and rotation parameters
|
78 |
+
const modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
|
79 |
+
const modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
|
80 |
+
const modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
|
81 |
+
|
82 |
+
const modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
|
83 |
+
|
84 |
+
const modelRotationX = config.modelRotationX !== undefined ? parseFloat(config.modelRotationX) : 0;
|
85 |
+
const modelRotationY = config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
|
86 |
+
const modelRotationZ = config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
|
87 |
+
|
88 |
+
// Direct camera coordinates
|
89 |
+
const cameraX = config.cameraX !== undefined ? parseFloat(config.cameraX) : 0;
|
90 |
+
const cameraY = config.cameraY !== undefined ? parseFloat(config.cameraY) : 2;
|
91 |
+
const cameraZ = config.cameraZ !== undefined ? parseFloat(config.cameraZ) : 5;
|
92 |
|
93 |
+
// Camera coordinates for mobile devices
|
94 |
+
const cameraXPhone = config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
|
95 |
+
const cameraYPhone = config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
|
96 |
+
const cameraZPhone = config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
|
|
|
|
|
|
|
|
|
97 |
|
98 |
// Detect if the device is iOS.
|
99 |
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
100 |
// Also detect Android devices.
|
101 |
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
102 |
|
103 |
+
// Choose the appropriate coordinates based on device type
|
104 |
+
const chosenCameraX = isMobile ? cameraXPhone : cameraX;
|
105 |
+
const chosenCameraY = isMobile ? cameraYPhone : cameraY;
|
106 |
+
const chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
|
107 |
|
108 |
// Determine the aspect ratio.
|
109 |
let aspectPercent = "100%";
|
|
|
276 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
277 |
});
|
278 |
|
279 |
+
// --- Camera Reset Function - FIXED TO PRESERVE CAMERA MOVEMENT ---
|
280 |
function resetCamera() {
|
281 |
+
if (!cameraEntity || !modelEntity || !app) return;
|
282 |
+
|
283 |
+
try {
|
284 |
+
// Get the orbit camera script
|
285 |
+
const orbitCam = cameraEntity.script.orbitCamera;
|
286 |
+
if (!orbitCam) return;
|
287 |
+
|
288 |
+
// Store model position
|
289 |
+
const modelPos = modelEntity.getPosition();
|
290 |
+
|
291 |
+
// 1. We'll create a temporary entity to help us calculate new values
|
292 |
+
const tempEntity = new pc.Entity();
|
293 |
+
tempEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
294 |
+
tempEntity.lookAt(modelPos);
|
295 |
+
|
296 |
+
// 2. Calculate the distance between camera and model
|
297 |
+
const distance = new pc.Vec3().sub2(
|
298 |
+
new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ),
|
299 |
+
modelPos
|
300 |
+
).length();
|
301 |
+
|
302 |
+
// 3. Set camera position
|
303 |
+
cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
304 |
+
cameraEntity.lookAt(modelPos);
|
305 |
+
|
306 |
+
// 4. Create and apply a new transform for the orbit camera
|
307 |
+
orbitCam.pivotPoint = new pc.Vec3(modelPos.x, modelPos.y, modelPos.z);
|
308 |
+
|
309 |
+
// 5. Set the distance and remove inertia
|
310 |
+
orbitCam._targetDistance = distance;
|
311 |
+
orbitCam._distance = distance;
|
312 |
+
|
313 |
+
// 6. Reset internal yaw and pitch values (this is critical)
|
314 |
+
// Get the current camera rotation
|
315 |
+
const rotation = tempEntity.getRotation();
|
316 |
+
const tempForward = new pc.Vec3();
|
317 |
+
rotation.transformVector(pc.Vec3.FORWARD, tempForward);
|
318 |
+
|
319 |
+
// Calculate yaw from the forward vector
|
320 |
+
const yaw = Math.atan2(-tempForward.x, -tempForward.z) * pc.math.RAD_TO_DEG;
|
321 |
+
|
322 |
+
// Calculate pitch
|
323 |
+
const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
|
324 |
+
const rotWithoutYaw = new pc.Quat().mul2(yawQuat, rotation);
|
325 |
+
const forwardWithoutYaw = new pc.Vec3();
|
326 |
+
rotWithoutYaw.transformVector(pc.Vec3.FORWARD, forwardWithoutYaw);
|
327 |
+
const pitch = Math.atan2(forwardWithoutYaw.y, -forwardWithoutYaw.z) * pc.math.RAD_TO_DEG;
|
328 |
+
|
329 |
+
// Set yaw and pitch directly on the internal variables
|
330 |
+
orbitCam._targetYaw = yaw;
|
331 |
+
orbitCam._yaw = yaw;
|
332 |
+
orbitCam._targetPitch = pitch;
|
333 |
+
orbitCam._pitch = pitch;
|
334 |
+
|
335 |
+
// 7. Force an update of the camera position
|
336 |
+
if (typeof orbitCam._updatePosition === 'function') {
|
337 |
+
orbitCam._updatePosition();
|
338 |
}
|
339 |
+
|
340 |
+
// Clean up
|
341 |
+
tempEntity.destroy();
|
342 |
+
|
343 |
+
} catch (error) {
|
344 |
+
console.error("Error resetting camera:", error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
}
|
346 |
}
|
347 |
|
348 |
+
resetCameraBtn.addEventListener('click', function() {
|
349 |
console.log("Reset camera button clicked.");
|
350 |
resetCamera();
|
351 |
});
|
|
|
370 |
}
|
371 |
});
|
372 |
|
373 |
+
// --- Initialize the 3D PLY Viewer using PlayCanvas ---
|
374 |
async function initializeViewer() {
|
|
|
375 |
progressDialog.style.display = 'block';
|
|
|
|
|
376 |
|
377 |
+
// Initialize PlayCanvas
|
378 |
+
const deviceType = "webgl2";
|
379 |
+
const gfxOptions = {
|
380 |
+
deviceTypes: [deviceType],
|
381 |
+
glslangUrl: `https://playcanvas.vercel.app/static/lib/glslang/glslang.js`,
|
382 |
+
twgslUrl: `https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js`,
|
383 |
+
antialias: false
|
384 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
386 |
try {
|
387 |
+
// Create graphics device
|
388 |
+
const device = await pc.createGraphicsDevice(canvas, gfxOptions);
|
389 |
+
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
|
390 |
+
|
391 |
+
// Create app
|
392 |
+
const createOptions = new pc.AppOptions();
|
393 |
+
createOptions.graphicsDevice = device;
|
394 |
+
createOptions.mouse = new pc.Mouse(document.body);
|
395 |
+
createOptions.touch = new pc.TouchDevice(document.body);
|
396 |
+
createOptions.componentSystems = [
|
397 |
+
pc.RenderComponentSystem,
|
398 |
+
pc.CameraComponentSystem,
|
399 |
+
pc.LightComponentSystem,
|
400 |
+
pc.ScriptComponentSystem,
|
401 |
+
pc.GSplatComponentSystem
|
402 |
+
];
|
403 |
+
createOptions.resourceHandlers = [
|
404 |
+
pc.TextureHandler,
|
405 |
+
pc.ContainerHandler,
|
406 |
+
pc.ScriptHandler,
|
407 |
+
pc.GSplatHandler
|
408 |
+
];
|
409 |
+
|
410 |
+
app = new pc.AppBase(canvas);
|
411 |
+
app.init(createOptions);
|
412 |
+
|
413 |
+
// Set canvas fill mode to match the container
|
414 |
+
app.setCanvasFillMode(pc.FILLMODE_NONE);
|
415 |
+
app.setCanvasResolution(pc.RESOLUTION_AUTO);
|
416 |
+
|
417 |
+
// Set scene options
|
418 |
+
app.scene.exposure = 0.8;
|
419 |
+
app.scene.toneMapping = pc.TONEMAP_ACES;
|
420 |
+
|
421 |
+
// Handle window resizing
|
422 |
+
const resize = () => {
|
423 |
+
if (app) {
|
424 |
+
app.resizeCanvas(canvas.clientWidth, canvas.clientHeight);
|
425 |
}
|
426 |
+
};
|
427 |
+
window.addEventListener('resize', resize);
|
428 |
+
app.on('destroy', () => window.removeEventListener('resize', resize));
|
429 |
+
|
430 |
+
// Load required assets
|
431 |
+
const assets = {
|
432 |
+
model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
|
433 |
+
orbit: new pc.Asset('script', 'script', { url: `https://bilca-visionneur-play-canva-2.static.hf.space/orbit-camera.js` })
|
434 |
+
};
|
435 |
+
|
436 |
+
// Create asset loader with progress tracking
|
437 |
+
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
|
438 |
+
|
439 |
+
// Handle asset loading progress
|
440 |
+
let lastProgress = 0;
|
441 |
+
assets.model.on('load', (asset) => {
|
442 |
+
progressDialog.style.display = 'none';
|
443 |
+
});
|
444 |
+
|
445 |
+
assets.model.on('error', (err) => {
|
446 |
+
console.error("Error loading PLY file:", err);
|
447 |
+
progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
|
448 |
+
});
|
449 |
+
|
450 |
+
// Set up progress monitoring
|
451 |
+
const checkProgress = () => {
|
452 |
+
if (app && assets.model.resource) {
|
453 |
+
progressIndicator.value = 100;
|
454 |
+
clearInterval(progressChecker);
|
455 |
+
progressDialog.style.display = 'none';
|
456 |
+
} else if (assets.model.loading) {
|
457 |
+
// Increment progress for visual feedback
|
458 |
+
lastProgress += 2;
|
459 |
+
if (lastProgress > 90) lastProgress = 90; // Cap at 90% until fully loaded
|
460 |
+
progressIndicator.value = lastProgress;
|
461 |
+
}
|
462 |
+
};
|
463 |
+
|
464 |
+
const progressChecker = setInterval(checkProgress, 100);
|
465 |
+
|
466 |
+
// Load assets and set up scene
|
467 |
+
assetListLoader.load(() => {
|
468 |
+
app.start();
|
469 |
+
|
470 |
+
// Create model entity
|
471 |
+
modelEntity = new pc.Entity('model');
|
472 |
+
modelEntity.addComponent('gsplat', {
|
473 |
+
asset: assets.model
|
474 |
+
});
|
475 |
+
|
476 |
+
// Position the model using JSON parameters
|
477 |
+
modelEntity.setLocalPosition(modelX, modelY, modelZ);
|
478 |
+
modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
479 |
+
modelEntity.setLocalScale(modelScale, modelScale, modelScale);
|
480 |
+
|
481 |
+
app.root.addChild(modelEntity);
|
482 |
+
|
483 |
+
// Create camera entity
|
484 |
+
cameraEntity = new pc.Entity('camera');
|
485 |
+
cameraEntity.addComponent('camera', {
|
486 |
+
clearColor: new pc.Color(
|
487 |
+
config.canvas_background ? parseInt(config.canvas_background.substr(1, 2), 16) / 255 : 0,
|
488 |
+
config.canvas_background ? parseInt(config.canvas_background.substr(3, 2), 16) / 255 : 0,
|
489 |
+
config.canvas_background ? parseInt(config.canvas_background.substr(5, 2), 16) / 255 : 0
|
490 |
+
),
|
491 |
+
toneMapping: pc.TONEMAP_ACES
|
492 |
+
});
|
493 |
+
|
494 |
+
// Set camera position directly using X, Y, Z coordinates from config
|
495 |
+
cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
|
496 |
+
cameraEntity.lookAt(modelEntity.getPosition());
|
497 |
+
|
498 |
+
// Add orbit camera script for interactive navigation
|
499 |
+
cameraEntity.addComponent('script');
|
500 |
+
cameraEntity.script.create('orbitCamera', {
|
501 |
+
attributes: {
|
502 |
+
inertiaFactor: 0.2,
|
503 |
+
focusEntity: modelEntity,
|
504 |
+
distanceMax: maxZoom,
|
505 |
+
distanceMin: minZoom,
|
506 |
+
pitchAngleMax: maxAngle,
|
507 |
+
pitchAngleMin: minAngle,
|
508 |
+
yawAngleMax: maxAzimuth,
|
509 |
+
yawAngleMin: minAzimuth,
|
510 |
+
frameOnStart: false // Don't auto-frame since we're setting position directly
|
511 |
+
}
|
512 |
+
});
|
513 |
+
|
514 |
+
// Initialize the orbit controller to match our direct coordinates
|
515 |
+
setTimeout(() => {
|
516 |
+
if (cameraEntity.script.orbitCamera) {
|
517 |
+
// Calculate distance from camera to model
|
518 |
+
const modelPos = modelEntity.getPosition();
|
519 |
+
const camPos = cameraEntity.getPosition();
|
520 |
+
const distanceVec = new pc.Vec3();
|
521 |
+
distanceVec.sub2(camPos, modelPos);
|
522 |
+
const distance = distanceVec.length();
|
523 |
+
|
524 |
+
// Set up the orbit controller
|
525 |
+
cameraEntity.script.orbitCamera.pivotPoint.copy(modelPos);
|
526 |
+
cameraEntity.script.orbitCamera.distance = distance;
|
527 |
+
cameraEntity.script.orbitCamera._removeInertia();
|
528 |
+
}
|
529 |
+
}, 100);
|
530 |
+
|
531 |
+
// Add input controllers
|
532 |
+
cameraEntity.script.create('orbitCameraInputMouse', {
|
533 |
+
attributes: {
|
534 |
+
orbitSensitivity: isMobile ? 0.6 : 0.3,
|
535 |
+
distanceSensitivity: isMobile ? 0.5 : 0.4
|
536 |
+
}
|
537 |
+
});
|
538 |
+
cameraEntity.script.create('orbitCameraInputTouch', {
|
539 |
+
attributes: {
|
540 |
+
orbitSensitivity: 0.6,
|
541 |
+
distanceSensitivity: 0.5
|
542 |
+
}
|
543 |
+
});
|
544 |
+
|
545 |
+
app.root.addChild(cameraEntity);
|
546 |
+
|
547 |
+
// Initial resize to match container
|
548 |
+
resize();
|
549 |
+
|
550 |
+
// Hide progress dialog when everything is set up
|
551 |
+
progressDialog.style.display = 'none';
|
552 |
+
});
|
553 |
+
|
554 |
} catch (error) {
|
555 |
+
console.error("Error initializing PlayCanvas viewer:", error);
|
556 |
+
progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
|
557 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
558 |
}
|
559 |
+
})();
|