Files
BrainWind/app/src/components/three/ResizableBox3D.tsx
T

122 lines
4.1 KiB
TypeScript

import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import { Edges } from '@react-three/drei';
import * as THREE from 'three';
interface ResizableBox3DProps {
l1: number;
l2: number;
h: number;
viewMode?: 'solid' | 'airflow';
}
function WindParticles({ l1, l2, h }: { l1: number, l2: number, h: number }) {
const count = 40;
const meshRef = useRef<THREE.InstancedMesh>(null);
// Create dummy object to compute matrix for each instance
const dummy = useMemo(() => new THREE.Object3D(), []);
// Generate random starting positions
const particles = useMemo(() => {
const temp = [];
for (let i = 0; i < count; i++) {
temp.push({
x: (Math.random() - 0.5) * (l1 * 3), // Spread wider than the building
y: Math.random() * (h * 1.5), // Spread higher than the building
z: (Math.random() * 40) + l2 / 2 + 5, // Start in front of the building
speed: 10 + Math.random() * 15, // Random speeds
offset: Math.random() * 100 // Phase offset
});
}
return temp;
}, [l1, l2, h, count]);
useFrame((_state, delta) => {
if (!meshRef.current) return;
particles.forEach((particle, i) => {
// Move particle along Z axis (wind direction)
particle.z -= particle.speed * delta;
// If particle passed the building, reset it to the front
if (particle.z < -l2 - 10) {
particle.z = (Math.random() * 20) + l2 / 2 + 10;
particle.x = (Math.random() - 0.5) * (l1 * 3);
particle.y = Math.random() * (h * 1.5);
}
// Simple avoidance / turbulence effect around the building
let currentX = particle.x;
let currentY = particle.y;
// If particle is close to the front face, push it out
if (particle.z > -l2/2 && particle.z < l2/2 + 2) {
const isHittingFront = Math.abs(particle.x) < l1/2 + 0.5 && particle.y < h + 0.5;
if (isHittingFront) {
// Push to the sides or up
if (Math.abs(particle.x) > (l1/2) * 0.7) {
currentX += Math.sign(particle.x) * (20 * delta);
} else {
currentY += 20 * delta;
}
}
}
dummy.position.set(currentX, currentY, particle.z);
dummy.scale.set(0.05, 0.05, Math.max(0.5, particle.speed * 0.1)); // Stretch by speed
dummy.updateMatrix();
meshRef.current!.setMatrixAt(i, dummy.matrix);
});
meshRef.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
<cylinderGeometry args={[1, 1, 1, 8]} />
<meshBasicMaterial color="#60a5fa" transparent opacity={0.4} />
</instancedMesh>
);
}
export function ResizableBox3D({ l1, l2, h, viewMode = 'solid' }: ResizableBox3DProps) {
const meshRef = useRef<THREE.Mesh>(null);
// Create target scale and position based on inputs
const targetScale = useMemo(() => new THREE.Vector3(l1, h, l2), [l1, l2, h]);
useFrame((_state, delta) => {
if (meshRef.current) {
// Smoothly animate the scale
meshRef.current.scale.lerp(targetScale, delta * 5);
// Smoothly animate the position to keep the base on the ground
meshRef.current.position.lerp(
new THREE.Vector3(0, meshRef.current.scale.y / 2, 0),
delta * 5
);
}
});
return (
<group>
{/* Ground plane for reference */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]} receiveShadow>
<planeGeometry args={[100, 100]} />
<meshStandardMaterial color="#333" depthWrite={false} transparent opacity={0.2} />
</mesh>
<gridHelper args={[100, 40, '#666', '#444']} position={[0, 0.01, 0]} />
{/* The Animated Box */}
<mesh ref={meshRef} castShadow receiveShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#4f46e5" transparent opacity={viewMode === 'airflow' ? 0.3 : 0.7} roughness={0.2} metalness={0.8} />
<Edges scale={1} threshold={15} color="#818cf8" />
</mesh>
{/* Wind Flow Animation */}
{viewMode === 'airflow' && <WindParticles l1={l1} l2={l2} h={h} />}
</group>
);
}