Spaces:
Sleeping
Sleeping
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Squelette 3D - Contrôles tactiles</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| user-select: none; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; | |
| background-color: #0a0a1a; | |
| touch-action: none; | |
| position: fixed; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Info panel */ | |
| #info { | |
| position: absolute; | |
| top: 15px; | |
| left: 15px; | |
| right: 15px; | |
| background: rgba(10, 10, 26, 0.9); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| color: white; | |
| padding: 12px 18px; | |
| border-radius: 16px; | |
| border: 1px solid rgba(255,255,255,0.15); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.4); | |
| z-index: 1000; | |
| border-left: 4px solid #3b82f6; | |
| max-width: 400px; | |
| margin: 0 auto; | |
| } | |
| @media (min-width: 768px) { | |
| #info { | |
| left: 20px; | |
| right: auto; | |
| width: auto; | |
| } | |
| } | |
| #info h2 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| margin-bottom: 4px; | |
| color: #3b82f6; | |
| } | |
| #info p { | |
| font-size: 0.8rem; | |
| opacity: 0.8; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| /* Status */ | |
| #status { | |
| position: absolute; | |
| top: 90px; | |
| left: 15px; | |
| right: 15px; | |
| background: rgba(0, 0, 0, 0.7); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| color: #fbbf24; | |
| padding: 10px 15px; | |
| border-radius: 30px; | |
| font-size: 0.85rem; | |
| border: 1px solid rgba(255,255,255,0.1); | |
| z-index: 1000; | |
| text-align: center; | |
| max-width: 400px; | |
| margin: 0 auto; | |
| } | |
| /* Panneau de contrôle tactile */ | |
| #control-panel { | |
| position: absolute; | |
| bottom: 30px; | |
| left: 15px; | |
| right: 15px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| z-index: 2000; | |
| pointer-events: none; | |
| max-width: 500px; | |
| margin: 0 auto; | |
| } | |
| /* Rangée de boutons */ | |
| .control-row { | |
| display: flex; | |
| justify-content: center; | |
| gap: 15px; | |
| pointer-events: auto; | |
| } | |
| /* Boutons de contrôle */ | |
| .control-btn { | |
| width: 70px; | |
| height: 70px; | |
| border-radius: 35px; | |
| background: rgba(30, 30, 50, 0.85); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| border: 2px solid rgba(255, 255, 255, 0.2); | |
| color: white; | |
| font-size: 28px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5); | |
| transition: all 0.1s ease; | |
| -webkit-tap-highlight-color: transparent; | |
| user-select: none; | |
| font-weight: bold; | |
| } | |
| .control-btn:active { | |
| transform: scale(0.9); | |
| background: rgba(59, 130, 246, 0.9); | |
| border-color: #3b82f6; | |
| } | |
| .control-btn.large { | |
| width: 90px; | |
| height: 90px; | |
| border-radius: 45px; | |
| font-size: 36px; | |
| background: rgba(59, 130, 246, 0.9); | |
| border-color: #3b82f6; | |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.5); | |
| } | |
| .control-btn.large:active { | |
| background: rgba(139, 92, 246, 0.9); | |
| } | |
| /* Label des boutons */ | |
| .btn-label { | |
| position: absolute; | |
| bottom: -25px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| color: rgba(255,255,255,0.6); | |
| font-size: 12px; | |
| white-space: nowrap; | |
| background: rgba(0,0,0,0.5); | |
| padding: 4px 8px; | |
| border-radius: 12px; | |
| backdrop-filter: blur(5px); | |
| pointer-events: none; | |
| } | |
| /* Bouton VR */ | |
| #vr-button { | |
| position: absolute; | |
| top: 15px; | |
| right: 15px; | |
| background: linear-gradient(135deg, #3b82f6, #8b5cf6); | |
| color: white; | |
| border: none; | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 25px; | |
| font-size: 24px; | |
| cursor: pointer; | |
| z-index: 2000; | |
| box-shadow: 0 4px 15px rgba(59, 130, 246, 0.5); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border: 1px solid rgba(255,255,255,0.2); | |
| } | |
| #vr-button:active { | |
| transform: scale(0.95); | |
| } | |
| #vr-button.vr-active { | |
| background: linear-gradient(135deg, #10b981, #059669); | |
| } | |
| /* Badge HF */ | |
| #hf-badge { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 15px; | |
| color: rgba(255,255,255,0.5); | |
| font-size: 0.7rem; | |
| background: rgba(0,0,0,0.4); | |
| padding: 6px 12px; | |
| border-radius: 20px; | |
| backdrop-filter: blur(5px); | |
| z-index: 1000; | |
| } | |
| /* Loading bar */ | |
| .loading-bar { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| height: 4px; | |
| background: linear-gradient(90deg, #3b82f6, #8b5cf6, #ec4899); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| z-index: 3000; | |
| } | |
| /* Message d'erreur */ | |
| #error { | |
| position: absolute; | |
| top: 50%; | |
| left: 15px; | |
| right: 15px; | |
| transform: translateY(-50%); | |
| background: rgba(239, 68, 68, 0.95); | |
| color: white; | |
| padding: 20px; | |
| border-radius: 16px; | |
| text-align: center; | |
| z-index: 4000; | |
| display: none; | |
| max-width: 350px; | |
| margin: 0 auto; | |
| } | |
| #error button { | |
| background: white; | |
| color: #ef4444; | |
| border: none; | |
| padding: 10px 25px; | |
| border-radius: 30px; | |
| font-size: 1rem; | |
| font-weight: bold; | |
| cursor: pointer; | |
| margin-top: 15px; | |
| width: 100%; | |
| } | |
| /* Adaptation pour grands écrans */ | |
| @media (min-width: 768px) { | |
| .control-btn { | |
| width: 80px; | |
| height: 80px; | |
| font-size: 32px; | |
| } | |
| .control-btn.large { | |
| width: 100px; | |
| height: 100px; | |
| font-size: 40px; | |
| } | |
| #control-panel { | |
| bottom: 40px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="loading-bar" id="loadingBar"></div> | |
| <div id="info"> | |
| <h2>💀 mohamedtsou/scleton</h2> | |
| <p> | |
| <span>👆 Boutons tactiles</span> | |
| <span>🤌 Gestes aussi</span> | |
| </p> | |
| </div> | |
| <div id="status"> | |
| <span id="statusText">Initialisation...</span> | |
| </div> | |
| <button id="vr-button" class="vr-button"> | |
| <span>🥽</span> | |
| </button> | |
| <!-- Panneau de contrôle tactile --> | |
| <div id="control-panel"> | |
| <!-- Ligne 1: Rotation --> | |
| <div class="control-row"> | |
| <button class="control-btn" id="rotate-left" title="Rotation gauche"> | |
| ↺ | |
| <span class="btn-label">Gauche</span> | |
| </button> | |
| <button class="control-btn" id="rotate-right" title="Rotation droite"> | |
| ↻ | |
| <span class="btn-label">Droite</span> | |
| </button> | |
| </div> | |
| <!-- Ligne 2: Zoom et recentrage --> | |
| <div class="control-row"> | |
| <button class="control-btn" id="zoom-out" title="Zoom arrière"> | |
| − | |
| <span class="btn-label">Zoom -</span> | |
| </button> | |
| <button class="control-btn large" id="reset-view" title="Recentrer"> | |
| ⌂ | |
| <span class="btn-label">Recentrer</span> | |
| </button> | |
| <button class="control-btn" id="zoom-in" title="Zoom avant"> | |
| + | |
| <span class="btn-label">Zoom +</span> | |
| </button> | |
| </div> | |
| <!-- Ligne 3: Déplacement --> | |
| <div class="control-row"> | |
| <button class="control-btn" id="move-left" title="Déplacer gauche"> | |
| ← | |
| <span class="btn-label">Gauche</span> | |
| </button> | |
| <button class="control-btn" id="move-up" title="Déplacer haut"> | |
| ↑ | |
| <span class="btn-label">Haut</span> | |
| </button> | |
| <button class="control-btn" id="move-down" title="Déplacer bas"> | |
| ↓ | |
| <span class="btn-label">Bas</span> | |
| </button> | |
| <button class="control-btn" id="move-right" title="Déplacer droite"> | |
| → | |
| <span class="btn-label">Droite</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="hf-badge"> | |
| 🤗 Hugging Face | |
| </div> | |
| <div id="error"></div> | |
| <!-- Three.js --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script> | |
| <script> | |
| (function() { | |
| // Éléments DOM | |
| const statusText = document.getElementById('statusText'); | |
| const loadingBar = document.getElementById('loadingBar'); | |
| const vrButton = document.getElementById('vr-button'); | |
| const errorDiv = document.getElementById('error'); | |
| // État | |
| let scene, camera, renderer, controls, model; | |
| let isVRMode = false; | |
| // Facteurs de contrôle | |
| const ROTATION_SPEED = 0.1; | |
| const ZOOM_SPEED = 0.5; | |
| const MOVE_SPEED = 0.2; | |
| function setStatus(message, percent) { | |
| if (statusText) statusText.textContent = message; | |
| if (loadingBar && percent !== undefined) { | |
| loadingBar.style.width = percent + '%'; | |
| } | |
| } | |
| function showError(message) { | |
| errorDiv.style.display = 'block'; | |
| errorDiv.innerHTML = ` | |
| <h3>❌ ${message}</h3> | |
| <button onclick="location.reload()">Réessayer</button> | |
| `; | |
| setStatus('Erreur', 100); | |
| } | |
| try { | |
| setStatus('Préparation...', 10); | |
| // Initialisation Three.js | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x0a0a1a); | |
| camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(5, 3, 8); | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| document.body.appendChild(renderer.domElement); | |
| // Contrôles (toujours actifs pour les gestes) | |
| controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| controls.autoRotate = true; | |
| controls.autoRotateSpeed = 2.0; | |
| controls.enableZoom = true; | |
| controls.enablePan = true; | |
| controls.enableRotate = true; | |
| controls.target.set(0, 0.5, 0); | |
| // Lumières | |
| scene.add(new THREE.AmbientLight(0x404060)); | |
| const light1 = new THREE.DirectionalLight(0xffffff, 1.2); | |
| light1.position.set(2, 5, 3); | |
| scene.add(light1); | |
| const light2 = new THREE.DirectionalLight(0xffaa88, 0.8); | |
| light2.position.set(-3, 2, -4); | |
| scene.add(light2); | |
| // Grille | |
| scene.add(new THREE.GridHelper(10, 20, 0x3b82f6, 0x1e293b)); | |
| setStatus('Chargement du modèle...', 30); | |
| // Chargement OBJ | |
| const loader = new THREE.OBJLoader(); | |
| loader.load( | |
| 'SubTool-0-3517926.OBJ', | |
| (object) => { | |
| // Centrer | |
| const box = new THREE.Box3().setFromObject(object); | |
| const center = box.getCenter(new THREE.Vector3()); | |
| object.position.set(-center.x, -center.y, -center.z); | |
| // Couleur | |
| object.traverse(child => { | |
| if (child.isMesh && !child.material) { | |
| child.material = new THREE.MeshStandardMaterial({ | |
| color: 0x88aaff, | |
| roughness: 0.4, | |
| metalness: 0.2 | |
| }); | |
| } | |
| }); | |
| scene.add(object); | |
| model = object; | |
| setStatus('✅ Prêt', 100); | |
| setTimeout(() => loadingBar.style.opacity = '0', 500); | |
| }, | |
| (xhr) => { | |
| if (xhr.lengthComputable) { | |
| const pct = Math.round((xhr.loaded / xhr.total) * 100); | |
| setStatus(`Chargement: ${pct}%`, pct); | |
| } | |
| }, | |
| (error) => showError('Erreur chargement modèle') | |
| ); | |
| // --- GESTION DES BOUTONS --- | |
| // Rotation | |
| document.getElementById('rotate-left').addEventListener('click', () => { | |
| if (model) { | |
| model.rotation.y += ROTATION_SPEED; | |
| } | |
| }); | |
| document.getElementById('rotate-right').addEventListener('click', () => { | |
| if (model) { | |
| model.rotation.y -= ROTATION_SPEED; | |
| } | |
| }); | |
| // Zoom | |
| document.getElementById('zoom-in').addEventListener('click', () => { | |
| camera.position.multiplyScalar(0.9); // Rapproche | |
| controls.update(); | |
| }); | |
| document.getElementById('zoom-out').addEventListener('click', () => { | |
| camera.position.multiplyScalar(1.1); // Éloigne | |
| controls.update(); | |
| }); | |
| // Déplacement | |
| document.getElementById('move-left').addEventListener('click', () => { | |
| controls.target.x -= MOVE_SPEED; | |
| }); | |
| document.getElementById('move-right').addEventListener('click', () => { | |
| controls.target.x += MOVE_SPEED; | |
| }); | |
| document.getElementById('move-up').addEventListener('click', () => { | |
| controls.target.y += MOVE_SPEED; | |
| }); | |
| document.getElementById('move-down').addEventListener('click', () => { | |
| controls.target.y -= MOVE_SPEED; | |
| }); | |
| // Recentrer | |
| document.getElementById('reset-view').addEventListener('click', () => { | |
| camera.position.set(5, 3, 8); | |
| controls.target.set(0, 0.5, 0); | |
| if (model) { | |
| model.rotation.set(0, 0, 0); | |
| } | |
| controls.update(); | |
| }); | |
| // Bouton VR | |
| vrButton.addEventListener('click', async () => { | |
| if (!isVRMode) { | |
| if (navigator.xr) { | |
| try { | |
| const session = await navigator.xr.requestSession('immersive-vr'); | |
| await renderer.xr.setSession(session); | |
| isVRMode = true; | |
| vrButton.classList.add('vr-active'); | |
| controls.autoRotate = false; | |
| } catch (e) { | |
| alert('VR non disponible'); | |
| } | |
| } else { | |
| alert('WebXR non supporté'); | |
| } | |
| } else { | |
| const session = renderer.xr.getSession(); | |
| if (session) await session.end(); | |
| isVRMode = false; | |
| vrButton.classList.remove('vr-active'); | |
| controls.autoRotate = true; | |
| } | |
| }); | |
| // Animation | |
| function animate() { | |
| if (!isVRMode) { | |
| controls.update(); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| renderer.setAnimationLoop(animate); | |
| // Responsive | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Empêcher le scroll tactile | |
| renderer.domElement.addEventListener('touchstart', e => e.preventDefault(), { passive: false }); | |
| } catch (e) { | |
| showError(e.message); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> |