🚀 Auto-deploy: BrainWind atualizado em 12/07/2026 17:11:46

This commit is contained in:
2026-07-12 17:11:46 +00:00
parent 7ca5385651
commit 58ac69ba18
14 changed files with 198 additions and 60 deletions
+1 -1
View File
@@ -191,7 +191,7 @@ export default function Bar3DViewer(input: Bar3DInput) {
);
return (
<SceneCanvas
<SceneCanvas moduleId="barras"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [size, size * 0.6, size], fov: 45 }}
+1 -1
View File
@@ -125,7 +125,7 @@ export default function Bridge3DViewer(input: Bridge3DInput) {
);
return (
<SceneCanvas
<SceneCanvas moduleId="ponte"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [dist * 0.8, deckHeight + width, dist], fov: 45 }}
+1 -1
View File
@@ -182,7 +182,7 @@ export default function Cylinder3DViewer({ diameter, height, cpeProfile, cpi }:
const maxDim = Math.max(diameter, height);
return (
<SceneCanvas
<SceneCanvas moduleId="cilindro"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [diameter * 1.5, height * 1.2, diameter * 1.5], fov: 40 }}
+1 -1
View File
@@ -179,7 +179,7 @@ export default function Dome3DViewer(props: Dome3DInput) {
const maxDim = Math.max(props.diameter, props.wallHeight + props.rise);
return (
<SceneCanvas
<SceneCanvas moduleId="cupula"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [props.diameter * 1.5, (props.wallHeight + props.rise) * 1.5, props.diameter * 1.5], fov: 40 }}
+1 -1
View File
@@ -191,7 +191,7 @@ export default function Dynamics3DViewer(props: Dynamics3DInput) {
);
return (
<SceneCanvas
<SceneCanvas moduleId="dinamica"
frameloop="always"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
+39 -8
View File
@@ -28,11 +28,17 @@ export interface IsolatedRoof3DInput {
forceKN: number;
}
function pressureColor(cpe: number): THREE.Color {
const clamped = Math.max(-2.5, Math.min(1.5, cpe));
const t = (clamped + 2.5) / 4.0;
const h = 240 - t * 240; // azul -> vermelho
return new THREE.Color(`hsl(${h}, 75%, 50%)`);
function pressureColor(cpe: number, isDark: boolean): THREE.Color {
if (Math.abs(cpe) < 0.05) return new THREE.Color(isDark ? '#475569' : '#cbd5e1');
if (cpe > 0) {
// Sucção (Vermelho)
const intensity = Math.min(1, cpe / 2.0);
return new THREE.Color(`hsl(0, ${60 + intensity * 30}%, ${50 - intensity * 10}%)`);
} else {
// Pressão (Azul)
const intensity = Math.min(1, Math.abs(cpe) / 2.0);
return new THREE.Color(`hsl(210, ${60 + intensity * 30}%, ${50 - intensity * 10}%)`);
}
}
function IsolatedRoofModel({
@@ -56,8 +62,8 @@ function IsolatedRoofModel({
const halfDepth = depth / 2;
const halfWidth = width / 2;
const windwardColor = useMemo(() => pressureColor(cpeWindward), [cpeWindward]);
const leewardColor = useMemo(() => pressureColor(cpeLeeward), [cpeLeeward]);
const windwardColor = useMemo(() => pressureColor(cpeWindward, isDark), [cpeWindward, isDark]);
const leewardColor = useMemo(() => pressureColor(cpeLeeward, isDark), [cpeLeeward, isDark]);
const h_diff = depth * Math.tan(thetaRad);
const h_half = (depth / 2) * Math.tan(thetaRad);
@@ -214,6 +220,31 @@ function IsolatedRoofModel({
</Text>
</group>
{/* Cota de Largura (b) */}
<group position={[0, height / 2, halfDepth + 0.4]}>
<mesh position={[0, 0, 0]}>
<boxGeometry args={[width, 0.015, 0.015]} />
<meshStandardMaterial color={lineMaterialColor} />
</mesh>
<mesh position={[halfWidth, 0, 0]}>
<boxGeometry args={[0.015, 0.1, 0.015]} />
<meshStandardMaterial color={lineMaterialColor} />
</mesh>
<mesh position={[-halfWidth, 0, 0]}>
<boxGeometry args={[0.015, 0.1, 0.015]} />
<meshStandardMaterial color={lineMaterialColor} />
</mesh>
<Text
position={[0, 0.15, 0]}
fontSize={0.25}
color={labelColor}
anchorX="center"
anchorY="bottom"
>
b = {width}m
</Text>
</group>
{/* Rótulo Superior */}
<Text
position={[0, centerY + 3, 0]}
@@ -248,7 +279,7 @@ export default function IsolatedRoof3DViewer({
);
return (
<SceneCanvas
<SceneCanvas moduleId="cobertura_isolada"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [cameraDistance, cameraDistance * 0.8, cameraDistance], fov: 40 }}
+4 -5
View File
@@ -53,11 +53,10 @@ function SignModel({
[applicationPoint, height, baseY],
);
// Cor da placa baseada no Cf (mais vermelho = mais carga)
// Cor da placa: Azul (Pressão/Empuxo) com intensidade proporcional ao Cf
const plateColor = useMemo(() => {
const intensity = Math.min(1, Math.abs(cf) / 2.0);
const hue = 220 - intensity * 220; // azul → vermelho
return new THREE.Color(`hsl(${hue}, ${60 + intensity * 30}%, ${50 - intensity * 10}%)`);
return new THREE.Color(`hsl(210, ${60 + intensity * 30}%, ${50 - intensity * 10}%)`);
}, [cf]);
// Pontas de extremidade (placas de extremidade opcionais)
@@ -185,7 +184,7 @@ function SignModel({
{/* Texto Informativo Superior */}
<Text
position={[0, topY + 0.6, 0]}
rotation={[0, Math.PI / 2, 0]}
rotation={[0, -Math.PI / 2, 0]}
fontSize={Math.max(0.25, Math.min(0.4, length / 12))}
color={isDark ? "#cbd5e1" : "#1a202c"}
anchorX="center"
@@ -221,7 +220,7 @@ export default function Sign3DViewer({
const maxDim = Math.max(length, height);
return (
<SceneCanvas
<SceneCanvas moduleId="muro_placa"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [length * 1.3, height * 1.5, length * 1.3], fov: 40 }}
+1 -1
View File
@@ -239,7 +239,7 @@ export default function Tower3DViewer(input: Tower3DInput) {
);
return (
<SceneCanvas
<SceneCanvas moduleId="torre"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [dist, height * 0.6, dist], fov: 45 }}
+1 -1
View File
@@ -200,7 +200,7 @@ export default function Vault3DViewer({ span, length, rise, cpi, cpeProfile, cpe
const maxDimension = Math.max(span, length, rise);
return (
<SceneCanvas
<SceneCanvas moduleId="abobada"
shadows
gl={{ preserveDrawingBuffer: true, antialias: true }}
camera={{ position: [span * 1.2, rise * 1.5, length * 1.2], fov: 40 }}
@@ -1,11 +1,51 @@
import React from 'react';
import React, { useContext, useEffect, useRef } from 'react';
import { OrbitControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber';
import { useViewerStore } from '@/store/viewerStore';
import { ViewerModuleContext } from '../SceneCanvas';
type OrbitControlsProps = React.ComponentProps<typeof OrbitControls>;
export const ViewerOrbitControls: React.FC<OrbitControlsProps> = (props) => {
const isLocked = useViewerStore((state) => state.isLocked);
const moduleId = useContext(ViewerModuleContext);
const { locks, cameras, saveCamera } = useViewerStore();
const { camera } = useThree();
const controlsRef = useRef<any>(null);
return <OrbitControls enabled={!isLocked} {...props} />;
const isLocked = moduleId ? !!locks[moduleId] : false;
// Carrega a posição salva da câmera se existir
useEffect(() => {
if (moduleId && cameras[moduleId] && controlsRef.current) {
const saved = cameras[moduleId];
camera.position.set(saved.position[0], saved.position[1], saved.position[2]);
controlsRef.current.target.set(saved.target[0], saved.target[1], saved.target[2]);
controlsRef.current.update();
}
}, [moduleId, camera]); // executado uma vez por módulo
const handleEnd = () => {
if (moduleId && controlsRef.current) {
saveCamera(moduleId, {
position: [camera.position.x, camera.position.y, camera.position.z],
target: [
controlsRef.current.target.x,
controlsRef.current.target.y,
controlsRef.current.target.z
]
});
}
if (props.onEnd) {
props.onEnd(undefined as any);
}
};
return (
<OrbitControls
ref={controlsRef}
enabled={!isLocked}
onEnd={handleEnd}
{...props}
/>
);
};