🚀 Auto-deploy: BrainWind atualizado em 28/07/2026 11:08:28

This commit is contained in:
2026-07-28 11:08:28 +00:00
parent 24067d61de
commit d83a832138
5 changed files with 221 additions and 48 deletions
+105
View File
@@ -0,0 +1,105 @@
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
interface AirflowSystemProps {
windAngle: number;
width: number;
length: number;
height: number;
permeabilityCase: string;
}
export function AirflowSystem({ windAngle, width, length, height, permeabilityCase }: AirflowSystemProps) {
const count = 300;
const meshRef = useRef<THREE.InstancedMesh>(null);
const particles = useMemo(() => {
const temp = [];
for (let i = 0; i < count; i++) {
temp.push({
position: new THREE.Vector3(
(Math.random() - 0.5) * (width * 3),
Math.random() * height * 2,
(Math.random() - 0.5) * (length * 3)
),
speed: 0.1 + Math.random() * 0.2,
wobbleSpeed: Math.random() * 0.05,
wobbleOffset: Math.random() * Math.PI * 2,
});
}
return temp;
}, [count, width, length, height]);
const dummy = useMemo(() => new THREE.Object3D(), []);
useFrame((state) => {
if (!meshRef.current) return;
const time = state.clock.elapsedTime;
const isParallel = windAngle === 90;
// Wind direction vector
const dir = isParallel ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(1, 0, 0);
// Bounds
const halfW = width / 2;
const halfL = length / 2;
particles.forEach((p, i) => {
// Move particle
p.position.addScaledVector(dir, p.speed);
// Add slight turbulence
p.position.y += Math.sin(time * p.wobbleSpeed * 10 + p.wobbleOffset) * 0.02;
const isInsideX = p.position.x > -halfW && p.position.x < halfW;
const isInsideZ = p.position.z > -halfL && p.position.z < halfL;
const isInsideY = p.position.y > 0 && p.position.y < height;
const isInsideBuilding = isInsideX && isInsideZ && isInsideY;
if (isInsideBuilding && permeabilityCase === 'airtight') {
// Push particle up to simulate wind going over the roof
p.position.y += 0.2;
}
// Reset if it goes too far
if (isParallel) {
if (p.position.z > halfL + 20) {
p.position.z = -halfL - 20;
p.position.y = Math.random() * height * 1.5;
p.position.x = (Math.random() - 0.5) * (width * 2);
}
} else {
if (p.position.x > halfW + 20) {
p.position.x = -halfW - 20;
p.position.y = Math.random() * height * 1.5;
p.position.z = (Math.random() - 0.5) * (length * 2);
}
}
dummy.position.copy(p.position);
// Orient the particle along the wind direction
dummy.lookAt(p.position.clone().add(dir));
dummy.rotateX(Math.PI / 2); // Cylinder is aligned along Y by default, rotate to face direction
dummy.updateMatrix();
meshRef.current!.setMatrixAt(i, dummy.matrix);
});
meshRef.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
{/* A simple arrow-like or dashed line geometry */}
<cylinderGeometry args={[0.02, 0.02, 1.5, 4]} />
<meshBasicMaterial
color="#a5f3fc"
transparent
opacity={0.6}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</instancedMesh>
);
}