Spaces:
Sleeping
Sleeping
| import React, { Suspense, useState, useEffect } from "react"; | |
| import { Canvas } from "@react-three/fiber"; | |
| import { OrbitControls, Environment } from "@react-three/drei"; | |
| import AvatarModel from "./AvatarModel"; | |
| import Loader from "./Loader"; | |
| // Vérification de la disponibilité WebGL | |
| function checkWebGLSupport() { | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| return !!( | |
| window.WebGLRenderingContext && | |
| (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')) | |
| ); | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| export default function Avatar3D({ audioUrl }) { // Accepte audioUrl comme prop optionnelle | |
| const [webGLAvailable, setWebGLAvailable] = useState(true); | |
| const [loading, setLoading] = useState(true); | |
| useEffect(() => { | |
| setWebGLAvailable(checkWebGLSupport()); | |
| }, []); | |
| if (!webGLAvailable) { | |
| return ( | |
| <div className="h-full flex flex-col items-center justify-center bg-gray-50 rounded-2xl p-6 text-center"> | |
| <div className="max-w-md"> | |
| <h2 className="text-xl font-bold text-red-600 mb-3">WebGL non supporté</h2> | |
| <p className="mb-4 text-gray-700"> | |
| Votre navigateur ne supporte pas WebGL, nécessaire pour afficher l'avatar 3D. | |
| Veuillez utiliser un navigateur moderne comme Chrome, Firefox ou Edge. | |
| </p> | |
| <div className="bg-gray-200 border-2 border-dashed rounded-xl w-full h-64 flex items-center justify-center"> | |
| <span className="text-gray-500">Prévisualisation non disponible</span> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="w-full h-full relative"> | |
| <Canvas | |
| camera={{ position: [0, 1.6, 3], fov: 50 }} | |
| className="rounded-2xl" | |
| onCreated={() => setLoading(false)} | |
| > | |
| {/* Éclairage amélioré */} | |
| <ambientLight intensity={0.8} /> | |
| <directionalLight | |
| position={[5, 5, 5]} | |
| intensity={1.2} | |
| castShadow | |
| shadow-mapSize-width={1024} | |
| shadow-mapSize-height={1024} | |
| /> | |
| <pointLight position={[-5, 5, 5]} intensity={0.8} /> | |
| {/* Environnement */} | |
| <Environment preset="city" /> | |
| {/* Avatar */} | |
| <Suspense fallback={null}> | |
| <AvatarModel | |
| scale={[1, 1, 1]} | |
| position={[0, -1, 0]} | |
| audioUrl={audioUrl} // Passe audioUrl pour la synchronisation labiale | |
| /> | |
| </Suspense> | |
| {/* Contrôles */} | |
| <OrbitControls | |
| enablePan={false} | |
| enableZoom={true} | |
| enableRotate={true} | |
| maxPolarAngle={Math.PI / 2} | |
| minPolarAngle={0} | |
| minDistance={2} | |
| maxDistance={5} | |
| /> | |
| </Canvas> | |
| {loading && ( | |
| <div className="absolute inset-0 flex items-center justify-center bg-white/80 backdrop-blur-sm rounded-2xl z-10"> | |
| <div className="text-center"> | |
| <div className="inline-block relative"> | |
| <div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div> | |
| <div className="absolute inset-0 flex items-center justify-center"> | |
| <div className="w-8 h-8 bg-blue-500 rounded-full animate-ping"></div> | |
| </div> | |
| </div> | |
| <p className="mt-4 text-gray-700 font-medium">Chargement de l'avatar...</p> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |