mohamedtsou commited on
Commit
f31ddc8
·
verified ·
1 Parent(s): bed82f1

Update view.html

Browse files
Files changed (1) hide show
  1. view.html +381 -78
view.html CHANGED
@@ -2,79 +2,274 @@
2
  <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Visualisation 3D - SubTool</title>
7
  <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- margin: 0;
11
- overflow: hidden;
12
- font-family: 'Segoe UI', system-ui, sans-serif;
 
 
 
 
 
13
  background-color: #0a0a1a;
 
14
  }
 
 
15
  #info {
16
  position: absolute;
17
- top: 20px;
18
- left: 20px;
19
- background: rgba(10, 10, 26, 0.85);
 
20
  backdrop-filter: blur(10px);
 
21
  color: white;
22
- padding: 16px 24px;
23
- border-radius: 12px;
24
- border: 1px solid rgba(255,255,255,0.1);
25
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
26
  z-index: 1000;
27
- pointer-events: none;
28
  border-left: 4px solid #3b82f6;
 
 
29
  }
 
 
 
 
 
 
 
 
 
 
30
  #info h2 {
31
- font-size: 1.1rem;
32
- font-weight: 500;
33
  margin-bottom: 4px;
34
  color: #3b82f6;
35
  }
 
 
 
 
 
 
 
36
  #info p {
37
- font-size: 0.85rem;
38
  opacity: 0.7;
 
 
 
 
39
  }
 
 
 
 
 
 
 
 
40
  #status {
41
  position: absolute;
42
- bottom: 30px;
43
- left: 50%;
44
- transform: translateX(-50%);
45
- background: rgba(0, 0, 0, 0.7);
46
  backdrop-filter: blur(10px);
 
47
  color: #fbbf24;
48
- padding: 12px 28px;
49
  border-radius: 40px;
50
- font-size: 0.95rem;
51
  font-weight: 500;
52
  border: 1px solid rgba(255,255,255,0.1);
53
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
54
  z-index: 1000;
 
55
  letter-spacing: 0.3px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
 
 
57
  #hf-badge {
58
  position: absolute;
59
- bottom: 30px;
60
- right: 30px;
61
- color: rgba(255,255,255,0.4);
62
- font-size: 0.8rem;
63
- background: rgba(0,0,0,0.3);
64
- padding: 6px 12px;
65
- border-radius: 20px;
66
  backdrop-filter: blur(5px);
 
67
  z-index: 1000;
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
 
 
69
  .loading-bar {
70
  position: fixed;
71
  top: 0;
72
  left: 0;
73
- height: 3px;
74
- background: linear-gradient(90deg, #3b82f6, #8b5cf6);
75
  width: 0%;
76
  transition: width 0.3s ease;
77
  z-index: 2000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
  </style>
80
  </head>
@@ -82,68 +277,127 @@
82
  <div class="loading-bar" id="loadingBar"></div>
83
 
84
  <div id="info">
85
- <h2>🔬 SubTool-0-3517926.OBJ</h2>
86
- <p>🖱️ Rotation · Scroll: Zoom · Glisser: Déplacer</p>
 
 
 
 
 
 
 
87
  </div>
88
 
89
  <div id="status">
90
  <span id="statusText">Initialisation...</span>
91
  </div>
92
 
 
 
 
 
 
93
  <div id="hf-badge">
94
- 🤗 Hugging Face Spaces · Docker
95
  </div>
 
 
96
 
97
- <!-- Three.js depuis CDN -->
98
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
99
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
100
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
 
 
 
101
 
102
  <script>
103
  (function() {
 
104
  const statusText = document.getElementById('statusText');
105
  const loadingBar = document.getElementById('loadingBar');
 
 
 
 
 
 
106
 
107
  // Mise à jour du statut
108
  function setStatus(message, percent) {
109
- statusText.textContent = message;
110
- if (percent !== undefined) {
111
  loadingBar.style.width = percent + '%';
112
  }
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  try {
116
- // Vérifier que Three.js est chargé
117
  if (typeof THREE === 'undefined') {
118
  throw new Error('Three.js non chargé');
119
  }
120
 
121
- setStatus('Préparation de la scène...', 10);
 
 
 
122
 
123
- // --- Configuration de base ---
124
- const scene = new THREE.Scene();
125
  scene.background = new THREE.Color(0x0a0a1a);
126
 
127
- const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
128
- camera.position.set(5, 3, 8);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
131
  renderer.setSize(window.innerWidth, window.innerHeight);
132
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
 
133
  document.body.appendChild(renderer.domElement);
134
 
135
- // --- Contrôles ---
136
- const controls = new THREE.OrbitControls(camera, renderer.domElement);
137
  controls.enableDamping = true;
138
- controls.dampingFactor = 0.05;
139
  controls.autoRotate = true;
140
- controls.autoRotateSpeed = 2.0;
141
  controls.enableZoom = true;
142
- controls.enablePan = true;
143
- controls.target.set(0, 0, 0);
 
 
144
 
145
  // --- Lumières ---
146
- scene.add(new THREE.AmbientLight(0x404060));
 
147
 
148
  const light1 = new THREE.DirectionalLight(0xffffff, 1.2);
149
  light1.position.set(2, 5, 3);
@@ -153,19 +407,24 @@
153
  light2.position.set(-3, 2, -4);
154
  scene.add(light2);
155
 
156
- // --- Grille et axes ---
 
 
 
 
 
157
  const gridHelper = new THREE.GridHelper(10, 20, 0x3b82f6, 0x1e293b);
158
  scene.add(gridHelper);
159
 
160
  setStatus('Chargement du modèle...', 30);
161
 
162
- // --- Chargement du modèle OBJ ---
163
  const loader = new THREE.OBJLoader();
164
 
165
  loader.load(
166
  'SubTool-0-3517926.OBJ',
167
  (object) => {
168
- console.log('✅ Modèle chargé avec succès');
169
 
170
  // Centrer le modèle
171
  const box = new THREE.Box3().setFromObject(object);
@@ -174,7 +433,7 @@
174
 
175
  object.position.set(-center.x, -center.y, -center.z);
176
 
177
- // Ajouter une couleur si le modèle n'en a pas
178
  object.traverse((child) => {
179
  if (child.isMesh) {
180
  if (!child.material) {
@@ -185,61 +444,105 @@
185
  emissive: 0x112233
186
  });
187
  }
188
- // Optimisation
189
  child.frustumCulled = true;
190
- child.castShadow = false;
191
- child.receiveShadow = false;
192
  }
193
  });
194
 
195
  scene.add(object);
 
196
 
197
- // Ajuster la caméra en fonction de la taille
198
  const maxDim = Math.max(size.x, size.y, size.z);
199
  if (maxDim > 0) {
200
- camera.position.set(maxDim * 1.5, maxDim * 0.8, maxDim * 1.5);
 
201
  controls.target.set(0, size.y/2, 0);
202
  }
203
 
204
- setStatus(`✅ Prêt (${size.x.toFixed(2)} x ${size.y.toFixed(2)} x ${size.z.toFixed(2)})`, 100);
205
 
206
- // Cacher la barre après 1 seconde
207
  setTimeout(() => {
208
  loadingBar.style.opacity = '0';
209
- }, 1000);
210
  },
211
  (xhr) => {
212
  if (xhr.lengthComputable) {
213
- const percent = Math.round((xhr.loaded / xhr.total) * 70) + 30; // 30-100%
214
  setStatus(`Chargement: ${Math.round((xhr.loaded / xhr.total) * 100)}%`, percent);
215
  }
216
  },
217
  (error) => {
218
- console.error('Erreur de chargement:', error);
219
- setStatus('❌ Erreur de chargement', 100);
220
- loadingBar.style.backgroundColor = '#ef4444';
221
  }
222
  );
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  // --- Animation ---
225
  function animate() {
226
- requestAnimationFrame(animate);
227
- controls.update();
 
228
  renderer.render(scene, camera);
229
  }
230
- animate();
231
 
232
- // --- Gestion du redimensionnement ---
 
 
233
  window.addEventListener('resize', () => {
234
- camera.aspect = window.innerWidth / window.innerHeight;
 
 
 
235
  camera.updateProjectionMatrix();
236
- renderer.setSize(window.innerWidth, window.innerHeight);
237
  });
238
 
 
 
 
 
 
239
  } catch (error) {
240
- console.error('Erreur critique:', error);
241
- setStatus('❌ Erreur: ' + error.message, 100);
242
- loadingBar.style.backgroundColor = '#ef4444';
243
  }
244
  })();
245
  </script>
 
2
  <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Squelette 3D - VR Compatible</title>
7
  <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ overflow: hidden;
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
18
  background-color: #0a0a1a;
19
+ touch-action: none; /* Évite les gestes système sur mobile */
20
  }
21
+
22
+ /* Info panel - responsive */
23
  #info {
24
  position: absolute;
25
+ top: 15px;
26
+ left: 15px;
27
+ right: 15px;
28
+ background: rgba(10, 10, 26, 0.9);
29
  backdrop-filter: blur(10px);
30
+ -webkit-backdrop-filter: blur(10px);
31
  color: white;
32
+ padding: 12px 18px;
33
+ border-radius: 16px;
34
+ border: 1px solid rgba(255,255,255,0.15);
35
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
36
  z-index: 1000;
 
37
  border-left: 4px solid #3b82f6;
38
+ max-width: 400px;
39
+ margin: 0 auto;
40
  }
41
+
42
+ @media (min-width: 768px) {
43
+ #info {
44
+ left: 20px;
45
+ right: auto;
46
+ width: auto;
47
+ padding: 16px 24px;
48
+ }
49
+ }
50
+
51
  #info h2 {
52
+ font-size: 1rem;
53
+ font-weight: 600;
54
  margin-bottom: 4px;
55
  color: #3b82f6;
56
  }
57
+
58
+ @media (min-width: 768px) {
59
+ #info h2 {
60
+ font-size: 1.2rem;
61
+ }
62
+ }
63
+
64
  #info p {
65
+ font-size: 0.8rem;
66
  opacity: 0.7;
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 5px;
70
+ flex-wrap: wrap;
71
  }
72
+
73
+ @media (min-width: 768px) {
74
+ #info p {
75
+ font-size: 0.9rem;
76
+ }
77
+ }
78
+
79
+ /* Status bar - responsive */
80
  #status {
81
  position: absolute;
82
+ bottom: 90px;
83
+ left: 15px;
84
+ right: 15px;
85
+ background: rgba(0, 0, 0, 0.8);
86
  backdrop-filter: blur(10px);
87
+ -webkit-backdrop-filter: blur(10px);
88
  color: #fbbf24;
89
+ padding: 14px 20px;
90
  border-radius: 40px;
91
+ font-size: 0.9rem;
92
  font-weight: 500;
93
  border: 1px solid rgba(255,255,255,0.1);
94
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
95
  z-index: 1000;
96
+ text-align: center;
97
  letter-spacing: 0.3px;
98
+ max-width: 400px;
99
+ margin: 0 auto;
100
+ }
101
+
102
+ @media (min-width: 768px) {
103
+ #status {
104
+ left: 50%;
105
+ right: auto;
106
+ transform: translateX(-50%);
107
+ width: auto;
108
+ min-width: 300px;
109
+ bottom: 30px;
110
+ }
111
+ }
112
+
113
+ /* VR Button - responsive et stylisé */
114
+ #vr-button {
115
+ position: absolute;
116
+ bottom: 160px;
117
+ left: 15px;
118
+ right: 15px;
119
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
120
+ color: white;
121
+ border: none;
122
+ padding: 16px 20px;
123
+ border-radius: 60px;
124
+ font-size: 1.1rem;
125
+ font-weight: bold;
126
+ cursor: pointer;
127
+ z-index: 1000;
128
+ box-shadow: 0 10px 30px rgba(59, 130, 246, 0.5);
129
+ transition: all 0.3s ease;
130
+ letter-spacing: 1px;
131
+ text-transform: uppercase;
132
+ border: 1px solid rgba(255,255,255,0.2);
133
+ backdrop-filter: blur(5px);
134
+ -webkit-backdrop-filter: blur(5px);
135
+ max-width: 400px;
136
+ margin: 0 auto;
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ gap: 10px;
141
+ }
142
+
143
+ #vr-button:active {
144
+ transform: scale(0.98);
145
+ box-shadow: 0 5px 15px rgba(59, 130, 246, 0.7);
146
+ }
147
+
148
+ #vr-button.vr-active {
149
+ background: linear-gradient(135deg, #10b981, #059669);
150
+ box-shadow: 0 10px 30px rgba(16, 185, 129, 0.5);
151
+ }
152
+
153
+ @media (min-width: 768px) {
154
+ #vr-button {
155
+ left: 50%;
156
+ right: auto;
157
+ transform: translateX(-50%);
158
+ width: auto;
159
+ min-width: 250px;
160
+ bottom: 100px;
161
+ padding: 15px 30px;
162
+ }
163
+
164
+ #vr-button:hover {
165
+ transform: translateX(-50%) scale(1.05);
166
+ }
167
+
168
+ #vr-button:active {
169
+ transform: translateX(-50%) scale(0.98);
170
+ }
171
  }
172
+
173
+ /* Badge Hugging Face */
174
  #hf-badge {
175
  position: absolute;
176
+ bottom: 20px;
177
+ right: 15px;
178
+ color: rgba(255,255,255,0.5);
179
+ font-size: 0.75rem;
180
+ background: rgba(0,0,0,0.4);
181
+ padding: 8px 14px;
182
+ border-radius: 30px;
183
  backdrop-filter: blur(5px);
184
+ -webkit-backdrop-filter: blur(5px);
185
  z-index: 1000;
186
+ border: 1px solid rgba(255,255,255,0.1);
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 6px;
190
+ }
191
+
192
+ @media (min-width: 768px) {
193
+ #hf-badge {
194
+ bottom: 30px;
195
+ right: 30px;
196
+ font-size: 0.8rem;
197
+ }
198
  }
199
+
200
+ /* Loading bar */
201
  .loading-bar {
202
  position: fixed;
203
  top: 0;
204
  left: 0;
205
+ height: 4px;
206
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6, #ec4899);
207
  width: 0%;
208
  transition: width 0.3s ease;
209
  z-index: 2000;
210
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.7);
211
+ }
212
+
213
+ /* Message d'erreur */
214
+ #error {
215
+ position: absolute;
216
+ top: 50%;
217
+ left: 15px;
218
+ right: 15px;
219
+ transform: translateY(-50%);
220
+ background: rgba(239, 68, 68, 0.95);
221
+ backdrop-filter: blur(10px);
222
+ -webkit-backdrop-filter: blur(10px);
223
+ color: white;
224
+ padding: 25px;
225
+ border-radius: 20px;
226
+ text-align: center;
227
+ z-index: 2000;
228
+ display: none;
229
+ max-width: 400px;
230
+ margin: 0 auto;
231
+ border: 1px solid rgba(255,255,255,0.2);
232
+ box-shadow: 0 20px 40px rgba(0,0,0,0.5);
233
+ }
234
+
235
+ #error button {
236
+ background: white;
237
+ color: #ef4444;
238
+ border: none;
239
+ padding: 12px 30px;
240
+ border-radius: 50px;
241
+ font-size: 1rem;
242
+ font-weight: bold;
243
+ cursor: pointer;
244
+ margin-top: 20px;
245
+ width: 100%;
246
+ transition: transform 0.2s;
247
+ }
248
+
249
+ #error button:active {
250
+ transform: scale(0.95);
251
+ }
252
+
253
+ /* Contrôles tactiles indication */
254
+ .touch-hint {
255
+ position: absolute;
256
+ top: 80px;
257
+ right: 15px;
258
+ background: rgba(0,0,0,0.5);
259
+ color: white;
260
+ padding: 8px 12px;
261
+ border-radius: 30px;
262
+ font-size: 0.75rem;
263
+ backdrop-filter: blur(5px);
264
+ z-index: 1000;
265
+ border: 1px solid rgba(255,255,255,0.1);
266
+ display: none;
267
+ }
268
+
269
+ @media (max-width: 768px) {
270
+ .touch-hint {
271
+ display: block;
272
+ }
273
  }
274
  </style>
275
  </head>
 
277
  <div class="loading-bar" id="loadingBar"></div>
278
 
279
  <div id="info">
280
+ <h2>💀 mohamedtsou/scleton</h2>
281
+ <p>
282
+ <span>👆 Un doigt: Rotation</span>
283
+ <span>🤌 Deux doigts: Zoom</span>
284
+ </p>
285
+ </div>
286
+
287
+ <div class="touch-hint">
288
+ 👆 Toucher pour explorer
289
  </div>
290
 
291
  <div id="status">
292
  <span id="statusText">Initialisation...</span>
293
  </div>
294
 
295
+ <button id="vr-button" class="vr-button">
296
+ <span>🥽</span>
297
+ <span id="vr-button-text">MODE VR</span>
298
+ </button>
299
+
300
  <div id="hf-badge">
301
+ <span>🤗</span> Hugging Face Spaces
302
  </div>
303
+
304
+ <div id="error"></div>
305
 
306
+ <!-- Three.js et WebXR -->
307
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
308
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
309
  <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
310
+
311
+ <!-- WebXR polyfill pour plus de compatibilité -->
312
+ <script src="https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.js"></script>
313
 
314
  <script>
315
  (function() {
316
+ // Éléments DOM
317
  const statusText = document.getElementById('statusText');
318
  const loadingBar = document.getElementById('loadingBar');
319
+ const vrButton = document.getElementById('vr-button');
320
+ const vrButtonText = document.getElementById('vr-button-text');
321
+ const errorDiv = document.getElementById('error');
322
+
323
+ let isVRMode = false;
324
+ let scene, camera, renderer, controls, model;
325
 
326
  // Mise à jour du statut
327
  function setStatus(message, percent) {
328
+ if (statusText) statusText.textContent = message;
329
+ if (loadingBar && percent !== undefined) {
330
  loadingBar.style.width = percent + '%';
331
  }
332
  }
333
 
334
+ // Afficher une erreur
335
+ function showError(message) {
336
+ console.error(message);
337
+ errorDiv.style.display = 'block';
338
+ errorDiv.innerHTML = `
339
+ <h3>❌ Erreur</h3>
340
+ <p>${message}</p>
341
+ <p>Vérifie ta connexion et réessaie</p>
342
+ <button onclick="location.reload()">🔄 Réessayer</button>
343
+ `;
344
+ setStatus('Erreur de chargement', 100);
345
+ loadingBar.style.background = '#ef4444';
346
+ }
347
+
348
  try {
349
+ // Vérifier Three.js
350
  if (typeof THREE === 'undefined') {
351
  throw new Error('Three.js non chargé');
352
  }
353
 
354
+ setStatus('Préparation...', 10);
355
+
356
+ // Détection mobile pour ajustements
357
+ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
358
 
359
+ // --- Initialisation de la scène ---
360
+ scene = new THREE.Scene();
361
  scene.background = new THREE.Color(0x0a0a1a);
362
 
363
+ // --- Caméra adaptative ---
364
+ const aspect = window.innerWidth / window.innerHeight;
365
+ camera = new THREE.PerspectiveCamera(65, aspect, 0.1, 1000);
366
+
367
+ // Position caméra selon l'appareil
368
+ if (isMobile) {
369
+ camera.position.set(4, 2.5, 6); // Plus proche sur mobile
370
+ } else {
371
+ camera.position.set(5, 3, 8);
372
+ }
373
+
374
+ // --- Renderer avec antialiasing ---
375
+ renderer = new THREE.WebGLRenderer({
376
+ antialias: true,
377
+ powerPreference: "high-performance",
378
+ alpha: false
379
+ });
380
 
 
381
  renderer.setSize(window.innerWidth, window.innerHeight);
382
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Limite pour performance
383
+ renderer.xr.enabled = true; // Activer WebXR pour la VR
384
  document.body.appendChild(renderer.domElement);
385
 
386
+ // --- Contrôles optimisés pour mobile ---
387
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
388
  controls.enableDamping = true;
389
+ controls.dampingFactor = 0.1;
390
  controls.autoRotate = true;
391
+ controls.autoRotateSpeed = isMobile ? 1.5 : 2.0;
392
  controls.enableZoom = true;
393
+ controls.enablePan = isMobile ? false : true; // Désactiver pan sur mobile
394
+ controls.enableRotate = true;
395
+ controls.rotateSpeed = isMobile ? 0.8 : 1.0;
396
+ controls.target.set(0, 0.5, 0);
397
 
398
  // --- Lumières ---
399
+ const ambientLight = new THREE.AmbientLight(0x404060);
400
+ scene.add(ambientLight);
401
 
402
  const light1 = new THREE.DirectionalLight(0xffffff, 1.2);
403
  light1.position.set(2, 5, 3);
 
407
  light2.position.set(-3, 2, -4);
408
  scene.add(light2);
409
 
410
+ // Lumière d'appoint
411
+ const fillLight = new THREE.PointLight(0x4466aa, 0.5);
412
+ fillLight.position.set(0, 2, 2);
413
+ scene.add(fillLight);
414
+
415
+ // --- Grille adaptative ---
416
  const gridHelper = new THREE.GridHelper(10, 20, 0x3b82f6, 0x1e293b);
417
  scene.add(gridHelper);
418
 
419
  setStatus('Chargement du modèle...', 30);
420
 
421
+ // --- Chargement du modèle ---
422
  const loader = new THREE.OBJLoader();
423
 
424
  loader.load(
425
  'SubTool-0-3517926.OBJ',
426
  (object) => {
427
+ console.log('✅ Modèle chargé');
428
 
429
  // Centrer le modèle
430
  const box = new THREE.Box3().setFromObject(object);
 
433
 
434
  object.position.set(-center.x, -center.y, -center.z);
435
 
436
+ // Appliquer matériaux si nécessaire
437
  object.traverse((child) => {
438
  if (child.isMesh) {
439
  if (!child.material) {
 
444
  emissive: 0x112233
445
  });
446
  }
 
447
  child.frustumCulled = true;
 
 
448
  }
449
  });
450
 
451
  scene.add(object);
452
+ model = object;
453
 
454
+ // Ajuster la caméra
455
  const maxDim = Math.max(size.x, size.y, size.z);
456
  if (maxDim > 0) {
457
+ const distance = isMobile ? maxDim * 1.8 : maxDim * 2;
458
+ camera.position.set(distance * 0.8, distance * 0.5, distance);
459
  controls.target.set(0, size.y/2, 0);
460
  }
461
 
462
+ setStatus(`✅ Prêt`, 100);
463
 
 
464
  setTimeout(() => {
465
  loadingBar.style.opacity = '0';
466
+ }, 500);
467
  },
468
  (xhr) => {
469
  if (xhr.lengthComputable) {
470
+ const percent = Math.round((xhr.loaded / xhr.total) * 70) + 30;
471
  setStatus(`Chargement: ${Math.round((xhr.loaded / xhr.total) * 100)}%`, percent);
472
  }
473
  },
474
  (error) => {
475
+ showError('Impossible de charger le modèle 3D');
 
 
476
  }
477
  );
478
 
479
+ // --- Gestion du bouton VR ---
480
+ vrButton.addEventListener('click', async () => {
481
+ if (!isVRMode) {
482
+ // Vérifier support WebXR
483
+ if (navigator.xr) {
484
+ try {
485
+ const supported = await navigator.xr.isSessionSupported('immersive-vr');
486
+ if (supported) {
487
+ const session = await navigator.xr.requestSession('immersive-vr');
488
+ await renderer.xr.setSession(session);
489
+
490
+ isVRMode = true;
491
+ vrButton.classList.add('vr-active');
492
+ vrButtonText.textContent = 'QUITTER VR';
493
+
494
+ // Désactiver auto-rotate en VR
495
+ controls.autoRotate = false;
496
+ } else {
497
+ alert('Mode VR non supporté sur cet appareil');
498
+ }
499
+ } catch (error) {
500
+ console.error('Erreur VR:', error);
501
+ alert('Impossible d\'activer le mode VR');
502
+ }
503
+ } else {
504
+ alert('WebXR non supporté. Utilise Chrome ou Firefox récent.');
505
+ }
506
+ } else {
507
+ // Quitter VR
508
+ const session = renderer.xr.getSession();
509
+ if (session) {
510
+ await session.end();
511
+ }
512
+ isVRMode = false;
513
+ vrButton.classList.remove('vr-active');
514
+ vrButtonText.textContent = 'MODE VR';
515
+ controls.autoRotate = true;
516
+ }
517
+ });
518
+
519
  // --- Animation ---
520
  function animate() {
521
+ if (!isVRMode) {
522
+ controls.update();
523
+ }
524
  renderer.render(scene, camera);
525
  }
 
526
 
527
+ renderer.setAnimationLoop(animate);
528
+
529
+ // --- Gestion du redimensionnement (responsive) ---
530
  window.addEventListener('resize', () => {
531
+ const width = window.innerWidth;
532
+ const height = window.innerHeight;
533
+
534
+ camera.aspect = width / height;
535
  camera.updateProjectionMatrix();
536
+ renderer.setSize(width, height);
537
  });
538
 
539
+ // --- Gestion des événements tactiles ---
540
+ renderer.domElement.addEventListener('touchstart', (e) => {
541
+ e.preventDefault(); // Empêche le scroll
542
+ }, { passive: false });
543
+
544
  } catch (error) {
545
+ showError(error.message);
 
 
546
  }
547
  })();
548
  </script>