So for those of you needing a great tool to help with your aquarium math here is a great tool I created to simplify the process for you and give you a good visual.
3D Aquarium Load Simulator
.aq3d-wrapper {
font-family: ‘Segoe UI’, system-ui, sans-serif;
max-width: 900px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
.aq3d-sidebar {
flex: 1;
min-width: 300px;
padding: 25px;
background: #f8fcfd;
border-right: 1px solid #e0e0e0;
box-sizing: border-box;
}
.aq3d-canvas-container {
flex: 2;
min-width: 300px;
min-height: 400px;
position: relative;
background: #eef5f9;
}
.aq3d-canvas-container canvas {
display: block;
width: 100% !important;
height: 100% !important;
outline: none;
}
.aq3d-overlay-text {
position: absolute;
top: 15px;
right: 15px;
background: rgba(255, 255, 255, 0.8);
padding: 5px 10px;
border-radius: 4px;
font-size: 0.8em;
color: #555;
pointer-events: none;
}
.aq3d-sidebar h3 {
margin-top: 0;
color: #0056b3;
font-size: 1.4em;
}
.aq3d-input-group {
margin-bottom: 12px;
}
.aq3d-input-group label {
display: flex;
justify-content: space-between;
font-weight: 600;
font-size: 0.85em;
color: #495057;
margin-bottom: 4px;
}
.aq3d-input-group input[type=”range”] {
width: 100%;
cursor: pointer;
accent-color: #0056b3;
}
.aq3d-input-group select {
width: 100%;
padding: 6px;
border: 1px solid #ced4da;
border-radius: 4px;
}
.aq3d-results {
margin-top: 20px;
background: #ffffff;
padding: 15px;
border-radius: 8px;
border: 1px solid #d0e8f2;
}
.aq3d-result-row {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
font-size: 0.9em;
color: #333;
}
.aq3d-total {
font-weight: bold;
font-size: 1.1em;
color: #004085;
border-top: 2px solid #adb5bd;
padding-top: 8px;
margin-top: 8px;
}
https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js
Left Click: Rotate | Scroll: Zoom
// 1. Setup Three.js Scene
const container = document.getElementById(‘canvas-container’);
const scene = new THREE.Scene();
scene.background = new THREE.Color(‘#eef5f9’);
const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(80, 80, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 20, 0);
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.position.set(50, 100, 50);
scene.add(dirLight);
// 2. Create Base 3D Objects (1x1x1 cubes that we will scale)
const baseGeometry = new THREE.BoxGeometry(1, 1, 1);
// Stand
const standMat = new THREE.MeshLambertMaterial({ color: 0x3e2723 }); // Dark wood
const standMesh = new THREE.Mesh(baseGeometry, standMat);
scene.add(standMesh);
// Substrate
const subMat = new THREE.MeshLambertMaterial({ color: 0x8b5a2b }); // Gravel brown
const subMesh = new THREE.Mesh(baseGeometry, subMat);
scene.add(subMesh);
// Water
const waterMat = new THREE.MeshPhongMaterial({
color: 0x1ca3ec, transparent: true, opacity: 0.6, shininess: 100
});
const waterMesh = new THREE.Mesh(baseGeometry, waterMat);
scene.add(waterMesh);
// Glass Tank (Outer Shell)
const glassMat = new THREE.MeshPhysicalMaterial({
color: 0xffffff, transmission: 0.9, opacity: 0.2, transparent: true, roughness: 0.1
});
const tankMesh = new THREE.Mesh(baseGeometry, glassMat);
scene.add(tankMesh);
// Tank Edges for visibility
const edges = new THREE.EdgesGeometry(baseGeometry);
const lineMat = new THREE.LineBasicMaterial({ color: 0x8fc3e3, linewidth: 2 });
const tankEdges = new THREE.LineSegments(edges, lineMat);
scene.add(tankEdges);
// 3. Update Function
function updateSystem() {
// Get values
const L = parseFloat(document.getElementById(‘inLen’).value);
const W = parseFloat(document.getElementById(‘inWid’).value);
const H = parseFloat(document.getElementById(‘inHgt’).value);
let SubH = parseFloat(document.getElementById(‘inSub’).value);
const StandH = parseFloat(document.getElementById(‘inStandH’).value);
const StandW = parseFloat(document.getElementById(‘inStandW’).value);
const Mat = document.getElementById(‘inMat’).value;
if(SubH > H) { SubH = H; document.getElementById(‘inSub’).value = SubH; }
// Update Text Labels
document.getElementById(‘vLen’).innerText = L + ‘ in’;
document.getElementById(‘vWid’).innerText = W + ‘ in’;
document.getElementById(‘vHgt’).innerText = H + ‘ in’;
document.getElementById(‘vSub’).innerText = SubH + ‘ in’;
document.getElementById(‘vStandH’).innerText = StandH + ‘ in’;
document.getElementById(‘vStandW’).innerText = StandW + ‘ lbs’;
// Math Calculations
const volTotalInches = L * W * H;
const galsTotal = volTotalInches / 231;
// Water occupies volume minus substrate, minus 1 inch at the top
const waterHeight = Math.max(0, H – SubH – 1);
const waterGals = (L * W * waterHeight) / 231;
const waterWt = waterGals * 8.34;
const subWt = (L * W * SubH) * 0.058;
const tankWt = Mat === ‘glass’ ? galsTotal * 1.6 : galsTotal * 0.8;
const buffer = (waterWt + subWt + tankWt) * 0.10;
const totalLoad = waterWt + subWt + tankWt + StandW + buffer;
// Update Results Panel
document.getElementById(‘rVol’).innerText = Math.round(galsTotal) + ‘ Gal’;
document.getElementById(‘rWat’).innerText = Math.round(waterWt) + ‘ lbs’;
document.getElementById(‘rSub’).innerText = Math.round(subWt) + ‘ lbs’;
document.getElementById(‘rTank’).innerText = Math.round(tankWt) + ‘ lbs’;
document.getElementById(‘rBuf’).innerText = Math.round(buffer) + ‘ lbs’;
document.getElementById(‘rTot’).innerText = Math.round(totalLoad) + ‘ lbs’;
// Update 3D Model Scales and Positions
// Stand
standMesh.scale.set(L, StandH, W);
standMesh.position.set(0, StandH / 2, 0);
// Tank & Edges
tankMesh.scale.set(L, H, W);
tankMesh.position.set(0, StandH + (H / 2), 0);
tankEdges.scale.set(L, H, W);
tankEdges.position.set(0, StandH + (H / 2), 0);
// Substrate
subMesh.scale.set(L – 0.2, SubH, W – 0.2); // Slightly smaller than glass to fit inside
subMesh.position.set(0, StandH + (SubH / 2), 0);
// Water
if(waterHeight > 0) {
waterMesh.visible = true;
waterMesh.scale.set(L – 0.4, waterHeight, W – 0.4);
waterMesh.position.set(0, StandH + SubH + (waterHeight / 2), 0);
} else {
waterMesh.visible = false;
}
// Keep the camera looking at the center of the tank
controls.target.set(0, StandH + (H/2), 0);
}
// Event Listeners for inputs
const inputs = document.querySelectorAll(‘input, select’);
inputs.forEach(input => input.addEventListener(‘input’, updateSystem));
// Handle Window Resize
window.addEventListener(‘resize’, () => {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
// Animation Loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Initialize
updateSystem();
animate();