feat: adicionar super manual didatico com fluxos 2d e sheet panel
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 { WindArrow } from './WindArrow';
|
||||
|
||||
export interface IsolatedRoof3DInput {
|
||||
/** Tipo de cobertura: 'shed' (uma água) ou 'gable' (duas águas) */
|
||||
@@ -11,7 +12,9 @@ export interface IsolatedRoof3DInput {
|
||||
theta: number;
|
||||
/** Altura livre dos suportes (m) */
|
||||
height: number;
|
||||
/** Profundidade da cobertura (m) — dimensão perpendicular à seção */
|
||||
/** Largura da cobertura (m) — dimensão transversal (b) */
|
||||
width: number;
|
||||
/** Profundidade da cobertura (m) — dimensão longitudinal (l) */
|
||||
depth: number;
|
||||
/** Cpe barlavento (sobre a face exposta ao vento) */
|
||||
cpeWindward: number;
|
||||
@@ -23,8 +26,6 @@ export interface IsolatedRoof3DInput {
|
||||
forceKN: number;
|
||||
}
|
||||
|
||||
const forceToLength = (kN: number): number => Math.min(Math.max(Math.abs(kN) * 0.15, 0.5), 6);
|
||||
|
||||
function pressureColor(cpe: number): THREE.Color {
|
||||
const clamped = Math.max(-2.5, Math.min(1.5, cpe));
|
||||
const t = (clamped + 2.5) / 4.0;
|
||||
@@ -36,6 +37,7 @@ function IsolatedRoofModel({
|
||||
type,
|
||||
theta,
|
||||
height,
|
||||
width,
|
||||
depth,
|
||||
cpeWindward,
|
||||
cpeLeeward,
|
||||
@@ -43,12 +45,11 @@ function IsolatedRoofModel({
|
||||
}: IsolatedRoof3DInput) {
|
||||
const thetaRad = (theta * Math.PI) / 180;
|
||||
const halfDepth = depth / 2;
|
||||
const halfWidth = width / 2;
|
||||
|
||||
const windwardColor = useMemo(() => pressureColor(cpeWindward), [cpeWindward]);
|
||||
const leewardColor = useMemo(() => pressureColor(cpeLeeward), [cpeLeeward]);
|
||||
|
||||
const arrowLen = forceToLength(forceKN);
|
||||
|
||||
const h_diff = depth * Math.tan(thetaRad);
|
||||
const h_half = (depth / 2) * Math.tan(thetaRad);
|
||||
|
||||
@@ -60,29 +61,29 @@ function IsolatedRoofModel({
|
||||
const list: { pos: [number, number, number]; h: number }[] = [];
|
||||
if (type === 'shed') {
|
||||
list.push(
|
||||
{ pos: [-halfDepth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [halfDepth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [-halfDepth, (height + h_diff) / 2, halfDepth], h: height + h_diff },
|
||||
{ pos: [halfDepth, (height + h_diff) / 2, halfDepth], h: height + h_diff },
|
||||
{ pos: [-halfWidth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [halfWidth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [-halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff },
|
||||
{ pos: [halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff },
|
||||
);
|
||||
} else {
|
||||
list.push(
|
||||
{ pos: [-halfDepth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [halfDepth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [-halfDepth, height / 2, halfDepth], h: height },
|
||||
{ pos: [halfDepth, height / 2, halfDepth], h: height },
|
||||
{ pos: [-halfDepth, (height + h_half) / 2, 0], h: height + h_half },
|
||||
{ pos: [halfDepth, (height + h_half) / 2, 0], h: height + h_half },
|
||||
{ pos: [-halfWidth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [halfWidth, height / 2, -halfDepth], h: height },
|
||||
{ pos: [-halfWidth, height / 2, halfDepth], h: height },
|
||||
{ pos: [halfWidth, height / 2, halfDepth], h: height },
|
||||
{ pos: [-halfWidth, (height + h_half) / 2, 0], h: height + h_half },
|
||||
{ pos: [halfWidth, (height + h_half) / 2, 0], h: height + h_half },
|
||||
);
|
||||
}
|
||||
return list;
|
||||
}, [type, depth, height, h_diff, h_half, halfDepth]);
|
||||
}, [type, depth, width, height, h_diff, h_half, halfDepth, halfWidth]);
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Solo translúcido */}
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.01, 0]} receiveShadow>
|
||||
<planeGeometry args={[depth * 2, depth * 2]} />
|
||||
<planeGeometry args={[width * 1.5, depth * 1.5]} />
|
||||
<meshStandardMaterial color="#94a3b8" transparent opacity={0.15} />
|
||||
</mesh>
|
||||
|
||||
@@ -97,7 +98,7 @@ function IsolatedRoofModel({
|
||||
castShadow
|
||||
receiveShadow
|
||||
>
|
||||
<boxGeometry args={[depth, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<boxGeometry args={[width, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<meshStandardMaterial color={windwardColor} opacity={0.9} transparent roughness={0.4} />
|
||||
</mesh>
|
||||
{/* Metade Sotavento (Z > 0) */}
|
||||
@@ -107,7 +108,7 @@ function IsolatedRoofModel({
|
||||
castShadow
|
||||
receiveShadow
|
||||
>
|
||||
<boxGeometry args={[depth, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<boxGeometry args={[width, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<meshStandardMaterial color={leewardColor} opacity={0.9} transparent roughness={0.4} />
|
||||
</mesh>
|
||||
</group>
|
||||
@@ -121,7 +122,7 @@ function IsolatedRoofModel({
|
||||
castShadow
|
||||
receiveShadow
|
||||
>
|
||||
<boxGeometry args={[depth, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<boxGeometry args={[width, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<meshStandardMaterial color={windwardColor} opacity={0.9} transparent roughness={0.4} />
|
||||
</mesh>
|
||||
{/* Água Direita / Sotavento (Z > 0) */}
|
||||
@@ -131,7 +132,7 @@ function IsolatedRoofModel({
|
||||
castShadow
|
||||
receiveShadow
|
||||
>
|
||||
<boxGeometry args={[depth, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<boxGeometry args={[width, 0.08, depth / (2 * Math.cos(thetaRad))]} />
|
||||
<meshStandardMaterial color={leewardColor} opacity={0.9} transparent roughness={0.4} />
|
||||
</mesh>
|
||||
</group>
|
||||
@@ -145,12 +146,10 @@ function IsolatedRoofModel({
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
{/* Seta de força resultante (sucção para cima) */}
|
||||
<ForceArrow
|
||||
start={new THREE.Vector3(0, centerY, 0)}
|
||||
direction={new THREE.Vector3(0, 1, 0)}
|
||||
length={arrowLen}
|
||||
color="#ef4444"
|
||||
{/* Vetor de vento (Seta verde/azul batendo no elemento) */}
|
||||
<WindArrow
|
||||
target={new THREE.Vector3(0, centerY, 0)}
|
||||
direction={new THREE.Vector3(0, 0, 1)}
|
||||
/>
|
||||
|
||||
{/* === LINHAS DE COTA (CAD-Style) === */}
|
||||
@@ -208,8 +207,7 @@ function IsolatedRoofModel({
|
||||
|
||||
{/* Rótulo Superior */}
|
||||
<Text
|
||||
position={[0, centerY + arrowLen + 0.8, 0]}
|
||||
fontSize={0.4}
|
||||
position={[0, centerY + 3, 0]}
|
||||
color="#1a202c"
|
||||
anchorX="center"
|
||||
anchorY="bottom"
|
||||
@@ -220,75 +218,23 @@ function IsolatedRoofModel({
|
||||
);
|
||||
}
|
||||
|
||||
function ForceArrow({
|
||||
start,
|
||||
direction,
|
||||
length,
|
||||
color,
|
||||
}: {
|
||||
start: THREE.Vector3;
|
||||
direction: THREE.Vector3;
|
||||
length: number;
|
||||
color: string;
|
||||
}) {
|
||||
const end = useMemo(
|
||||
() => new THREE.Vector3(start.x + direction.x * length, start.y + direction.y * length, start.z + direction.z * length),
|
||||
[start, direction, length],
|
||||
);
|
||||
const headLen = 0.3;
|
||||
const headRadius = 0.1;
|
||||
const shaftRadius = 0.04;
|
||||
|
||||
const midPoint = useMemo(
|
||||
() => new THREE.Vector3((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2),
|
||||
[start, end],
|
||||
);
|
||||
const shaftLength = length - headLen;
|
||||
|
||||
const rotation = useMemo(() => {
|
||||
const dir = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const quat = new THREE.Quaternion();
|
||||
quat.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
|
||||
const euler = new THREE.Euler().setFromQuaternion(quat);
|
||||
return [euler.x, euler.y, euler.z] as [number, number, number];
|
||||
}, [start, end]);
|
||||
|
||||
return (
|
||||
<group>
|
||||
{shaftLength > 0 && (
|
||||
<mesh position={midPoint.toArray()} rotation={rotation} castShadow>
|
||||
<cylinderGeometry args={[shaftRadius, shaftRadius, shaftLength, 12]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
)}
|
||||
<mesh
|
||||
position={[end.x, end.y, end.z]}
|
||||
rotation={rotation}
|
||||
castShadow
|
||||
>
|
||||
<coneGeometry args={[headRadius, headLen, 12]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function IsolatedRoof3DViewer({
|
||||
type,
|
||||
theta,
|
||||
height,
|
||||
width,
|
||||
depth,
|
||||
cpeWindward,
|
||||
cpeLeeward,
|
||||
cpeTop,
|
||||
forceKN,
|
||||
}: IsolatedRoof3DInput) {
|
||||
const cameraDistance = Math.max(depth * 1.3, height * 1.5, 8);
|
||||
const cameraDistance = Math.max(Math.max(width, depth) * 1.3, height * 1.5, 8);
|
||||
|
||||
const fallback = (
|
||||
<FallbackDiagram
|
||||
type="isolatedRoof"
|
||||
props={{ type, theta, height, depth, cpeWindward, cpeLeeward, cpeTop, forceKN }}
|
||||
props={{ type, theta, height, width, depth, cpeWindward, cpeLeeward, cpeTop, forceKN }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -311,6 +257,7 @@ export default function IsolatedRoof3DViewer({
|
||||
type={type}
|
||||
theta={theta}
|
||||
height={height}
|
||||
width={width}
|
||||
depth={depth}
|
||||
cpeWindward={cpeWindward}
|
||||
cpeLeeward={cpeLeeward}
|
||||
|
||||
Reference in New Issue
Block a user