🚀 Auto-deploy: BrainWind atualizado em 29/07/2026 12:00:49
This commit is contained in:
@@ -2,9 +2,11 @@ import React, { useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { Text, Grid, Environment } from '@react-three/drei';
|
||||
import { WindArrow as GlobalWindArrow } from './WindArrow';
|
||||
import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls';
|
||||
import SceneCanvas from '../SceneCanvas';
|
||||
import { useCanvasTheme } from '@/lib/theme';
|
||||
import { ShelterAirflowSystem } from './ShelterAirflowSystem';
|
||||
import type { ShelterCondition, OpeningPosition, ShelterWindAngle } from '@/lib/nbr-tables/table-shelters';
|
||||
|
||||
export interface Shelter3DProps {
|
||||
@@ -19,43 +21,9 @@ export interface Shelter3DProps {
|
||||
openingPos: OpeningPosition;
|
||||
backRange?: [number, number];
|
||||
windAngle?: ShelterWindAngle;
|
||||
viewMode: '3d' | 'elevation';
|
||||
viewMode: '3d' | 'elevation' | 'airflow';
|
||||
}
|
||||
|
||||
const WindArrow: React.FC<{
|
||||
start: [number, number, number];
|
||||
end: [number, number, number];
|
||||
color?: string;
|
||||
speed?: number;
|
||||
}> = ({ start, end, color = '#38bdf8', speed = 1.5 }) => {
|
||||
const arrowRef = useRef<THREE.Group>(null);
|
||||
|
||||
const quaternion = React.useMemo(() => {
|
||||
const dir = new THREE.Vector3(...end).sub(new THREE.Vector3(...start)).normalize();
|
||||
return new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
|
||||
}, [start, end]);
|
||||
|
||||
useFrame(({ clock, invalidate }) => {
|
||||
if (arrowRef.current) {
|
||||
const t = (clock.getElapsedTime() * speed) % 1;
|
||||
const x = start[0] + (end[0] - start[0]) * t;
|
||||
const y = start[1] + (end[1] - start[1]) * t;
|
||||
const z = start[2] + (end[2] - start[2]) * t;
|
||||
arrowRef.current.position.set(x, y, z);
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={arrowRef} quaternion={quaternion}>
|
||||
<mesh>
|
||||
<coneGeometry args={[0.25, 0.6, 8]} />
|
||||
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.6} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
function ShelterModel({
|
||||
condition: _condition,
|
||||
depth,
|
||||
@@ -71,10 +39,13 @@ function ShelterModel({
|
||||
viewMode,
|
||||
}: Shelter3DProps) {
|
||||
const isElevation = viewMode === 'elevation';
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
// Geometria básica
|
||||
const halfW = width / 2;
|
||||
const halfD = depth / 2;
|
||||
const isAirflow = viewMode === 'airflow';
|
||||
const thetaRad = (theta * Math.PI) / 180;
|
||||
const slopeRise = depth * Math.tan(thetaRad);
|
||||
|
||||
@@ -161,50 +132,51 @@ function ShelterModel({
|
||||
{/* Parede Lateral Direita (X = +halfW) */}
|
||||
{renderWallWithGap(depth, height, rightClosure, 'distributed', Math.PI / 2, [halfW, 0, 0])}
|
||||
|
||||
{/* 4. Linhas de Corrente e Vetores de Vento (Ilustrativo e Didático) */}
|
||||
<group position={[0, 0, 0]} rotation={[0, -((windAngle || 0) * Math.PI) / 180, 0]}>
|
||||
{/* Vento de entrada (Barlavento) entrando em direção ao abrigo */}
|
||||
<WindArrow start={[0, height * 0.5, -halfD - 4]} end={[0, height * 0.5, -halfD + 1]} color="#38bdf8" speed={1.2} />
|
||||
<WindArrow start={[-halfW * 0.5, height * 0.7, -halfD - 4]} end={[-halfW * 0.5, height * 0.7, -halfD + 1]} color="#38bdf8" speed={1.4} />
|
||||
<WindArrow start={[halfW * 0.5, height * 0.4, -halfD - 4]} end={[halfW * 0.5, height * 0.4, -halfD + 1]} color="#38bdf8" speed={1.3} />
|
||||
|
||||
{/* Linhas de corrente sobre o telhado */}
|
||||
<WindArrow start={[0, height + 1, -halfD - 3]} end={[0, height + slopeRise + 1.5, halfD + 2]} color="#60a5fa" speed={1.6} />
|
||||
|
||||
{/* Fresta na parede de fundo: se houver abertura no topo, mostrar vento escapando pela fresta superior */}
|
||||
{backClosure > 0 && backClosure < 100 && (openingPos === 'top' || (backRange && backRange[1] < 98)) && (
|
||||
<>
|
||||
<WindArrow start={[0, height * 0.85, 0]} end={[0, height * 0.95, halfD + 2.5]} color="#34d399" speed={1.8} />
|
||||
<Text position={[0, height + 0.35, halfD]} rotation={[0, Math.PI, 0]} fontSize={0.4} color="#34d399">
|
||||
Alívio (Fresta Topo)
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Fresta na base */}
|
||||
{backClosure > 0 && backClosure < 100 && (openingPos === 'bottom' || (backRange && backRange[0] > 2)) && (
|
||||
<>
|
||||
<WindArrow start={[0, 0.4, 0]} end={[0, 0.4, halfD + 2.5]} color="#facc15" speed={1.8} />
|
||||
<Text position={[0, 1.2, halfD]} rotation={[0, Math.PI, 0]} fontSize={0.4} color="#facc15">
|
||||
Vão Livre Inferior
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{/* Seta de Vento Global (Sempre visível para não pular de posição) */}
|
||||
<group>
|
||||
<GlobalWindArrow
|
||||
target={[-halfW * 1.2, height * 1.5 - 1.5, -halfD - 5 + 2]}
|
||||
direction={[0, 0, 1]}
|
||||
scale={4}
|
||||
/>
|
||||
<Text
|
||||
position={[-halfW * 1.2, height * 1.5, -halfD - 5]}
|
||||
rotation={[0, Math.PI / 4, 0]}
|
||||
fontSize={1.2}
|
||||
color={isDark ? '#60a5fa' : '#3b82f6'}
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
>
|
||||
Vento
|
||||
</Text>
|
||||
</group>
|
||||
|
||||
{/* Partículas de Ar */}
|
||||
{isAirflow && (
|
||||
<group>
|
||||
<ShelterAirflowSystem
|
||||
width={width}
|
||||
depth={depth}
|
||||
height={height}
|
||||
theta={theta}
|
||||
/>
|
||||
</group>
|
||||
)}
|
||||
|
||||
{/* Textos Informativos 3D - Virados para quem olha da frente (-Z) */}
|
||||
<Text position={[0, height + slopeRise + 0.8, 0]} rotation={[0, Math.PI, 0]} fontSize={0.45} color="#e2e8f0">
|
||||
{isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'}
|
||||
</Text>
|
||||
<Text position={[0, 0.3, -halfD - 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.35} color="#38bdf8">
|
||||
{`VENTO ${windAngle === 0 ? 'FRONTAL (0°)' : windAngle === 45 ? 'OBLÍQUO (45°)' : 'LATERAL (90°)'}`}
|
||||
</Text>
|
||||
<Text position={[0, height + slopeRise + 0.25, 0]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#f43f5e">
|
||||
Arrancamento [+Fz ↑]
|
||||
</Text>
|
||||
<Text position={[0, height * 0.5, halfD + 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#fb923c">
|
||||
Empuxo no Fundo [+Fx →]
|
||||
</Text>
|
||||
{!isAirflow && (
|
||||
<>
|
||||
<Text position={[0, height + slopeRise + 1.2, 0]} rotation={[0, Math.PI, 0]} fontSize={0.45} color="#e2e8f0">
|
||||
{isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'}
|
||||
</Text>
|
||||
<Text position={[0, height + slopeRise + 0.5, 0]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#f43f5e">
|
||||
Arrancamento [+Fz ↑]
|
||||
</Text>
|
||||
<Text position={[0, height * 0.5, halfD + 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#fb923c">
|
||||
Empuxo no Fundo [+Fx →]
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -212,13 +184,14 @@ function ShelterModel({
|
||||
export function Shelter3D(props: Shelter3DProps) {
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const { width, depth, height } = props;
|
||||
const { width, depth, height, viewMode } = props;
|
||||
const dist = Math.max(width, depth) * 1.8 + 8;
|
||||
|
||||
return (
|
||||
<SceneCanvas
|
||||
moduleId="shelter"
|
||||
shadows
|
||||
frameloop={viewMode === 'airflow' ? 'always' : 'demand'}
|
||||
gl={{ preserveDrawingBuffer: true, antialias: true }}
|
||||
camera={{ position: [dist * 0.75, height * 1.1, -dist * 0.85], fov: 45 }}
|
||||
fallback={<div className="flex items-center justify-center h-full text-muted-foreground">3D indisponível</div>}
|
||||
|
||||
Reference in New Issue
Block a user