04 - 3D Visualization with Three.js
Create interactive 3D tank model (45 min)
Install Three.js Dependencies
npm install three @react-three/fiber @react-three/drei
3D Tank Model Component
Create src/app/(core)/cad-cam-demo/components/Tank3DModel.tsx:
"use client";
import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls, PerspectiveCamera, Environment } from "@react-three/drei";
import * as THREE from "three";
import { DesignParams } from "../utils/types";
interface Props {
params: DesignParams | null;
autoRotate?: boolean;
}
// Tank mesh component
function TankMesh({ params, autoRotate }: { params: DesignParams; autoRotate: boolean }) {
const groupRef = useRef<THREE.Group>(null);
const waterRef = useRef<THREE.Mesh>(null);
// Auto rotation
useFrame((state) => {
if (autoRotate && groupRef.current) {
groupRef.current.rotation.y += 0.005;
}
// Water animation
if (waterRef.current) {
waterRef.current.position.y = Math.sin(state.clock.elapsedTime) * 0.02;
}
});
const radius = params.tankDiameter / 2;
const height = params.tankHeight;
const wallThickness = params.wallThickness / 1000;
const innerRadius = radius - wallThickness;
return (
<group ref={groupRef}>
{/* Outer tank wall */}
<mesh position={[0, height / 2, 0]}>
<cylinderGeometry args={[radius, radius, height, 32, 1, true]} />
<meshStandardMaterial color="#3b82f6" transparent opacity={0.3} side={THREE.DoubleSide} />
</mesh>
{/* Inner tank wall */}
<mesh position={[0, height / 2, 0]}>
<cylinderGeometry args={[innerRadius, innerRadius, height - 0.1, 32, 1, true]} />
<meshStandardMaterial color="#60a5fa" transparent opacity={0.2} side={THREE.DoubleSide} />
</mesh>
{/* Bottom */}
<mesh position={[0, wallThickness / 2, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<circleGeometry args={[radius, 32]} />
<meshStandardMaterial color="#1e40af" />
</mesh>
{/* Water (animated) */}
<mesh ref={waterRef} position={[0, height * 0.4, 0]}>
<cylinderGeometry args={[innerRadius * 0.98, innerRadius * 0.98, height * 0.7, 32]} />
<meshStandardMaterial color="#06b6d4" transparent opacity={0.4} />
</mesh>
{/* Baffles */}
{Array.from({ length: params.baffleCount }).map((_, i) => {
const angle = (i * Math.PI * 2) / params.baffleCount;
const baffleX = Math.cos(angle) * (innerRadius * 0.5);
const baffleZ = Math.sin(angle) * (innerRadius * 0.5);
return (
<mesh key={`baffle-${i}`} position={[baffleX, height * 0.5, baffleZ]} rotation={[0, -angle, 0]}>
<boxGeometry args={[0.02, height * 0.7, innerRadius * 0.5]} />
<meshStandardMaterial color="#f59e0b" />
</mesh>
);
})}
{/* Aeration slots (bubbles representation) */}
{Array.from({ length: params.aerationSlots }).map((_, i) => {
const angle = (i * Math.PI * 2) / params.aerationSlots;
const slotX = Math.cos(angle) * (innerRadius * 0.6);
const slotZ = Math.sin(angle) * (innerRadius * 0.6);
return (
<mesh key={`aeration-${i}`} position={[slotX, 0.1, slotZ]}>
<sphereGeometry args={[0.05, 16, 16]} />
<meshStandardMaterial color="#22c55e" emissive="#22c55e" emissiveIntensity={0.5} />
</mesh>
);
})}
{/* Inlet pipe */}
<group position={[0, height - 0.3, radius + 0.2]}>
<mesh rotation={[Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[params.inletDiameter / 2000, params.inletDiameter / 2000, 0.5, 16]} />
<meshStandardMaterial color="#ef4444" />
</mesh>
</group>
{/* Outlet pipe */}
<group position={[0, 0.3, -(radius + 0.2)]}>
<mesh rotation={[Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[params.outletDiameter / 2000, params.outletDiameter / 2000, 0.5, 16]} />
<meshStandardMaterial color="#10b981" />
</mesh>
</group>
{/* Ground plane */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.01, 0]}>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#334155" />
</mesh>
</group>
);
}
// Main 3D viewer component
export function Tank3DModel({ params, autoRotate = true }: Props) {
if (!params) {
return (
<div className="bg-white rounded-xl shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">3D Model</h3>
<div className="aspect-video bg-slate-900 rounded-lg flex items-center justify-center">
<span className="text-gray-400">Waiting for design parameters...</span>
</div>
</div>
);
}
return (
<div className="bg-white rounded-xl shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">3D Model Preview</h3>
<div className="aspect-video bg-slate-900 rounded-lg overflow-hidden">
<Canvas>
<PerspectiveCamera makeDefault position={[5, 4, 5]} />
<OrbitControls enablePan enableZoom enableRotate />
{/* Lighting */}
<ambientLight intensity={0.4} />
<directionalLight position={[10, 10, 5]} intensity={1} />
<pointLight position={[-10, -10, -5]} intensity={0.5} />
{/* Tank */}
<TankMesh params={params} autoRotate={autoRotate} />
</Canvas>
</div>
{/* Controls info */}
<div className="mt-3 text-xs text-gray-500 flex gap-4">
<span>🖱️ Drag to rotate</span>
<span>🔍 Scroll to zoom</span>
<span>⌨️ Shift+drag to pan</span>
</div>
</div>
);
}
Simplified Version (No Dependencies)
If you want a simpler version without Three.js, use CSS 3D transforms:
"use client";
import { DesignParams } from "../utils/types";
interface Props {
params: DesignParams | null;
}
export function Tank3DSimple({ params }: Props) {
if (!params) return null;
return (
<div className="bg-white rounded-xl shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">3D Preview (CSS)</h3>
<div className="aspect-video bg-slate-900 rounded-lg flex items-center justify-center" style={{ perspective: "1000px" }}>
<div
className="relative"
style={{
transformStyle: "preserve-3d",
transform: "rotateX(-20deg) rotateY(-30deg)",
animation: "spin 10s linear infinite",
}}
>
{/* Tank cylinder (simplified) */}
<div
className="rounded-full border-4 border-blue-500 bg-blue-500/20"
style={{
width: `${params.tankDiameter * 60}px`,
height: `${params.tankDiameter * 60}px`,
}}
/>
<div className="absolute inset-0 flex items-center justify-center text-white text-sm">{params.tankVolume}m³</div>
</div>
</div>
<style jsx>{`
@keyframes spin {
from {
transform: rotateX(-20deg) rotateY(0deg);
}
to {
transform: rotateX(-20deg) rotateY(360deg);
}
}
`}</style>
</div>
);
}
Next: 05-GCODE-SIMULATOR.md
Simulate CNC toolpath visualization →