feat: apply dark mode contrast corrections to all 3D modules
This commit is contained in:
@@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Bar3DInput {
|
||||
/** Tipo de seção */
|
||||
@@ -34,14 +35,18 @@ function BarModel({
|
||||
alpha,
|
||||
cx,
|
||||
}: Bar3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const barRadius = barType === 'circular' ? (diameter ?? 0.05) / 2 : Math.min(width ?? 0.1, 0.08) / 2;
|
||||
const barThickness = barType === 'circular' ? barRadius : barRadius * 0.5;
|
||||
|
||||
const barColor = useMemo(() => {
|
||||
const intensity = Math.min(1, Math.abs(cx) / 2.5);
|
||||
const hue = 215 - intensity * 215;
|
||||
return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${45 - intensity * 10}%)`);
|
||||
}, [cx]);
|
||||
const l = isDark ? (55 - intensity * 10) : (45 - intensity * 10);
|
||||
return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${l}%)`);
|
||||
}, [cx, isDark]);
|
||||
|
||||
// Ação do vento deve rotacionar a barra em seu próprio eixo longitudinal (roll).
|
||||
// O eixo longitudinal na nossa geometria é o X.
|
||||
@@ -87,7 +92,7 @@ function BarModel({
|
||||
<Text
|
||||
position={[0, -barRadius * 2 - 0.4, 0]}
|
||||
fontSize={0.3}
|
||||
color="#1e40af"
|
||||
color={isDark ? "#93c5fd" : "#1e40af"}
|
||||
anchorX="center"
|
||||
anchorY="top"
|
||||
>
|
||||
@@ -172,6 +177,8 @@ function SectionShape({
|
||||
}
|
||||
|
||||
export default function Bar3DViewer(input: Bar3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const { length, width, diameter } = input;
|
||||
const size = Math.max(length * 0.6, (width ?? diameter ?? 0.1) * 8);
|
||||
|
||||
@@ -192,7 +199,13 @@ export default function Bar3DViewer(input: Bar3DInput) {
|
||||
<ambientLight intensity={0.6} />
|
||||
<directionalLight position={[size, size, size]} intensity={1.2} castShadow shadow-mapSize-width={1024} shadow-mapSize-height={1024} />
|
||||
<BarModel {...input} />
|
||||
<Grid infiniteGrid fadeDistance={size * 2} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -size * 0.3, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={size * 2}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -size * 0.3, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { WindArrow } from './WindArrow';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Bridge3DInput {
|
||||
/** Maior vão Lₚ (m) */
|
||||
@@ -34,6 +35,9 @@ function BridgeModel({
|
||||
cx,
|
||||
alpha,
|
||||
}: Bridge3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const halfL = lp / 2;
|
||||
const halfW = width / 2;
|
||||
const deckThickness = Math.max(heg, 0.8);
|
||||
@@ -43,11 +47,14 @@ function BridgeModel({
|
||||
const deckColor = useMemo(() => {
|
||||
const intensity = Math.min(1, Math.abs(cx) / 3);
|
||||
const hue = 200 - intensity * 60;
|
||||
return new THREE.Color(`hsl(${hue}, ${55 + intensity * 30}%, ${50 - intensity * 8}%)`);
|
||||
}, [cx]);
|
||||
const l = isDark ? (60 - intensity * 8) : (50 - intensity * 8);
|
||||
return new THREE.Color(`hsl(${hue}, ${55 + intensity * 30}%, ${l}%)`);
|
||||
}, [cx, isDark]);
|
||||
|
||||
// Pilar heights: posicionar 3 pilares ao longo do vão
|
||||
const pillarHeights = useMemo(() => [deckY - 0.5, deckY - 0.5, deckY - 0.5], [deckY]);
|
||||
const barrierColor = isDark ? '#cbd5e1' : '#94a3b8';
|
||||
const pillarColor = isDark ? '#94a3b8' : '#64748b';
|
||||
|
||||
return (
|
||||
<group>
|
||||
@@ -60,11 +67,11 @@ function BridgeModel({
|
||||
{/* Guarda-rodas/barreira lateral */}
|
||||
<mesh position={[0, deckY + deckThickness / 2 + 0.3, halfW - 0.15]} castShadow>
|
||||
<boxGeometry args={[lp, 0.5, 0.1]} />
|
||||
<meshStandardMaterial color="#94a3b8" roughness={0.7} />
|
||||
<meshStandardMaterial color={barrierColor} roughness={0.7} />
|
||||
</mesh>
|
||||
<mesh position={[0, deckY + deckThickness / 2 + 0.3, -halfW + 0.15]} castShadow>
|
||||
<boxGeometry args={[lp, 0.5, 0.1]} />
|
||||
<meshStandardMaterial color="#94a3b8" roughness={0.7} />
|
||||
<meshStandardMaterial color={barrierColor} roughness={0.7} />
|
||||
</mesh>
|
||||
|
||||
{/* Pilares (3 ao longo do comprimento) */}
|
||||
@@ -73,7 +80,7 @@ function BridgeModel({
|
||||
return (
|
||||
<mesh key={`pillar-${i}`} position={[x, h / 2, 0]} castShadow receiveShadow>
|
||||
<boxGeometry args={[1.5, h, 1.5]} />
|
||||
<meshStandardMaterial color="#64748b" roughness={0.7} />
|
||||
<meshStandardMaterial color={pillarColor} roughness={0.7} />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
@@ -93,7 +100,7 @@ function BridgeModel({
|
||||
<Text
|
||||
position={[0, deckY + deckThickness + 1.5, 0]}
|
||||
fontSize={1.2}
|
||||
color="#1e40af"
|
||||
color={isDark ? "#93c5fd" : "#1e40af"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -103,9 +110,9 @@ function BridgeModel({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function Bridge3DViewer(input: Bridge3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const { lp, deckHeight, width } = input;
|
||||
const dist = Math.max(lp * 0.6, deckHeight * 2);
|
||||
|
||||
@@ -126,7 +133,13 @@ export default function Bridge3DViewer(input: Bridge3DInput) {
|
||||
<ambientLight intensity={0.6} />
|
||||
<directionalLight position={[lp, deckHeight * 3, width * 3]} intensity={1.2} castShadow shadow-mapSize-width={1024} shadow-mapSize-height={1024} />
|
||||
<BridgeModel {...input} />
|
||||
<Grid infiniteGrid fadeDistance={lp * 0.5} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={lp * 0.5}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Cylinder3DInput {
|
||||
diameter: number;
|
||||
@@ -22,6 +23,8 @@ function cylinderColor(cpe: number, cpi: number): THREE.Color {
|
||||
}
|
||||
|
||||
function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const segments = 64;
|
||||
const radius = diameter / 2;
|
||||
|
||||
@@ -121,11 +124,11 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
|
||||
{/* Anéis de detalhe (bordas do cilindro) */}
|
||||
<mesh position={[0, height + 0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[radius - 0.03, radius + 0.03, segments]} />
|
||||
<meshStandardMaterial color="#2d3748" roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#2d3748"} roughness={0.5} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[radius - 0.03, radius + 0.03, segments]} />
|
||||
<meshStandardMaterial color="#2d3748" roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#2d3748"} roughness={0.5} />
|
||||
</mesh>
|
||||
|
||||
{/* Seta indicativa de direção do vento */}
|
||||
@@ -154,7 +157,7 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
|
||||
<Text
|
||||
position={[0, height + 0.8, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, diameter / 10))}
|
||||
color="#1a202c"
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -165,6 +168,9 @@ function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
|
||||
}
|
||||
|
||||
export default function Cylinder3DViewer({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const fallback = (
|
||||
<FallbackDiagram
|
||||
type="cylinder"
|
||||
@@ -195,7 +201,13 @@ export default function Cylinder3DViewer({ diameter, height, cpeProfile, cpi }:
|
||||
shadow-camera-bottom={-maxDim}
|
||||
/>
|
||||
<CylinderModel diameter={diameter} height={height} cpeProfile={cpeProfile} cpi={cpi} />
|
||||
<Grid infiniteGrid fadeDistance={maxDim * 5} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={maxDim * 5}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Dome3DInput {
|
||||
diameter: number;
|
||||
@@ -14,16 +15,19 @@ export interface Dome3DInput {
|
||||
cpeLateral: number;
|
||||
}
|
||||
|
||||
function domeColor(cpe: number, cpi: number): THREE.Color {
|
||||
function domeColor(cpe: number, cpi: number, isDark: boolean): THREE.Color {
|
||||
const p = cpe - cpi;
|
||||
const intensity = Math.min(1, Math.abs(p) / 1.5);
|
||||
const l = isDark ? (65 - intensity * 15) : (60 - intensity * 25);
|
||||
if (p > 0) {
|
||||
return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 60 - intensity * 25)}%)`);
|
||||
return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, l)}%)`);
|
||||
}
|
||||
return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 60 - intensity * 20)}%)`);
|
||||
return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, l + 5)}%)`);
|
||||
}
|
||||
|
||||
function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cpeLateral }: Dome3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const radius = diameter / 2;
|
||||
const segments = 64;
|
||||
|
||||
@@ -31,12 +35,6 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp
|
||||
const domeGeoms = useMemo(() => {
|
||||
const arr: { startTheta: number; endTheta: number; color: THREE.Color }[] = [];
|
||||
// Divide a circunferência completa (360°) em 6 zonas (simétricas)
|
||||
// 0° a 60°: Barlavento
|
||||
// 60° a 120°: Topo
|
||||
// 120° a 180°: Lateral
|
||||
// 180° a 240°: Lateral (espelhado)
|
||||
// 240° a 300°: Topo (espelhado)
|
||||
// 300° a 360°: Barlavento (espelhado)
|
||||
const zones = [
|
||||
{ fromDeg: 0, toDeg: 60, cpe: cpeBarlavento },
|
||||
{ fromDeg: 60, toDeg: 120, cpe: cpeTopo },
|
||||
@@ -49,11 +47,11 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp
|
||||
arr.push({
|
||||
startTheta: (z.fromDeg * Math.PI) / 180,
|
||||
endTheta: (z.toDeg * Math.PI) / 180,
|
||||
color: domeColor(z.cpe, cpi),
|
||||
color: domeColor(z.cpe, cpi, isDark),
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}, [cpeBarlavento, cpeTopo, cpeLateral, cpi]);
|
||||
}, [cpeBarlavento, cpeTopo, cpeLateral, cpi, isDark]);
|
||||
|
||||
// Raio da esfera da calota esférica baseada na flecha (rise) e raio da base (radius)
|
||||
const rSphere = useMemo(() => {
|
||||
@@ -71,11 +69,11 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp
|
||||
{/* Detalhes de anéis metálicos nas bordas */}
|
||||
<mesh position={[0, 0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[radius - 0.03, radius + 0.03, segments]} />
|
||||
<meshStandardMaterial color="#2d3748" roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#2d3748"} roughness={0.5} />
|
||||
</mesh>
|
||||
<mesh position={[0, wallHeight, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[radius - 0.03, radius + 0.03, segments]} />
|
||||
<meshStandardMaterial color="#2d3748" roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#2d3748"} roughness={0.5} />
|
||||
</mesh>
|
||||
|
||||
{/* Cúpula de cobertura (Spherical Cap) segmentada */}
|
||||
@@ -156,7 +154,7 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp
|
||||
<Text
|
||||
position={[0, wallHeight + rise + 0.8, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, diameter / 12))}
|
||||
color="#1a202c"
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -167,6 +165,9 @@ function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cp
|
||||
}
|
||||
|
||||
export default function Dome3DViewer(props: Dome3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const fallback = (
|
||||
<FallbackDiagram
|
||||
type="dome"
|
||||
@@ -197,7 +198,13 @@ export default function Dome3DViewer(props: Dome3DInput) {
|
||||
shadow-camera-bottom={-maxDim}
|
||||
/>
|
||||
<DomeModel {...props} />
|
||||
<Grid infiniteGrid fadeDistance={maxDim * 5} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={maxDim * 5}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Dynamics3DInput {
|
||||
/** Altura da estrutura (m) */
|
||||
@@ -138,11 +139,13 @@ function VortexStreet({
|
||||
}
|
||||
|
||||
function DynamicsModel(props: Dynamics3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
return (
|
||||
<group>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.01, 0]} receiveShadow>
|
||||
<planeGeometry args={[20, 20]} />
|
||||
<meshStandardMaterial color="#94a3b8" transparent opacity={0.15} />
|
||||
<meshStandardMaterial color={isDark ? "#1e293b" : "#94a3b8"} transparent opacity={0.15} />
|
||||
</mesh>
|
||||
|
||||
<OscillatingBuilding
|
||||
@@ -164,7 +167,7 @@ function DynamicsModel(props: Dynamics3DInput) {
|
||||
<Text
|
||||
position={[0, props.height * SCALE + 0.8, 0]}
|
||||
fontSize={0.5}
|
||||
color="#1e40af"
|
||||
color={isDark ? "#93c5fd" : "#1e40af"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -175,6 +178,8 @@ function DynamicsModel(props: Dynamics3DInput) {
|
||||
}
|
||||
|
||||
export default function Dynamics3DViewer(props: Dynamics3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const cameraDistance = Math.max(props.height * SCALE * 2, 6);
|
||||
|
||||
const fallback = (
|
||||
@@ -201,7 +206,13 @@ export default function Dynamics3DViewer(props: Dynamics3DInput) {
|
||||
shadow-mapSize-height={1024}
|
||||
/>
|
||||
<DynamicsModel {...props} />
|
||||
<Grid infiniteGrid fadeDistance={50} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={50}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { WindArrow } from './WindArrow';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Sign3DInput {
|
||||
/** Comprimento ℓ (m) */
|
||||
@@ -34,6 +35,8 @@ function SignModel({
|
||||
cf,
|
||||
applicationPoint,
|
||||
}: Sign3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const baseY = groundClearance;
|
||||
const topY = baseY + height;
|
||||
const halfL = length / 2;
|
||||
@@ -72,11 +75,11 @@ function SignModel({
|
||||
<>
|
||||
<mesh position={[0, (baseY + topY) / 2, halfL]} castShadow>
|
||||
<boxGeometry args={[0.3, height * 0.95, 0.04]} />
|
||||
<meshStandardMaterial color="#64748b" opacity={0.6} transparent side={THREE.DoubleSide} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} opacity={0.6} transparent side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
<mesh position={[0, (baseY + topY) / 2, -halfL]} castShadow>
|
||||
<boxGeometry args={[0.3, height * 0.95, 0.04]} />
|
||||
<meshStandardMaterial color="#64748b" opacity={0.6} transparent side={THREE.DoubleSide} />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} opacity={0.6} transparent side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
</>
|
||||
)}
|
||||
@@ -84,13 +87,13 @@ function SignModel({
|
||||
{/* Linha do solo (base translúcida) */}
|
||||
<mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
||||
<planeGeometry args={[length * 1.5, length * 1.5]} />
|
||||
<meshStandardMaterial color="#94a3b8" opacity={0.15} transparent />
|
||||
<meshStandardMaterial color={isDark ? "#1e293b" : "#94a3b8"} opacity={0.15} transparent />
|
||||
</mesh>
|
||||
|
||||
{/* Eixo horizontal de referência de direção do vento */}
|
||||
<mesh position={[0, 0.005, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<planeGeometry args={[length * 1.5, 0.03]} />
|
||||
<meshStandardMaterial color="#475569" opacity={0.5} transparent />
|
||||
<meshStandardMaterial color={isDark ? "#334155" : "#475569"} opacity={0.5} transparent />
|
||||
</mesh>
|
||||
|
||||
{/* Marca da excentricidade (Ponto de Aplicação da Resultante) */}
|
||||
@@ -104,7 +107,7 @@ function SignModel({
|
||||
position={[0.2, (baseY + topY) / 2, applicationPoint]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
fontSize={0.15}
|
||||
color="#d97706"
|
||||
color={isDark ? "#fcd34d" : "#d97706"}
|
||||
anchorX="left"
|
||||
anchorY="middle"
|
||||
>
|
||||
@@ -123,24 +126,24 @@ function SignModel({
|
||||
{/* Linha vertical */}
|
||||
<mesh position={[0, (baseY + topY) / 2, 0]}>
|
||||
<boxGeometry args={[0.015, height, 0.015]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Traço superior */}
|
||||
<mesh position={[0, topY, 0]}>
|
||||
<boxGeometry args={[0.1, 0.015, 0.015]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Traço inferior */}
|
||||
<mesh position={[0, baseY, 0]}>
|
||||
<boxGeometry args={[0.1, 0.015, 0.015]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Texto da altura */}
|
||||
<Text
|
||||
position={[-0.15, (baseY + topY) / 2, 0]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
fontSize={0.25}
|
||||
color="#475569"
|
||||
color={isDark ? "#94a3b8" : "#475569"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
@@ -153,24 +156,24 @@ function SignModel({
|
||||
{/* Linha horizontal longitudinal */}
|
||||
<mesh position={[0, 0, 0]}>
|
||||
<boxGeometry args={[0.015, 0.015, length]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Traço frontal */}
|
||||
<mesh position={[0, 0, halfL]}>
|
||||
<boxGeometry args={[0.1, 0.015, 0.015]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Traço traseiro */}
|
||||
<mesh position={[0, 0, -halfL]}>
|
||||
<boxGeometry args={[0.1, 0.015, 0.015]} />
|
||||
<meshStandardMaterial color="#64748b" />
|
||||
<meshStandardMaterial color={isDark ? "#475569" : "#64748b"} />
|
||||
</mesh>
|
||||
{/* Texto do comprimento */}
|
||||
<Text
|
||||
position={[-0.15, 0, 0]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
fontSize={0.25}
|
||||
color="#475569"
|
||||
color={isDark ? "#94a3b8" : "#475569"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
@@ -182,7 +185,7 @@ function SignModel({
|
||||
<Text
|
||||
position={[0, topY + 0.6, 0]}
|
||||
fontSize={0.4}
|
||||
color="#1a202c"
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -203,6 +206,9 @@ export default function Sign3DViewer({
|
||||
forceKN,
|
||||
applicationPoint,
|
||||
}: Sign3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const fallback = (
|
||||
<FallbackDiagram
|
||||
type="sign"
|
||||
@@ -230,7 +236,13 @@ export default function Sign3DViewer({
|
||||
forceKN={forceKN}
|
||||
applicationPoint={applicationPoint}
|
||||
/>
|
||||
<Grid infiniteGrid fadeDistance={maxDim * 5} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={maxDim * 5}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import FallbackDiagram from '../FallbackDiagram';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
|
||||
export interface Vault3DInput {
|
||||
span: number;
|
||||
@@ -12,16 +13,19 @@ export interface Vault3DInput {
|
||||
cpeProfile: Record<string, number>;
|
||||
}
|
||||
|
||||
function vaultColor(cpe: number, cpi: number): THREE.Color {
|
||||
function vaultColor(cpe: number, cpi: number, isDark: boolean): THREE.Color {
|
||||
const p = cpe - cpi;
|
||||
const intensity = Math.min(1, Math.abs(p) / 1.5);
|
||||
const l = isDark ? (65 - intensity * 15) : (60 - intensity * 25);
|
||||
if (p > 0) {
|
||||
return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 60 - intensity * 25)}%)`);
|
||||
return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, l)}%)`);
|
||||
}
|
||||
return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 60 - intensity * 20)}%)`);
|
||||
return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, l + 5)}%)`);
|
||||
}
|
||||
|
||||
function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const segments = 64;
|
||||
const points = useMemo(() => {
|
||||
const pts: THREE.Vector3[] = [];
|
||||
@@ -66,7 +70,7 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) {
|
||||
<group>
|
||||
{/* Casca da Abóbada */}
|
||||
{zones.map((zone, idx) => {
|
||||
const color = vaultColor(zone.cpe, cpi);
|
||||
const color = vaultColor(zone.cpe, cpi, isDark);
|
||||
const verts: number[] = [];
|
||||
for (let i = zone.startIdx; i <= zone.endIdx; i++) {
|
||||
verts.push(points[i].x, points[i].y, points[i].z);
|
||||
@@ -94,20 +98,20 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) {
|
||||
{/* Tímpano Traseiro (Z = 0) */}
|
||||
<mesh position={[0, 0, 0]} castShadow receiveShadow>
|
||||
<shapeGeometry args={[archShape]} />
|
||||
<meshStandardMaterial color="#cbd5e1" opacity={0.7} transparent side={THREE.DoubleSide} roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#334155" : "#cbd5e1"} opacity={0.7} transparent side={THREE.DoubleSide} roughness={0.5} />
|
||||
</mesh>
|
||||
|
||||
{/* Tímpano Frontal (Z = length) */}
|
||||
<mesh position={[0, 0, length]} castShadow receiveShadow>
|
||||
<shapeGeometry args={[archShape]} />
|
||||
<meshStandardMaterial color="#cbd5e1" opacity={0.7} transparent side={THREE.DoubleSide} roughness={0.5} />
|
||||
<meshStandardMaterial color={isDark ? "#334155" : "#cbd5e1"} opacity={0.7} transparent side={THREE.DoubleSide} roughness={0.5} />
|
||||
</mesh>
|
||||
|
||||
{/* Rótulo de dimensões */}
|
||||
<Text
|
||||
position={[0, rise + 0.6, length / 2]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, span / 20))}
|
||||
color="#1a202c"
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
@@ -118,6 +122,9 @@ function VaultModel({ span, length, rise, cpi, cpeProfile }: Vault3DInput) {
|
||||
}
|
||||
|
||||
export default function Vault3DViewer({ span, length, rise, cpi, cpeProfile }: Vault3DInput) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const fallback = (
|
||||
<FallbackDiagram
|
||||
type="vault"
|
||||
@@ -143,7 +150,13 @@ export default function Vault3DViewer({ span, length, rise, cpi, cpeProfile }: V
|
||||
shadow-mapSize-height={1024}
|
||||
/>
|
||||
<VaultModel span={span} length={length} rise={rise} cpi={cpi} cpeProfile={cpeProfile} />
|
||||
<Grid infiniteGrid fadeDistance={maxDimension * 5} sectionColor="#94a3b8" cellColor="#cbd5e1" position={[0, -0.01, 0]} />
|
||||
<Grid
|
||||
infiniteGrid
|
||||
fadeDistance={maxDimension * 5}
|
||||
sectionColor={isDark ? "#475569" : "#94a3b8"}
|
||||
cellColor={isDark ? "#1e293b" : "#cbd5e1"}
|
||||
position={[0, -0.01, 0]}
|
||||
/>
|
||||
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2 - 0.05} />
|
||||
<Environment preset="city" />
|
||||
</SceneCanvas>
|
||||
|
||||
Reference in New Issue
Block a user