diff --git a/wbld/orbits.html b/wbld/orbits.html index 543d7d6..15f6cc3 100644 --- a/wbld/orbits.html +++ b/wbld/orbits.html @@ -46,6 +46,7 @@ + @@ -65,7 +66,7 @@ return `Year ${year} - ${seasons[seasonIndex]} ${dayOfSeason}`; } - // Phaser configuration (responsive) + // Phaser configuration const config = { type: Phaser.AUTO, width: window.innerWidth, @@ -85,48 +86,49 @@ let globalGraphics; let dateText; - let simulationTime = 0; // in days (simulation time) - let simulationSpeed = 5; // speed multiplier - let isPlaying = false; // Start paused - let isRewinding = false; // Control forward/backward + let simulationTime = 0; + let simulationSpeed = 5; + let isPlaying = false; + let isRewinding = false; - - // Orbital periods (in days) - const planetPeriod = 396; // Senthara's orbital year - const planetRotationPeriod = 1; // Planet rotates fully in 1 day + // Orbital parameters (in days and relative units) + const planetPeriod = 396; + const planetRotationPeriod = 1; const kerielPeriod = 27; const arkaenPeriod = 82; const minianPeriod = 98; - // Orbital radii (computed relative to window size) + // Orbital radii and eccentricities let planetOrbitRadius, kerielOrbitRadius, arkaenOrbitRadius, minianOrbitRadius; + const planetEccentricity = 0.2; // Example eccentricity for Senthara + const kerielEccentricity = 0.1; + const arkaenEccentricity = 0.15; + const minianEccentricity = 0.05; - // Containers for the planet and the moons + // Angle offsets + let planetAngleOffset; + let kerielAngleOffset; + let arkaenAngleOffset; + let minianAngleOffset; + + // Containers let planetContainer; let kerielContainer, arkaenContainer, minianContainer; function preload() { // No external assets are needed. } - function create() { globalGraphics = this.add.graphics(); - - dateText = this.add.text(10, game.scale.height - 30, "", { - font: "20px Arial", - fill: "#ffffff" - }); + dateText = this.add.text(10, game.scale.height - 30, "", { font: "20px Arial", fill: "#ffffff" }); recalcOrbitRadii(); + setRandomAngleOffsets(); - // Create a container for Senthara (the planet) planetContainer = this.add.container(0, 0); - - // Draw the planet body (green circle) with a marker for rotation. let planetBody = this.add.graphics(); planetBody.fillStyle(0x00FF00, 1); planetBody.fillCircle(0, 0, 12); - // Marker line (points to the right) planetBody.lineStyle(2, 0x000000, 1); planetBody.beginPath(); planetBody.moveTo(0, 0); @@ -134,29 +136,22 @@ planetBody.strokePath(); planetContainer.add(planetBody); - // Create a night overlay (semi-transparent dark half-circle) let nightOverlay = this.add.graphics(); nightOverlay.fillStyle(0x000000, 0.5); - // Draw a half-circle (180° arc) with radius 12 nightOverlay.slice(0, 0, 12, 0, Math.PI, false); nightOverlay.fillPath(); - // Save as a property for updating its rotation later. planetContainer.nightOverlay = nightOverlay; planetContainer.add(nightOverlay); - // Create containers for each moon. - kerielContainer = createMoonContainer(this, 6, 0xFF0000); // Keriel: red - arkaenContainer = createMoonContainer(this, 5, 0x0000FF); // Arkaen: blue - minianContainer = createMoonContainer(this, 5, 0xFFFFFF); // Minian: white + kerielContainer = createMoonContainer(this, 6, 0xFF0000); + arkaenContainer = createMoonContainer(this, 5, 0x0000FF); + minianContainer = createMoonContainer(this, 5, 0xFFFFFF); - // Listen for window resize events. this.scale.on('resize', (gameSize) => { recalcOrbitRadii(); dateText.setPosition(10, gameSize.height - 30); }); - - // --- UI Control Event Handlers --- const playPauseButton = document.getElementById("playPauseButton"); const rewindButton = document.getElementById("rewindButton"); const speedSelect = document.getElementById("speedSelect"); @@ -164,29 +159,25 @@ playPauseButton.addEventListener("click", () => { isPlaying = !isPlaying; playPauseButton.textContent = isPlaying ? "Pause" : "Play"; - if (isPlaying) isRewinding = false; // Stop rewinding when playing + if (isPlaying) isRewinding = false; }); - rewindButton.addEventListener("click", () => { isRewinding = !isRewinding; rewindButton.textContent = isRewinding ? "Forward" : "Rewind"; - if(isRewinding) isPlaying = false; // Pause when rewinding - playPauseButton.textContent = "Play"; // Reset play/pause button + if (isRewinding) isPlaying = false; + playPauseButton.textContent = "Play"; }); speedSelect.addEventListener("change", () => { simulationSpeed = parseFloat(speedSelect.value); }); } - - // Helper function to create a moon container with a circular body and a marker. function createMoonContainer(scene, radius, color) { let moonContainer = scene.add.container(0, 0); let moonBody = scene.add.graphics(); moonBody.fillStyle(color, 1); moonBody.fillCircle(0, 0, radius); - // Marker line indicating the "front" of the moon. moonBody.lineStyle(2, 0x000000, 1); moonBody.beginPath(); moonBody.moveTo(0, 0); @@ -199,87 +190,122 @@ // Recalculate orbital radii based on the current window size. function recalcOrbitRadii() { const minDim = Math.min(window.innerWidth, window.innerHeight); - planetOrbitRadius = minDim * 0.3; // 30% of the smaller dimension + planetOrbitRadius = minDim * 0.3; kerielOrbitRadius = planetOrbitRadius * 0.25; arkaenOrbitRadius = planetOrbitRadius * 0.35; minianOrbitRadius = planetOrbitRadius * 0.45; } + // Sets random angle offsets. + function setRandomAngleOffsets() { + planetAngleOffset = Phaser.Math.RND.between(0, 360); + kerielAngleOffset = Phaser.Math.RND.between(0, 360); + arkaenAngleOffset = Phaser.Math.RND.between(0, 360); + minianAngleOffset = Phaser.Math.RND.between(0, 360); + } + + // Calculates position on an ellipse. + function calculateEllipsePosition(centerX, centerY, semiMajorAxis, eccentricity, period, time, angleOffset) { + const meanAnomaly = (2 * Math.PI * (time % period)) / period; + // Approximate eccentric anomaly using Newton-Raphson method + let eccentricAnomaly = approximateEccentricAnomaly(meanAnomaly, eccentricity); + + // Calculate the true anomaly + const trueAnomaly = 2 * Math.atan2( + Math.sqrt(1 + eccentricity) * Math.sin(eccentricAnomaly / 2), + Math.sqrt(1 - eccentricity) * Math.cos(eccentricAnomaly / 2) + ); + + // Calculate distance from the center + const distance = semiMajorAxis * (1 - eccentricity * Math.cos(eccentricAnomaly)); + + // Calculate x and y coordinates, with angle offset + const x = centerX + distance * Math.cos(trueAnomaly + Phaser.Math.DegToRad(angleOffset)); + const y = centerY + distance * Math.sin(trueAnomaly + Phaser.Math.DegToRad(angleOffset)); + + return { x, y }; + } + // Approximates the eccentric anomaly using the Newton-Raphson method. + function approximateEccentricAnomaly(meanAnomaly, eccentricity) { + let E = meanAnomaly; // Initial guess + let delta = 1; + + // Iterate until the change is small enough (e.g., less than 0.0001) + while (delta > 0.0001) { + let nextE = E - (E - eccentricity * Math.sin(E) - meanAnomaly) / (1 - eccentricity * Math.cos(E)); + delta = Math.abs(nextE - E); + E = nextE; + } + return E; + } function update(time, delta) { - // Advance or rewind simulation time based on state. if (isPlaying) { simulationTime += (delta * simulationSpeed) / 1000; } else if (isRewinding) { simulationTime -= (delta * simulationSpeed) / 1000; } - - // Get center of the screen (position of the star). const centerX = game.scale.width / 2; const centerY = game.scale.height / 2; - // Clear global graphics. globalGraphics.clear(); - - // Draw the central star. globalGraphics.fillStyle(0xFFFF00, 1); globalGraphics.fillCircle(centerX, centerY, 20); - // Draw Senthara's orbital path (around the star). - globalGraphics.lineStyle(1, 0x555555, 1); - globalGraphics.strokeCircle(centerX, centerY, planetOrbitRadius); + // Calculate and draw Senthara's elliptical orbit + const planetPos = calculateEllipsePosition(centerX, centerY, planetOrbitRadius, planetEccentricity, planetPeriod, simulationTime, planetAngleOffset); + planetContainer.x = planetPos.x; + planetContainer.y = planetPos.y; - // Calculate Senthara's (the planet's) position along its orbit. - const planetOrbitAngle = Phaser.Math.DegToRad((360 * (simulationTime % planetPeriod)) / planetPeriod - 90); - const planetX = centerX + planetOrbitRadius * Math.cos(planetOrbitAngle); - const planetY = centerY + planetOrbitRadius * Math.sin(planetOrbitAngle); - - // Update the planet container's position. - planetContainer.x = planetX; - planetContainer.y = planetY; - // Set the planet's self-rotation (day/night cycle) with a 1-day period. const planetRotationAngle = Phaser.Math.DegToRad((360 * (simulationTime % planetRotationPeriod)) / planetRotationPeriod); planetContainer.rotation = planetRotationAngle; - // Update the night overlay so that the dark half covers the side away from the star. - const subsolarAngle = Phaser.Math.Angle.Between(planetX, planetY, centerX, centerY); - // The night overlay's rotation is adjusted relative to the planet's rotation. + const subsolarAngle = Phaser.Math.Angle.Between(planetPos.x, planetPos.y, centerX, centerY); planetContainer.nightOverlay.rotation = subsolarAngle + Math.PI / 2 - planetContainer.rotation; - // Calculate and update moon positions (relative to Senthara). - // Keriel: - const kerielOrbitAngle = Phaser.Math.DegToRad((360 * (simulationTime % kerielPeriod)) / kerielPeriod - 90); - const kerielX = planetX + kerielOrbitRadius * Math.cos(kerielOrbitAngle); - const kerielY = planetY + kerielOrbitRadius * Math.sin(kerielOrbitAngle); - kerielContainer.x = kerielX; - kerielContainer.y = kerielY; - // Ensure tidal locking: set Keriel's rotation so its marker points toward Senthara. - kerielContainer.rotation = Phaser.Math.Angle.Between(kerielX, kerielY, planetX, planetY); + // Calculate and update moon positions (relative to Senthara, using elliptical orbits) + const kerielPos = calculateEllipsePosition(planetPos.x, planetPos.y, kerielOrbitRadius, kerielEccentricity, kerielPeriod, simulationTime, kerielAngleOffset); + kerielContainer.x = kerielPos.x; + kerielContainer.y = kerielPos.y; + kerielContainer.rotation = Phaser.Math.Angle.Between(kerielPos.x, kerielPos.y, planetPos.x, planetPos.y); - // Arkaen: - const arkaenOrbitAngle = Phaser.Math.DegToRad((360 * (simulationTime % arkaenPeriod)) / arkaenPeriod - 90); - const arkaenX = planetX + arkaenOrbitRadius * Math.cos(arkaenOrbitAngle); - const arkaenY = planetY + arkaenOrbitRadius * Math.sin(arkaenOrbitAngle); - arkaenContainer.x = arkaenX; - arkaenContainer.y = arkaenY; - arkaenContainer.rotation = Phaser.Math.Angle.Between(arkaenX, arkaenY, planetX, planetY); + const arkaenPos = calculateEllipsePosition(planetPos.x, planetPos.y, arkaenOrbitRadius, arkaenEccentricity, arkaenPeriod, simulationTime, arkaenAngleOffset); + arkaenContainer.x = arkaenPos.x; + arkaenContainer.y = arkaenPos.y; + arkaenContainer.rotation = Phaser.Math.Angle.Between(arkaenPos.x, arkaenPos.y, planetPos.x, planetPos.y); - // Minian: - const minianOrbitAngle = Phaser.Math.DegToRad((360 * (simulationTime % minianPeriod)) / minianPeriod - 90); - const minianX = planetX + minianOrbitRadius * Math.cos(minianOrbitAngle); - const minianY = planetY + minianOrbitRadius * Math.sin(minianOrbitAngle); - minianContainer.x = minianX; - minianContainer.y = minianY; - minianContainer.rotation = Phaser.Math.Angle.Between(minianX, minianY, planetX, planetY); + const minianPos = calculateEllipsePosition(planetPos.x, planetPos.y, minianOrbitRadius, minianEccentricity, minianPeriod, simulationTime, minianAngleOffset); + minianContainer.x = minianPos.x; + minianContainer.y = minianPos.y; + minianContainer.rotation = Phaser.Math.Angle.Between(minianPos.x, minianPos.y, planetPos.x, planetPos.y); - // Optionally, redraw the moons' orbital paths (around Senthara). - globalGraphics.lineStyle(1, 0x888888, 1); - globalGraphics.strokeCircle(planetX, planetY, kerielOrbitRadius); - globalGraphics.strokeCircle(planetX, planetY, arkaenOrbitRadius); - globalGraphics.strokeCircle(planetX, planetY, minianOrbitRadius); + // Draw elliptical orbits (optional, for visualization) + drawEllipticalOrbit(globalGraphics, centerX, centerY, planetOrbitRadius, planetEccentricity, planetAngleOffset); + drawEllipticalOrbit(globalGraphics, planetPos.x, planetPos.y, kerielOrbitRadius, kerielEccentricity, kerielAngleOffset); + drawEllipticalOrbit(globalGraphics, planetPos.x, planetPos.y, arkaenOrbitRadius, arkaenEccentricity, arkaenAngleOffset); + drawEllipticalOrbit(globalGraphics, planetPos.x, planetPos.y, minianOrbitRadius, minianEccentricity, minianAngleOffset); - // Update the Senthara calendar date text. dateText.setText(getSentharaDate(simulationTime)); + } + // Helper function to draw an elliptical orbit using Phaser graphics. + function drawEllipticalOrbit(graphics, centerX, centerY, semiMajorAxis, eccentricity, angleOffset) { + const points = []; + const steps = 100; // Number of points to create a smooth curve + for (let i = 0; i <= steps; i++) { + const meanAnomaly = (2 * Math.PI * i) / steps; + const eccentricAnomaly = approximateEccentricAnomaly(meanAnomaly, eccentricity); + const trueAnomaly = 2 * Math.atan2( + Math.sqrt(1 + eccentricity) * Math.sin(eccentricAnomaly / 2), + Math.sqrt(1 - eccentricity) * Math.cos(eccentricAnomaly / 2) + ); + const distance = semiMajorAxis * (1 - eccentricity * Math.cos(eccentricAnomaly)); + const x = centerX + distance * Math.cos(trueAnomaly + Phaser.Math.DegToRad(angleOffset)); + const y = centerY + distance * Math.sin(trueAnomaly + Phaser.Math.DegToRad(angleOffset)); + points.push(new Phaser.Math.Vector2(x, y)); + } + + graphics.lineStyle(1, 0x888888, 1); // Set orbit line style + graphics.strokePoints(points, true); // Draw the ellipse }