scleton / view.html
mohamedtsou's picture
Update view.html
ca5e733 verified
<!DOCTYPE html>
<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>