You may need to reload this page for the Simulator to fully load.
<div id="planetary-instability-model" class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code&display=swap" rel="stylesheet" />
<style>
body { font-family: 'Inter', sans-serif; }
.fira-code { font-family: 'Fira Code', monospace; }
.slider-label { display: flex; justify-content: space-between; font-size: 0.875rem; }
#data-readout span:nth-child(odd) { color: #9ca3af; }
</style>
<div class="flex flex-col items-center">
<div id="simulation-container" class="w-[1000px] h-[1000px] bg-black rounded-t-lg shadow-2xl overflow-hidden relative border-2 border-gray-700"></div>
<div id="ui-container" class="w-[1000px] h-[300px] bg-gray-800/50 backdrop-blur-sm rounded-b-lg shadow-2xl p-6 grid grid-cols-3 gap-6 border-x-2 border-b-2 border-gray-700">
<div class="space-y-3">
<h2 class="text-lg font-bold text-white border-b border-gray-600 pb-1">Simulation Controls</h2>
<div>
<div class="slider-label"><span>Time Scale</span><span id="timeScale-val" class="fira-code"></span></div>
<input id="timeScale" type="range" min="1" max="500000" value="250000" class="w-full" />
</div>
<div>
<div class="slider-label"><span>Melt Rate (Gt/yr)</span><span id="meltRate-val" class="fira-code"></span></div>
<input id="meltRate" type="range" min="0" max="500" value="400" class="w-full" />
</div>
<div>
<label for="viscosity" class="block text-sm">Viscosity (Outer Core)</label>
<input id="viscosity" type="number" value="4.2146445" step="0.000001" class="bg-gray-700 rounded p-1 w-full fira-code" />
</div>
<div>
<div class="slider-label"><span>Torque Scale</span><span id="torqueMagnitudeScale-val" class="fira-code"></span></div>
<input id="torqueMagnitudeScale" type="range" min="0" max="0.01" value="0.001" step="0.00001" class="w-full" />
</div>
<div class="flex items-center gap-4 pt-2">
<button id="start-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">Start</button>
<button id="reset-button" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded w-full">Reset</button>
</div>
</div>
<div class="space-y-1 bg-gray-900/50 p-3 rounded-lg border border-gray-700 fira-code text-xs">
<h2 class="text-lg font-bold text-white border-b border-gray-600 pb-1 mb-2 font-sans">Live Data</h2>
<div id="data-readout" class="grid grid-cols-2 gap-x-4">
<span>Liquid Ratio:</span><span id="liquidMassRatio-data"></span>
<span>Eff. Core Mass:</span><span id="effectiveCoreMass-data"></span>
<span>Surface Water:</span><span id="surfaceWaterMass-data"></span>
<span>Remaining Ice:</span><span id="remainingIceMass-data"></span>
<span>Angular Vel:</span><span id="angularVelocity-data"></span>
<span>Applied Torque:</span><span id="torqueMagnitude-data"></span>
<span>LOD Shift (Up):</span><span id="objectUpDeviation-data"></span>
<span>COM Shift (Rot Vec):</span><span id="rotationVectorDeviation-data"></span>
<span>Simulated Days:</span><span id="simulatedTime-data"></span>
</div>
</div>
<div class="space-y-3">
<h2 class="text-lg font-bold text-white border-b border-gray-600 pb-1">Initial Conditions (Gt)</h2>
<div>
<label class="block text-sm">Total Mass</label>
<input id="totalEarthMass" type="number" value="5972000000" class="bg-gray-700 rounded p-1 w-full" />
</div>
<div>
<label class="block text-sm">Outer Core Mass</label>
<input id="outerCoreMass" type="number" value="1900000000" class="bg-gray-700 rounded p-1 w-full" />
</div>
<div>
<label class="block text-sm">Total Ice Mass</label>
<input id="totalIceMass" type="number" value="24000000" class="bg-gray-700 rounded p-1 w-full" />
</div>
</div>
</div>
</div>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
(function() {
let scene, camera, renderer, controls, world, clock;
let sphereMesh, trenchMesh, sphereBody, trenchBody, topIce, bottomIce;
let remainingIceMass, surfaceWaterMass, effectiveCoreMass, effectiveLiquidMass, liquidMassRatio, simulationTimeDays = 0;
let animationFrameId;
let timeScale, meltRate, viscosity, torqueMagnitudeScale;
let totalEarthMass, outerCoreMass, totalIceMass;
const initialSurfaceWaterMass = 1385504; // 0.0232% of total mass
const gravitationalAcceleration = 9.81, centerOffset = new THREE.Vector3(0.1, 0, 0);
// UI elements from Trench_Simulator5.html
const simContainer = document.getElementById('simulation-container');
const sliders = { timeScale: document.getElementById('timeScale'), meltRate: document.getElementById('meltRate'), viscosity: document.getElementById('viscosity'), torqueMagnitudeScale: document.getElementById('torqueMagnitudeScale') };
const sliderVals = { timeScale: document.getElementById('timeScale-val'), meltRate: document.getElementById('meltRate-val'), torqueMagnitudeScale: document.getElementById('torqueMagnitudeScale-val') };
const dataReadouts = { liquidMassRatio: document.getElementById('liquidMassRatio-data'), effectiveCoreMass: document.getElementById('effectiveCoreMass-data'), surfaceWaterMass: document.getElementById('surfaceWaterMass-data'), remainingIceMass: document.getElementById('remainingIceMass-data'), angularVelocity: document.getElementById('angularVelocity-data'), torqueMagnitude: document.getElementById('torqueMagnitude-data'), objectUpDeviation: document.getElementById('objectUpDeviation-data'), rotationVectorDeviation: document.getElementById('rotationVectorDeviation-data'), simulatedTime: document.getElementById('simulatedTime-data') };
const initialInputs = { totalEarthMass: document.getElementById('totalEarthMass'), outerCoreMass: document.getElementById('outerCoreMass'), totalIceMass: document.getElementById('totalIceMass') };
const startButton = document.getElementById('start-button');
const resetButton = document.getElementById('reset-button');
let isSimulationRunning = false;
// Constants for a precise, deterministic motion as requested
const SECONDS_PER_DAY = 24 * 3600;
const SECONDS_PER_YEAR = 365.25 * SECONDS_PER_DAY;
const ORBIT_RADIUS = 10000;
const AXIAL_ANGULAR_VELOCITY = (2 * Math.PI) / SECONDS_PER_DAY;
const ORBITAL_ANGULAR_VELOCITY = (2 * Math.PI) / SECONDS_PER_YEAR;
init();
setupUI();
resetSimulation();
function init() {
// Three.js Scene and Camera setup from ChatGPTPIM.html
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(75, 1000 / 1000, 0.1, 50000);
// Adjusted camera position to better frame the sphere inside the toroid
camera.position.set(0, 0, 15000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(1000, 1000);
simContainer.appendChild(renderer.domElement);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(100, 300, 200);
scene.add(new THREE.AmbientLight(0x404040));
scene.add(light);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// Physics world is now used for all motion
world = new CANNON.World();
world.gravity.set(0, -gravitationalAcceleration, 0);
// Sphere from ChatGPTP.html
const sphereGeo = new THREE.SphereGeometry(10, 32, 32);
const sphereMat = new THREE.MeshPhongMaterial({ color: 0x446688 });
sphereMesh = new THREE.Mesh(sphereGeo, sphereMat);
scene.add(sphereMesh);
const sphereShape = new CANNON.Sphere(10);
sphereBody = new CANNON.Body({ mass: 1, shape: sphereShape });
world.addBody(sphereBody);
// Trench from ChatGPTP.html
const trenchGeo = new THREE.TorusGeometry(10000, 40, 32, 64);
const trenchMat = new THREE.MeshPhongMaterial({ color: 0x333344, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });
trenchMesh = new THREE.Mesh(trenchGeo, trenchMat);
trenchMesh.rotation.x = Math.PI / 2;
scene.add(trenchMesh);
const vertices = trenchGeo.attributes.position.array;
const indices = trenchGeo.index.array;
const cannonTrimesh = new CANNON.Trimesh(vertices, indices);
trenchBody = new CANNON.Body({ mass: 0 });
trenchBody.addShape(cannonTrimesh);
trenchBody.quaternion.setFromEuler(Math.PI / 2, 0, 0);
world.addBody(trenchBody);
// Visual ice caps from c_script_Fuid_Inertia.txt
const iceGeo = new THREE.SphereGeometry(10, 16, 16); // Increased radius for visibility
const iceMat = new THREE.MeshPhongMaterial({ color: 0xffffff });
topIce = new THREE.Mesh(iceGeo, iceMat);
bottomIce = new THREE.Mesh(iceGeo, iceMat);
// Parent the ice caps to the sphere mesh
sphereMesh.add(topIce);
sphereMesh.add(bottomIce);
}
function render() {
controls.update();
renderer.render(scene, camera);
}
function setupUI() {
sliders.timeScale.addEventListener('input', (e) => { timeScale = parseFloat(e.target.value); sliderVals.timeScale.textContent = e.target.value; });
sliders.meltRate.addEventListener('input', (e) => { meltRate = parseFloat(e.target.value); sliderVals.meltRate.textContent = e.target.value; });
sliders.viscosity.addEventListener('input', (e) => { viscosity = parseFloat(e.target.value); });
sliders.torqueMagnitudeScale.addEventListener('input', (e) => { torqueMagnitudeScale = parseFloat(e.target.value); sliderVals.torqueMagnitudeScale.textContent = torqueMagnitudeScale.toExponential(2); });
sliderVals.timeScale.textContent = sliders.timeScale.value;
sliderVals.meltRate.textContent = sliders.meltRate.value;
sliderVals.torqueMagnitudeScale.textContent = parseFloat(sliders.torqueMagnitudeScale.value).toExponential(2);
startButton.addEventListener('click', startSimulation);
resetButton.addEventListener('click', resetSimulation);
}
function resetSimulation() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
isSimulationRunning = false;
clock = new THREE.Clock();
// Ensure all initial values are parsed to floats
totalEarthMass = parseFloat(initialInputs.totalEarthMass.value);
outerCoreMass = parseFloat(initialInputs.outerCoreMass.value);
totalIceMass = parseFloat(initialInputs.totalIceMass.value);
viscosity = parseFloat(sliders.viscosity.value);
timeScale = parseFloat(sliders.timeScale.value);
meltRate = parseFloat(sliders.meltRate.value);
torqueMagnitudeScale = parseFloat(sliders.torqueMagnitudeScale.value);
remainingIceMass = totalIceMass;
surfaceWaterMass = initialSurfaceWaterMass;
simulationTimeDays = 0;
// Reset position of physics body
sphereBody.position.set(ORBIT_RADIUS, 5, 0);
sphereBody.velocity.set(0, 0, 0);
sphereBody.angularVelocity.set(0, AXIAL_ANGULAR_VELOCITY, 0);
sphereBody.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), 0);
sphereBody.mass = 1;
updateCalculations();
updateAllReadouts();
updateIceCapVisuals();
render();
}
function startSimulation() {
if (isSimulationRunning) return;
isSimulationRunning = true;
animate();
}
function animate() {
animationFrameId = requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
const scaledDeltaTime = deltaTime * timeScale;
// Step the physics world to apply torque and update angular velocity
world.step(1 / 60, deltaTime, 3);
// Update sphere's orbital movement using the correct units
const orbitalAngle = (simulationTimeDays / 365.25) * 2 * Math.PI;
sphereBody.position.x = ORBIT_RADIUS * Math.cos(orbitalAngle);
sphereBody.position.z = ORBIT_RADIUS * Math.sin(orbitalAngle);
// Update visuals based on physics body
sphereMesh.position.copy(sphereBody.position);
sphereMesh.quaternion.copy(sphereBody.quaternion);
// Increment simulated time based on the time scale slider
simulationTimeDays += scaledDeltaTime / SECONDS_PER_DAY;
// Apply empirical physics (sinusoidal melt and torque)
applyEmpiricalPhysics(scaledDeltaTime);
controls.update();
renderer.render(scene, camera);
updateAllReadouts(scaledDeltaTime);
}
function applyEmpiricalPhysics(effectiveDeltaTime) {
const secondsPerYear = 365.25 * 24 * 3600;
const meltFactor = (Math.sin(simulationTimeDays * 0.01) + 1) / 2;
const meltPerFrame = meltRate * meltFactor * effectiveDeltaTime / secondsPerYear;
if (remainingIceMass > 0) {
const melt = Math.min(meltPerFrame, remainingIceMass);
remainingIceMass -= melt;
surfaceWaterMass += melt;
updateIceCapVisuals();
}
updateCalculations();
const threeQuaternion = new THREE.Quaternion(sphereBody.quaternion.x, sphereBody.quaternion.y, sphereBody.quaternion.z, sphereBody.quaternion.w);
const torqueArmWorld = centerOffset.clone().applyQuaternion(threeQuaternion);
const gravitationalForceVector = new THREE.Vector3(0, -gravitationalAcceleration, 0).multiplyScalar(sphereBody.mass);
const torqueVector = new THREE.Vector3().crossVectors(torqueArmWorld, gravitationalForceVector);
torqueVector.multiplyScalar((liquidMassRatio / 100) * torqueMagnitudeScale);
const cannonTorque = new CANNON.Vec3(torqueVector.x, torqueVector.y, torqueVector.z);
sphereBody.torque.vadd(cannonTorque, sphereBody.torque);
}
function updateCalculations() {
effectiveCoreMass = outerCoreMass / viscosity;
effectiveLiquidMass = effectiveCoreMass + surfaceWaterMass;
liquidMassRatio = (effectiveLiquidMass / totalEarthMass) * 100;
}
function updateIceCapVisuals() {
const ratio = (totalIceMass > 0) ? remainingIceMass / totalIceMass : 0;
const scale = 0.8 * Math.sqrt(ratio); // Adjusted scale for better visibility
const visibleScale = Math.max(0.001, scale);
topIce.scale.set(visibleScale, visibleScale, visibleScale);
bottomIce.scale.set(visibleScale, visibleScale, visibleScale);
topIce.position.y = 5 * (1 + (1-ratio));
bottomIce.position.y = -5 * (1 + (1-ratio));
}
function updateAllReadouts(effectiveDeltaTime = 0) {
const angVel = sphereBody.angularVelocity.length();
const sphereUp = new THREE.Vector3(0, 1, 0).applyQuaternion(sphereMesh.quaternion);
const objectUpDeviationDegrees = sphereUp.angleTo(new THREE.Vector3(0, 1, 0)) * (180 / Math.PI);
let rotationVectorDeviationDegrees = 0;
if (sphereBody.angularVelocity.lengthSquared() > 1e-6) {
const av = sphereBody.angularVelocity.unit();
rotationVectorDeviationDegrees = new THREE.Vector3(av.x, av.y, av.z).angleTo(new THREE.Vector3(0, 1, 0)) * (180 / Math.PI);
}
const threeQuaternion = new THREE.Quaternion(sphereBody.quaternion.x, sphereBody.quaternion.y, sphereBody.quaternion.z, sphereBody.quaternion.w);
const torqueArmWorld = centerOffset.clone().applyQuaternion(threeQuaternion);
const gravitationalForceVector = new THREE.Vector3(0, -gravitationalAcceleration, 0).multiplyScalar(sphereBody.mass);
const torqueVector = new THREE.Vector3().crossVectors(torqueArmWorld, gravitationalForceVector);
torqueVector.multiplyScalar((liquidMassRatio / 100) * torqueMagnitudeScale);
dataReadouts.liquidMassRatio.textContent = `${liquidMassRatio.toFixed(4)}%`;
dataReadouts.effectiveCoreMass.textContent = `${effectiveCoreMass.toFixed(4)} Gt`;
dataReadouts.surfaceWaterMass.textContent = `${surfaceWaterMass.toFixed(4)} Gt`;
dataReadouts.remainingIceMass.textContent = `${remainingIceMass.toFixed(4)} Gt`;
dataReadouts.angularVelocity.textContent = `${angVel.toFixed(4)} rad/s`;
dataReadouts.torqueMagnitude.textContent = torqueVector.length().toExponential(4);
dataReadouts.objectUpDeviation.textContent = `${objectUpDeviationDegrees.toFixed(2)}°`;
dataReadouts.rotationVectorDeviation.textContent = `${rotationVectorDeviationDegrees.toFixed(2)}°`;
dataReadouts.simulatedTime.textContent = `${simulationTimeDays.toFixed(2)}`;
}
})();
</script>
</div>