Buckets:
| import { useRef, useState, useMemo } from 'react'; | |
| import { Canvas, useFrame, useThree } from '@react-three/fiber'; | |
| import { OrbitControls, Grid, Text } from '@react-three/drei'; | |
| import * as THREE from 'three'; | |
| // Composant pour le maillage | |
| function Mesh({ meshData, showWireframe, showFaces }) { | |
| const meshRef = useRef(); | |
| const geometry = useMemo(() => { | |
| const geom = new THREE.BufferGeometry(); | |
| // Créer les vertices (avec z=0 pour un maillage 2D en 3D) | |
| const vertices = []; | |
| meshData.vertices.forEach(([x, y]) => { | |
| vertices.push(x, y, 0); | |
| }); | |
| // 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.setIndex(indices); | |
| geom.computeVertexNormals(); | |
| return geom; | |
| }, [meshData]); | |
| return ( | |
| <group ref={meshRef}> | |
| {/* Faces */} | |
| {showFaces && ( | |
| <mesh geometry={geometry}> | |
| <meshStandardMaterial | |
| color="#3498db" | |
| side={THREE.DoubleSide} | |
| transparent | |
| opacity={0.6} | |
| /> | |
| </mesh> | |
| )} | |
| {/* Wireframe */} | |
| {showWireframe && ( | |
| <lineSegments> | |
| <edgesGeometry args={[geometry]} /> | |
| <lineBasicMaterial color="#00ff88" linewidth={1} /> | |
| </lineSegments> | |
| )} | |
| </group> | |
| ); | |
| } | |
| // Composant pour les axes | |
| function Axes({ bounds }) { | |
| const { x_min, x_max, y_min, y_max } = bounds; | |
| const cx = (x_min + x_max) / 2; | |
| const cy = (y_min + y_max) / 2; | |
| return ( | |
| <group position={[0, 0, -0.01]}> | |
| {/* Axe X */} | |
| <line> | |
| <bufferGeometry> | |
| <bufferAttribute | |
| attach="attributes-position" | |
| count={2} | |
| array={new Float32Array([x_min - 0.2, 0, 0, x_max + 0.2, 0, 0])} | |
| itemSize={3} | |
| /> | |
| </bufferGeometry> | |
| <lineBasicMaterial color="#ff6b6b" /> | |
| </line> | |
| {/* Axe Y */} | |
| <line> | |
| <bufferGeometry> | |
| <bufferAttribute | |
| attach="attributes-position" | |
| count={2} | |
| array={new Float32Array([0, y_min - 0.2, 0, 0, y_max + 0.2, 0])} | |
| itemSize={3} | |
| /> | |
| </bufferGeometry> | |
| <lineBasicMaterial color="#00ff88" /> | |
| </line> | |
| </group> | |
| ); | |
| } | |
| // Composant pour les labels | |
| function Labels({ bounds }) { | |
| const { x_min, x_max, y_min, y_max } = bounds; | |
| return ( | |
| <group> | |
| <Text position={[x_max + 0.3, 0, 0]} fontSize={0.1} color="#ff6b6b">X</Text> | |
| <Text position={[0, y_max + 0.2, 0]} fontSize={0.1} color="#00ff88">Y</Text> | |
| <Text position={[x_min, -0.15, 0]} fontSize={0.08} color="#888">{x_min}</Text> | |
| <Text position={[x_max, -0.15, 0]} fontSize={0.08} color="#888">{x_max}</Text> | |
| <Text position={[-0.15, y_min, 0]} fontSize={0.08} color="#888">{y_min}</Text> | |
| <Text position={[-0.15, y_max, 0]} fontSize={0.08} color="#888">{y_max}</Text> | |
| </group> | |
| ); | |
| } | |
| // Composant Camera | |
| function CameraSetup({ bounds }) { | |
| 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); | |
| camera.position.set(cx, cy, size * 1.5); | |
| camera.lookAt(cx, cy, 0); | |
| }, [bounds, camera]); | |
| return null; | |
| } | |
| export default function MeshViewer3D({ meshData }) { | |
| const [showWireframe, setShowWireframe] = useState(true); | |
| const [showFaces, setShowFaces] = useState(true); | |
| if (!meshData) return null; | |
| const bounds = meshData.bounds; | |
| const cx = (bounds.x_min + bounds.x_max) / 2; | |
| const cy = (bounds.y_min + bounds.y_max) / 2; | |
| return ( | |
| <div className="mesh-viewer-3d"> | |
| <div className="viewer-controls"> | |
| <label> | |
| <input | |
| type="checkbox" | |
| checked={showWireframe} | |
| onChange={(e) => setShowWireframe(e.target.checked)} | |
| /> | |
| Wireframe | |
| </label> | |
| <label> | |
| <input | |
| type="checkbox" | |
| checked={showFaces} | |
| onChange={(e) => setShowFaces(e.target.checked)} | |
| /> | |
| Faces | |
| </label> | |
| <span className="info"> | |
| {meshData.num_vertices} sommets | {meshData.num_triangles} triangles | |
| </span> | |
| </div> | |
| <Canvas camera={{ position: [cx, cy, 2], fov: 50 }}> | |
| <color attach="background" args={['#1a1a2e']} /> | |
| <ambientLight intensity={0.5} /> | |
| <directionalLight position={[5, 5, 5]} intensity={1} /> | |
| <CameraSetup bounds={bounds} /> | |
| <Mesh | |
| meshData={meshData} | |
| showWireframe={showWireframe} | |
| showFaces={showFaces} | |
| /> | |
| <Axes bounds={bounds} /> | |
| <Labels bounds={bounds} /> | |
| <OrbitControls | |
| target={[cx, cy, 0]} | |
| enablePan={true} | |
| enableZoom={true} | |
| enableRotate={true} | |
| /> | |
| <gridHelper | |
| args={[4, 20, '#333', '#222']} | |
| position={[cx, cy, -0.1]} | |
| rotation={[Math.PI / 2, 0, 0]} | |
| /> | |
| </Canvas> | |
| <div className="viewer-help"> | |
| Clic gauche: rotation | Clic droit: déplacement | Molette: zoom | |
| </div> | |
| </div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 6.55 kB
- Xet hash:
- c61c57f110ac5cd9ed7b74676f6860738f4436d000c059e353a436e041eca544
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.