bilca commited on
Commit
66c76d6
·
verified ·
1 Parent(s): 009c9d7

Create galerie_double_v/script_double_v.js

Browse files
js_scripts/galerie_double_v/script_double_v.js ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (async function() {
2
+ // Retrieve the current script tag and load the JSON configuration file from the data-config attribute.
3
+ const scriptTag = document.currentScript;
4
+ const configUrl = scriptTag.getAttribute("data-config");
5
+ let config = {};
6
+ if (configUrl) {
7
+ try {
8
+ const response = await fetch(configUrl);
9
+ config = await response.json();
10
+ } catch (error) {
11
+ console.error("Error loading config file:", error);
12
+ return;
13
+ }
14
+ } else {
15
+ console.error("No config file provided. Please set a data-config attribute on the script tag.");
16
+ return;
17
+ }
18
+
19
+ // Load the external CSS file if provided in the config.
20
+ if (config.css_url) {
21
+ const linkEl = document.createElement("link");
22
+ linkEl.rel = "stylesheet";
23
+ linkEl.href = config.css_url;
24
+ document.head.appendChild(linkEl);
25
+ }
26
+
27
+ // --- Outer scope variables for camera and scene state ---
28
+ let cameraInstance = null;
29
+ let controlsInstance = null;
30
+ let initialCameraPosition = null;
31
+ let initialCameraRotation = null;
32
+ let SPLAT = null; // We'll save the imported SPLAT module here.
33
+
34
+ // Global variables for toggling the PLY models.
35
+ let scene, renderer, camera;
36
+ let currentQuality = "LQ"; // "LQ" or "HQ"
37
+
38
+ // Generate a unique identifier for this widget instance.
39
+ const instanceId = Math.random().toString(36).substr(2, 8);
40
+
41
+ // Read configuration values from the JSON file.
42
+ const gifUrl = config.gif_url;
43
+ const plyUrl = config.ply_url;
44
+ // HQ URL stored in the JSON as ply_url_hq.
45
+ const plyUrlHQ = config.ply_url_hq;
46
+ const minZoom = parseFloat(config.minZoom || "0");
47
+ const maxZoom = parseFloat(config.maxZoom || "20");
48
+ const minAngle = parseFloat(config.minAngle || "0");
49
+ const maxAngle = parseFloat(config.maxAngle || "360");
50
+ const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -Infinity;
51
+ const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : Infinity;
52
+
53
+ // Read initial orbit parameters for desktop.
54
+ const initAlphaDesktop = config.initAlpha !== undefined ? parseFloat(config.initAlpha) : 0.5;
55
+ const initBetaDesktop = config.initBeta !== undefined ? parseFloat(config.initBeta) : 0.5;
56
+ const initRadiusDesktop = config.initRadius !== undefined ? parseFloat(config.initRadius) : 5;
57
+ // Read initial orbit parameters for phone.
58
+ const initAlphaPhone = config.initAlphaPhone !== undefined ? parseFloat(config.initAlphaPhone) : initAlphaDesktop;
59
+ const initBetaPhone = config.initBetaPhone !== undefined ? parseFloat(config.initBetaPhone) : initBetaDesktop;
60
+ const initRadiusPhone = config.initRadiusPhone !== undefined ? parseFloat(config.initRadiusPhone) : initRadiusDesktop;
61
+
62
+ // Detect if the device is iOS.
63
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
64
+ // Also detect Android devices.
65
+ const isMobile = isIOS || /Android/i.test(navigator.userAgent);
66
+
67
+ // Choose the appropriate initial orbit values based on device type.
68
+ const chosenInitAlpha = isMobile ? initAlphaPhone : initAlphaDesktop;
69
+ const chosenInitBeta = isMobile ? initBetaPhone : initBetaDesktop;
70
+ const chosenInitRadius = isMobile ? initRadiusPhone : initRadiusDesktop;
71
+
72
+ // Determine the aspect ratio.
73
+ let aspectPercent = "100%";
74
+ if (config.aspect) {
75
+ if (config.aspect.indexOf(":") !== -1) {
76
+ const parts = config.aspect.split(":");
77
+ const w = parseFloat(parts[0]);
78
+ const h = parseFloat(parts[1]);
79
+ if (!isNaN(w) && !isNaN(h) && w > 0) {
80
+ aspectPercent = (h / w * 100) + "%";
81
+ }
82
+ } else {
83
+ const aspectValue = parseFloat(config.aspect);
84
+ if (!isNaN(aspectValue) && aspectValue > 0) {
85
+ aspectPercent = (100 / aspectValue) + "%";
86
+ }
87
+ }
88
+ } else {
89
+ const parentContainer = scriptTag.parentNode;
90
+ const containerWidth = parentContainer.offsetWidth;
91
+ const containerHeight = parentContainer.offsetHeight;
92
+ if (containerWidth > 0 && containerHeight > 0) {
93
+ aspectPercent = (containerHeight / containerWidth * 100) + "%";
94
+ }
95
+ }
96
+
97
+ // Create the widget container.
98
+ const widgetContainer = document.createElement('div');
99
+ widgetContainer.id = 'ply-widget-container-' + instanceId;
100
+ widgetContainer.classList.add('ply-widget-container');
101
+ // Add a mobile class if on a phone.
102
+ if (isMobile) {
103
+ widgetContainer.classList.add('mobile');
104
+ }
105
+ // Set inline style for aspect ratio.
106
+ widgetContainer.style.height = "0";
107
+ widgetContainer.style.paddingBottom = aspectPercent;
108
+
109
+ // Note the new toggle-quality button is added before the reset view button.
110
+ widgetContainer.innerHTML = `
111
+ <!-- GIF Preview Container -->
112
+ <div id="gif-preview-container-${instanceId}" class="gif-preview-container">
113
+ <img id="preview-image-${instanceId}" alt="Preview" crossorigin="anonymous">
114
+ </div>
115
+ <!-- Viewer Container -->
116
+ <div id="viewer-container-${instanceId}" class="viewer-container">
117
+ <canvas id="canvas-${instanceId}" class="ply-canvas"></canvas>
118
+ <div id="progress-dialog-${instanceId}" class="progress-dialog">
119
+ <progress id="progress-indicator-${instanceId}" max="100" value="0"></progress>
120
+ </div>
121
+ <button id="close-btn-${instanceId}" class="widget-button close-btn">X</button>
122
+ <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
123
+ <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
124
+ <button id="toggle-quality-btn-${instanceId}" class="widget-button toggle-quality-btn">charger HD (lent)</button>
125
+ <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn">
126
+ <span class="reset-icon">⟲</span>
127
+ </button>
128
+ <div id="menu-content-${instanceId}" class="menu-content"></div>
129
+ </div>
130
+ `;
131
+ scriptTag.parentNode.appendChild(widgetContainer);
132
+
133
+ // Grab element references.
134
+ const gifPreview = document.getElementById('gif-preview-container-' + instanceId);
135
+ const viewerContainer = document.getElementById('viewer-container-' + instanceId);
136
+ const previewImage = document.getElementById('preview-image-' + instanceId);
137
+ const closeBtn = document.getElementById('close-btn-' + instanceId);
138
+ const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
139
+ const helpToggle = document.getElementById('help-toggle-' + instanceId);
140
+ const resetCameraBtn = document.getElementById('reset-camera-btn-' + instanceId);
141
+ const toggleQualityBtn = document.getElementById('toggle-quality-btn-' + instanceId);
142
+ const menuContent = document.getElementById('menu-content-' + instanceId);
143
+ const canvas = document.getElementById('canvas-' + instanceId);
144
+ const progressDialog = document.getElementById('progress-dialog-' + instanceId);
145
+ const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
146
+
147
+ // Set help instructions based on device type.
148
+ if (isMobile) {
149
+ menuContent.innerHTML = `
150
+ - Pour vous déplacer, glissez deux doigts sur l'écran.<br>
151
+ - Pour orbiter, utilisez un doigt.<br>
152
+ - Pour zoomer, pincez avec deux doigts.
153
+ `;
154
+ } else {
155
+ menuContent.innerHTML = `
156
+ - orbitez avec le clic droit<br>
157
+ - zoomez avec la molette<br>
158
+ - déplacez vous avec le clic gauche
159
+ `;
160
+ }
161
+
162
+ // If a gif_url is provided, set the preview image.
163
+ // Otherwise, hide the preview container, show the viewer immediately,
164
+ // and hide the "close" button since there's no preview to return to.
165
+ if (gifUrl) {
166
+ previewImage.src = gifUrl;
167
+ } else {
168
+ gifPreview.style.display = 'none';
169
+ viewerContainer.style.display = 'block';
170
+ closeBtn.style.display = 'none';
171
+ initializeViewer();
172
+ }
173
+
174
+ // --- Button Event Handlers ---
175
+ if (gifUrl) {
176
+ gifPreview.addEventListener('click', function() {
177
+ gifPreview.style.display = 'none';
178
+ viewerContainer.style.display = 'block';
179
+ initializeViewer();
180
+ });
181
+ }
182
+
183
+ closeBtn.addEventListener('click', function() {
184
+ if (document.fullscreenElement === widgetContainer) {
185
+ if (document.exitFullscreen) {
186
+ document.exitFullscreen();
187
+ }
188
+ }
189
+ if (widgetContainer.classList.contains('fake-fullscreen')) {
190
+ widgetContainer.classList.remove('fake-fullscreen');
191
+ fullscreenToggle.textContent = '⇱';
192
+ resetCamera();
193
+ }
194
+ viewerContainer.style.display = 'none';
195
+ gifPreview.style.display = 'block';
196
+ });
197
+
198
+ fullscreenToggle.addEventListener('click', function() {
199
+ if (isIOS) {
200
+ if (!widgetContainer.classList.contains('fake-fullscreen')) {
201
+ widgetContainer.classList.add('fake-fullscreen');
202
+ } else {
203
+ widgetContainer.classList.remove('fake-fullscreen');
204
+ resetCamera();
205
+ }
206
+ fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
207
+ } else {
208
+ if (!document.fullscreenElement) {
209
+ if (widgetContainer.requestFullscreen) {
210
+ widgetContainer.requestFullscreen();
211
+ } else if (widgetContainer.webkitRequestFullscreen) {
212
+ widgetContainer.webkitRequestFullscreen();
213
+ } else if (widgetContainer.mozRequestFullScreen) {
214
+ widgetContainer.mozRequestFullScreen();
215
+ } else if (widgetContainer.msRequestFullscreen) {
216
+ widgetContainer.msRequestFullscreen();
217
+ }
218
+ } else {
219
+ if (document.exitFullscreen) {
220
+ document.exitFullscreen();
221
+ }
222
+ }
223
+ }
224
+ });
225
+
226
+ // Listen for native fullscreen changes.
227
+ document.addEventListener('fullscreenchange', function() {
228
+ if (document.fullscreenElement === widgetContainer) {
229
+ fullscreenToggle.textContent = '⇲';
230
+ widgetContainer.style.height = '100%';
231
+ widgetContainer.style.paddingBottom = '0';
232
+ resetCamera();
233
+ } else {
234
+ fullscreenToggle.textContent = '⇱';
235
+ widgetContainer.style.height = '0';
236
+ widgetContainer.style.paddingBottom = aspectPercent;
237
+ resetCamera();
238
+ }
239
+ });
240
+
241
+ helpToggle.addEventListener('click', function(e) {
242
+ e.stopPropagation();
243
+ menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
244
+ });
245
+
246
+ // --- Camera Reset Function ---
247
+ function resetCamera() {
248
+ console.log("Resetting camera to initial position.");
249
+ if (cameraInstance && initialCameraPosition && initialCameraRotation) {
250
+ cameraInstance.position = initialCameraPosition.clone();
251
+ cameraInstance.rotation = initialCameraRotation.clone();
252
+ if (typeof cameraInstance.update === 'function') {
253
+ cameraInstance.update();
254
+ }
255
+ if (controlsInstance && typeof controlsInstance.dispose === 'function') {
256
+ controlsInstance.dispose();
257
+ }
258
+ controlsInstance = new SPLAT.OrbitControls(
259
+ cameraInstance,
260
+ canvas,
261
+ 0.5,
262
+ 0.5,
263
+ 5,
264
+ true,
265
+ new SPLAT.Vector3(),
266
+ chosenInitAlpha,
267
+ chosenInitBeta,
268
+ chosenInitRadius
269
+ );
270
+ controlsInstance.maxZoom = maxZoom;
271
+ controlsInstance.minZoom = minZoom;
272
+ controlsInstance.minAngle = minAngle;
273
+ controlsInstance.maxAngle = maxAngle;
274
+ controlsInstance.minAzimuth = minAzimuth;
275
+ controlsInstance.maxAzimuth = maxAzimuth;
276
+ controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
277
+ controlsInstance.update();
278
+ }
279
+ }
280
+
281
+ resetCameraBtn.addEventListener('click', async function() {
282
+ console.log("Reset camera button clicked.");
283
+ resetCamera();
284
+ });
285
+
286
+ document.addEventListener('keydown', function(e) {
287
+ if (e.key === 'Escape' || e.key === 'Esc') {
288
+ let wasFullscreen = false;
289
+ if (document.fullscreenElement === widgetContainer) {
290
+ wasFullscreen = true;
291
+ if (document.exitFullscreen) {
292
+ document.exitFullscreen();
293
+ }
294
+ }
295
+ if (widgetContainer.classList.contains('fake-fullscreen')) {
296
+ wasFullscreen = true;
297
+ widgetContainer.classList.remove('fake-fullscreen');
298
+ fullscreenToggle.textContent = '⇱';
299
+ }
300
+ if (wasFullscreen) {
301
+ resetCamera();
302
+ }
303
+ }
304
+ });
305
+
306
+ // --- Helper Function to Load a PLY Model ---
307
+ async function loadModel(url) {
308
+ // Clear the current model from the scene.
309
+ if (scene && typeof scene.clear === 'function') {
310
+ scene.clear();
311
+ }
312
+ progressDialog.style.display = 'block';
313
+ progressIndicator.value = 0;
314
+ try {
315
+ await SPLAT.PLYLoader.LoadAsync(
316
+ url,
317
+ scene,
318
+ (progress) => {
319
+ progressIndicator.value = progress * 100;
320
+ }
321
+ );
322
+ progressDialog.style.display = 'none';
323
+ } catch (error) {
324
+ console.error("Error loading PLY file:", error);
325
+ progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
326
+ }
327
+ }
328
+
329
+ // --- Initialize the 3D PLY Viewer ---
330
+ async function initializeViewer() {
331
+ SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
332
+ progressDialog.style.display = 'block';
333
+
334
+ // Create the renderer, scene, and camera.
335
+ renderer = new SPLAT.WebGLRenderer(canvas);
336
+ scene = new SPLAT.Scene();
337
+ camera = new SPLAT.Camera();
338
+
339
+ // Set up orbit controls.
340
+ controlsInstance = new SPLAT.OrbitControls(
341
+ camera,
342
+ canvas,
343
+ 0.5,
344
+ 0.5,
345
+ 5,
346
+ true,
347
+ new SPLAT.Vector3(),
348
+ chosenInitAlpha,
349
+ chosenInitBeta,
350
+ chosenInitRadius
351
+ );
352
+
353
+ cameraInstance = camera;
354
+ initialCameraPosition = camera.position.clone();
355
+ initialCameraRotation = camera.rotation.clone();
356
+
357
+ canvas.style.background = config.canvas_background || "#FEFEFD";
358
+
359
+ controlsInstance.maxZoom = maxZoom;
360
+ controlsInstance.minZoom = minZoom;
361
+ controlsInstance.minAngle = minAngle;
362
+ controlsInstance.maxAngle = maxAngle;
363
+ controlsInstance.minAzimuth = minAzimuth;
364
+ controlsInstance.maxAzimuth = maxAzimuth;
365
+ controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
366
+
367
+ controlsInstance.update();
368
+
369
+ // Load the initial LQ model.
370
+ await loadModel(plyUrl);
371
+
372
+ const frame = () => {
373
+ controlsInstance.update();
374
+ renderer.render(scene, camera);
375
+ requestAnimationFrame(frame);
376
+ };
377
+
378
+ const handleResize = () => {
379
+ renderer.setSize(canvas.clientWidth, canvas.clientHeight);
380
+ };
381
+
382
+ handleResize();
383
+ window.addEventListener("resize", handleResize);
384
+ requestAnimationFrame(frame);
385
+ }
386
+
387
+ // --- Toggle Quality Button Handler ---
388
+ toggleQualityBtn.addEventListener('click', async function() {
389
+ // Do nothing if the viewer (and hence scene) is not yet initialized.
390
+ if (!scene) return;
391
+ if (currentQuality === "LQ") {
392
+ // Toggle to HQ: change button text and load HQ model.
393
+ currentQuality = "HQ";
394
+ toggleQualityBtn.textContent = "charger LD";
395
+ await loadModel(plyUrlHQ);
396
+ } else {
397
+ // Toggle back to LQ.
398
+ currentQuality = "LQ";
399
+ toggleQualityBtn.textContent = "charger HD (lent)";
400
+ await loadModel(plyUrl);
401
+ }
402
+ });
403
+
404
+ })();