03 - Water Treatment Plant 3D Model
3D model structure, components, and visualization specifications
Overview
The Water Treatment Plant (WTP) 3D model is an interactive visualization that represents a typical municipal wastewater treatment facility. Users can rotate, zoom, and click on components to extract water quality metadata.
Plant Layout
WATER TREATMENT PLANT - TOP VIEW
┌─────────────────────────────────────────────────────────────────────────────────┐
│ │
│ RAW WATER TREATED WATER│
│ ═══════▶ ═══════▶ │
│ │
│ ┌─────┐ ┌─────┐ ┌───────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ IN │───▶│ SCR │───▶│ GRIT │───▶│ PRIMARY │───▶│ AERATION │ │
│ │ │ │ │ │ CHAMBER │ │ CLARIFIER │ │ TANK │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └─────┘ └─────┘ └───────────┘ └───────────────┘ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────┐ ┌─────┐ ┌───────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ OUT │◀───│ DIS │◀───│ FILTER │◀───│ SECONDARY │◀───│ SECONDARY │ │
│ │ │ │ │ │ UNIT │ │ CLARIFIER │ │ CLARIFIER │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └─────┘ └─────┘ └───────────┘ └───────────────┘ └─────────────┘ │
│ │
│ SLUDGE HANDLING │
│ ┌─────────┐ ┌─────────┐ │
│ │THICKENER│───▶│DIGESTER │ │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
3D Model Specifications
Scene Setup
interface WTPSceneConfig {
// Camera
camera: {
position: [30, 20, 30];
fov: 50;
near: 0.1;
far: 1000;
};
// Lighting
lights: {
ambient: { intensity: 0.5 };
directional: {
position: [10, 20, 10];
intensity: 1;
};
point: {
position: [0, 10, 0];
intensity: 0.5;
};
};
// Ground plane
ground: {
size: [100, 100];
color: "#228B22";
position: [0, -0.5, 0];
};
// Controls
controls: {
enableRotate: true;
enableZoom: true;
enablePan: true;
minDistance: 10;
maxDistance: 100;
};
}
Component Geometries
// Component 3D definitions
const componentGeometries = {
inlet: {
type: "cylinder",
args: [0.5, 0.5, 2, 32],
rotation: [0, 0, Math.PI / 2],
material: "pipe",
},
screening: {
type: "box",
args: [2, 1.5, 1.5],
material: "metal",
},
grit_chamber: {
type: "box",
args: [3, 1.5, 2],
material: "concrete",
},
primary_clarifier: {
type: "cylinder",
args: [3, 3, 2, 32],
material: "concrete",
},
aeration_tank: {
type: "box",
args: [5, 2, 4],
material: "concrete",
},
secondary_clarifier: {
type: "cylinder",
args: [3, 3, 2, 32],
material: "concrete",
},
filtration: {
type: "box",
args: [2.5, 1.5, 2.5],
material: "sand",
},
disinfection: {
type: "box",
args: [2, 1, 1.5],
material: "uv",
},
outlet: {
type: "cylinder",
args: [0.5, 0.5, 2, 32],
rotation: [0, 0, Math.PI / 2],
material: "pipe",
},
};
Materials
const materials = {
pipe: {
color: "#8B4513",
metalness: 0.8,
roughness: 0.3,
},
metal: {
color: "#696969",
metalness: 0.9,
roughness: 0.2,
},
concrete: {
color: "#808080",
metalness: 0.1,
roughness: 0.9,
},
water: {
color: "#4682B4",
metalness: 0.2,
roughness: 0.1,
transparent: true,
opacity: 0.7,
},
sand: {
color: "#DEB887",
metalness: 0.0,
roughness: 1.0,
},
uv: {
color: "#FFD700",
metalness: 0.5,
roughness: 0.3,
emissive: "#FFD700",
emissiveIntensity: 0.3,
},
highlight: {
color: "#00FF00",
metalness: 0.5,
roughness: 0.5,
emissive: "#00FF00",
emissiveIntensity: 0.5,
},
};
Component Positions
const componentPositions: Record<string, [number, number, number]> = {
inlet: [-15, 0, 0],
screening: [-11, 0, 0],
grit_chamber: [-6, 0, 0],
primary_clarifier: [0, 0, 0],
aeration_tank: [7, 0, 0],
secondary_clarifier: [14, 0, 0],
filtration: [14, 0, -6],
disinfection: [7, 0, -6],
outlet: [0, 0, -6],
// Sludge handling (below main line)
sludge_thickener: [0, 0, 8],
sludge_digester: [7, 0, 8],
};
Connecting Pipes
interface PipeConnection {
from: string;
to: string;
type: "main" | "sludge" | "return";
color: string;
}
const pipeConnections: PipeConnection[] = [
{ from: "inlet", to: "screening", type: "main", color: "#8B4513" },
{ from: "screening", to: "grit_chamber", type: "main", color: "#8B4513" },
{ from: "grit_chamber", to: "primary_clarifier", type: "main", color: "#4682B4" },
{ from: "primary_clarifier", to: "aeration_tank", type: "main", color: "#4682B4" },
{ from: "aeration_tank", to: "secondary_clarifier", type: "main", color: "#20B2AA" },
{ from: "secondary_clarifier", to: "filtration", type: "main", color: "#5F9EA0" },
{ from: "filtration", to: "disinfection", type: "main", color: "#DEB887" },
{ from: "disinfection", to: "outlet", type: "main", color: "#00CED1" },
// Sludge lines
{ from: "primary_clarifier", to: "sludge_thickener", type: "sludge", color: "#654321" },
{ from: "secondary_clarifier", to: "sludge_thickener", type: "sludge", color: "#654321" },
{ from: "sludge_thickener", to: "sludge_digester", type: "sludge", color: "#654321" },
// Return activated sludge
{ from: "secondary_clarifier", to: "aeration_tank", type: "return", color: "#8B4513" },
];
React Three Fiber Implementation
Main Component
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Environment, Html } from "@react-three/drei";
interface WTPModelProps {
onComponentSelect: (componentId: string) => void;
selectedComponent: string | null;
hoveredComponent: string | null;
onComponentHover: (componentId: string | null) => void;
}
export function WTPModel3D({ onComponentSelect, selectedComponent, hoveredComponent, onComponentHover }: WTPModelProps) {
return (
<Canvas camera={{ position: [30, 20, 30], fov: 50 }} style={{ background: "#1a1a2e" }}>
{/* Lighting */}
<ambientLight intensity={0.5} />
<directionalLight position={[10, 20, 10]} intensity={1} />
<pointLight position={[0, 10, 0]} intensity={0.5} />
{/* Environment */}
<Environment preset="city" />
{/* Ground */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
<planeGeometry args={[100, 100]} />
<meshStandardMaterial color="#228B22" />
</mesh>
{/* WTP Components */}
{Object.entries(componentPositions).map(([id, position]) => (
<WTPComponent
key={id}
id={id}
position={position}
isSelected={selectedComponent === id}
isHovered={hoveredComponent === id}
onClick={() => onComponentSelect(id)}
onPointerOver={() => onComponentHover(id)}
onPointerOut={() => onComponentHover(null)}
/>
))}
{/* Pipes */}
{pipeConnections.map((pipe, index) => (
<Pipe key={index} {...pipe} />
))}
{/* Controls */}
<OrbitControls enableRotate enableZoom enablePan minDistance={10} maxDistance={100} />
</Canvas>
);
}
Individual Component
interface WTPComponentProps {
id: string;
position: [number, number, number];
isSelected: boolean;
isHovered: boolean;
onClick: () => void;
onPointerOver: () => void;
onPointerOut: () => void;
}
function WTPComponent({ id, position, isSelected, isHovered, onClick, onPointerOver, onPointerOut }: WTPComponentProps) {
const geometry = componentGeometries[id];
const metadata = componentMetadataStore[id];
const color = isSelected ? "#00FF00" : isHovered ? metadata.highlightColor : metadata.color;
return (
<group position={position}>
<mesh onClick={onClick} onPointerOver={onPointerOver} onPointerOut={onPointerOut}>
{geometry.type === "box" && <boxGeometry args={geometry.args} />}
{geometry.type === "cylinder" && <cylinderGeometry args={geometry.args} />}
<meshStandardMaterial
color={color}
metalness={0.3}
roughness={0.7}
emissive={isSelected ? "#00FF00" : isHovered ? color : "#000000"}
emissiveIntensity={isSelected ? 0.3 : isHovered ? 0.1 : 0}
/>
</mesh>
{/* Label */}
{(isSelected || isHovered) && (
<Html position={[0, 2, 0]} center>
<div className="bg-black/80 text-white px-2 py-1 rounded text-sm">{metadata.name}</div>
</Html>
)}
</group>
);
}
Water Flow Animation
// Animate water particles flowing through the plant
interface WaterParticle {
id: number;
position: [number, number, number];
stage: number;
progress: number;
}
function useWaterFlowAnimation() {
const [particles, setParticles] = useState<WaterParticle[]>([]);
useFrame((state, delta) => {
setParticles(
(prev) =>
prev
.map((particle) => {
const newProgress = particle.progress + delta * 0.1;
if (newProgress >= 1) {
// Move to next stage
return {
...particle,
stage: particle.stage + 1,
progress: 0,
position: getStagePosition(particle.stage + 1),
};
}
return {
...particle,
progress: newProgress,
position: interpolatePosition(particle.stage, particle.stage + 1, newProgress),
};
})
.filter((p) => p.stage < 9) // Remove at outlet
);
});
return particles;
}
Component Details Panel
When a component is selected, show detailed information:
interface ComponentDetailsPanelProps {
componentId: string;
metadata: ComponentMetadata;
onFetch: () => void;
}
function ComponentDetailsPanel({ componentId, metadata, onFetch }: ComponentDetailsPanelProps) {
return (
<Card className="w-80">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ backgroundColor: metadata.color }} />
{metadata.name}
</CardTitle>
<CardDescription>{metadata.description}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-medium mb-2">Process</h4>
<p className="text-sm text-muted-foreground">{metadata.processDescription}</p>
</div>
<div>
<h4 className="font-medium mb-2">Key Parameters</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
<div>BOD: {metadata.waterQuality.BOD} mg/L</div>
<div>COD: {metadata.waterQuality.COD} mg/L</div>
<div>TSS: {metadata.waterQuality.TSS} mg/L</div>
<div>pH: {metadata.waterQuality.pH}</div>
</div>
</div>
<Button onClick={onFetch} className="w-full">
<Download className="w-4 h-4 mr-2" />
Fetch Parameters
</Button>
</CardContent>
</Card>
);
}
Performance Optimizations
- Level of Detail (LOD): Reduce geometry complexity at distance
- Instancing: Use instanced meshes for repeated elements
- Frustum Culling: Don't render off-screen components
- Lazy Loading: Load detailed textures on demand
import { useGLTF, Detailed } from "@react-three/drei";
function OptimizedComponent({ id, position }) {
return (
<Detailed distances={[0, 20, 50]}>
{/* High detail - close up */}
<DetailedMesh geometry="high" />
{/* Medium detail */}
<SimplifiedMesh geometry="medium" />
{/* Low detail - far away */}
<BoxMesh />
</Detailed>
);
}
Next: 04-UI-UX-FLOW.md
Learn about the user interaction flow and wireframes →