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