Buckets:
| import { useRef, useState, useMemo } from 'react'; | |
| import { Canvas, useThree } from '@react-three/fiber'; | |
| import { OrbitControls, Text } from '@react-three/drei'; | |
| import * as THREE from 'three'; | |
| // Fonction pour convertir une valeur en couleur (colormap jet-like) | |
| function valueToColor(value, min, max) { | |
| const t = (value - min) / (max - min); | |
| // Colormap similaire à "jet" (bleu -> cyan -> vert -> jaune -> rouge) | |
| let r, g, b; | |
| if (t < 0.25) { | |
| r = 0; | |
| g = 4 * t; | |
| b = 1; | |
| } else if (t < 0.5) { | |
| r = 0; | |
| g = 1; | |
| b = 1 - 4 * (t - 0.25); | |
| } else if (t < 0.75) { | |
| r = 4 * (t - 0.5); | |
| g = 1; | |
| b = 0; | |
| } else { | |
| r = 1; | |
| g = 1 - 4 * (t - 0.75); | |
| b = 0; | |
| } | |
| return [r, g, b]; | |
| } | |
| // Composant pour le maillage coloré par la solution | |
| function SolutionMesh({ meshData, solutionData, showWireframe, elevationScale }) { | |
| const geometry = useMemo(() => { | |
| const geom = new THREE.BufferGeometry(); | |
| const { min, max, values } = solutionData; | |
| // Créer les vertices avec élévation basée sur la solution | |
| const vertices = []; | |
| const colors = []; | |
| meshData.vertices.forEach(([x, y], i) => { | |
| const z = values[i] * elevationScale; | |
| vertices.push(x, y, z); | |
| const [r, g, b] = valueToColor(values[i], min, max); | |
| colors.push(r, g, b); | |
| }); | |
| // Créer les indices pour les triangles | |
| const indices = []; | |
| meshData.triangles.forEach(tri => { | |
| indices.push(...tri); | |
| }); | |
| geom.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); | |
| geom.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); | |
| geom.setIndex(indices); | |
| geom.computeVertexNormals(); | |
| return geom; | |
| }, [meshData, solutionData, elevationScale]); | |
| return ( | |
| <group> | |
| {/* Mesh coloré */} | |
| <mesh geometry={geometry}> | |
| <meshStandardMaterial | |
| vertexColors | |
| side={THREE.DoubleSide} | |
| /> | |
| </mesh> | |
| {/* Wireframe optionnel */} | |
| {showWireframe && ( | |
| <lineSegments> | |
| <edgesGeometry args={[geometry]} /> | |
| <lineBasicMaterial color="#000" linewidth={1} opacity={0.3} transparent /> | |
| </lineSegments> | |
| )} | |
| </group> | |
| ); | |
| } | |
| // Barre de couleur (colorbar) | |
| function ColorBar({ min, max }) { | |
| const segments = 50; | |
| return ( | |
| <div className="colorbar"> | |
| <div className="colorbar-gradient"> | |
| {Array.from({ length: segments }).map((_, i) => { | |
| const t = i / (segments - 1); | |
| const [r, g, b] = valueToColor(t, 0, 1); | |
| return ( | |
| <div | |
| key={i} | |
| style={{ | |
| flex: 1, | |
| backgroundColor: `rgb(${r*255}, ${g*255}, ${b*255})` | |
| }} | |
| /> | |
| ); | |
| })} | |
| </div> | |
| <div className="colorbar-labels"> | |
| <span>{min?.toFixed(4)}</span> | |
| <span>{((min + max) / 2)?.toFixed(4)}</span> | |
| <span>{max?.toFixed(4)}</span> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Camera setup | |
| function CameraSetup({ bounds, elevationScale, solutionMax }) { | |
| const { camera } = useThree(); | |
| const { x_min, x_max, y_min, y_max } = bounds; | |
| useMemo(() => { | |
| const cx = (x_min + x_max) / 2; | |
| const cy = (y_min + y_max) / 2; | |
| const size = Math.max(x_max - x_min, y_max - y_min); | |
| const height = solutionMax * elevationScale; | |
| camera.position.set(cx + size, cy + size * 0.5, size + height); | |
| camera.lookAt(cx, cy, height / 2); | |
| }, [bounds, camera, elevationScale, solutionMax]); | |
| return null; | |
| } | |
| export default function SolutionViewer({ data }) { | |
| const [showWireframe, setShowWireframe] = useState(false); | |
| const [elevationScale, setElevationScale] = useState(0.3); | |
| const [viewMode, setViewMode] = useState('3d'); // '3d' ou '2d' | |
| // Debug logging | |
| console.log('SolutionViewer data:', data); | |
| console.log('mesh:', data?.mesh); | |
| console.log('solution:', data?.solution); | |
| if (!data || !data.mesh || !data.solution) { | |
| console.log('SolutionViewer: données manquantes', { | |
| hasData: !!data, | |
| hasMesh: !!data?.mesh, | |
| hasSolution: !!data?.solution | |
| }); | |
| return <div className="placeholder">Aucune solution disponible</div>; | |
| } | |
| // Vérifier que les données sont valides | |
| if (!data.solution.values || !Array.isArray(data.solution.values)) { | |
| console.error('Solution values missing or not an array:', data.solution); | |
| return <div className="placeholder">Données de solution invalides</div>; | |
| } | |
| if (!data.mesh.vertices || !Array.isArray(data.mesh.vertices)) { | |
| console.error('Mesh vertices missing or not an array:', data.mesh); | |
| return <div className="placeholder">Données de maillage invalides</div>; | |
| } | |
| const { mesh, solution } = data; | |
| const bounds = mesh.bounds; | |
| const cx = (bounds.x_min + bounds.x_max) / 2; | |
| const cy = (bounds.y_min + bounds.y_max) / 2; | |
| return ( | |
| <div className="solution-viewer"> | |
| <div className="viewer-controls"> | |
| <label> | |
| <input | |
| type="checkbox" | |
| checked={showWireframe} | |
| onChange={(e) => setShowWireframe(e.target.checked)} | |
| /> | |
| Wireframe | |
| </label> | |
| <div className="control-group"> | |
| <label>Élévation: {elevationScale.toFixed(2)}</label> | |
| <input | |
| type="range" | |
| min="0" | |
| max="1" | |
| step="0.05" | |
| value={elevationScale} | |
| onChange={(e) => setElevationScale(parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| <div className="view-mode-toggle"> | |
| <button | |
| className={viewMode === '3d' ? 'active' : ''} | |
| onClick={() => setViewMode('3d')} | |
| > | |
| 3D | |
| </button> | |
| <button | |
| className={viewMode === '2d' ? 'active' : ''} | |
| onClick={() => setViewMode('2d')} | |
| > | |
| 2D | |
| </button> | |
| </div> | |
| </div> | |
| <div className="canvas-container"> | |
| <Canvas camera={{ position: [cx + 2, cy + 1, 2], fov: 50 }}> | |
| <color attach="background" args={['#1a1a2e']} /> | |
| <ambientLight intensity={0.6} /> | |
| <directionalLight position={[5, 5, 5]} intensity={0.8} /> | |
| <directionalLight position={[-5, -5, 5]} intensity={0.4} /> | |
| <CameraSetup | |
| bounds={bounds} | |
| elevationScale={viewMode === '2d' ? 0 : elevationScale} | |
| solutionMax={solution.max} | |
| /> | |
| <SolutionMesh | |
| meshData={mesh} | |
| solutionData={solution} | |
| showWireframe={showWireframe} | |
| elevationScale={viewMode === '2d' ? 0 : elevationScale} | |
| /> | |
| <OrbitControls | |
| target={[cx, cy, viewMode === '2d' ? 0 : solution.max * elevationScale / 2]} | |
| enablePan={true} | |
| enableZoom={true} | |
| enableRotate={viewMode === '3d'} | |
| /> | |
| {/* Plan de référence */} | |
| <gridHelper | |
| args={[4, 20, '#333', '#222']} | |
| position={[cx, cy, -0.01]} | |
| rotation={[Math.PI / 2, 0, 0]} | |
| /> | |
| </Canvas> | |
| <ColorBar min={solution.min} max={solution.max} /> | |
| </div> | |
| <div className="viewer-help"> | |
| {viewMode === '3d' | |
| ? 'Clic gauche: rotation | Clic droit: déplacement | Molette: zoom' | |
| : 'Clic droit: déplacement | Molette: zoom' | |
| } | |
| </div> | |
| </div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 9.07 kB
- Xet hash:
- 616365a8791fe77e2dd77af4f068ebc9047326a684a09de6a5e42510ef455877
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.