download
raw
9.07 kB
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.