Search code examples
javascripthtml5-canvas

How to calculate an optical axis line for planetary orbits around the sun to earth in HTML5


I am creating an inner solar sytem model from the percpective of Earth for creating astology birthcharts references. I am able to draw an optical axis line for the Earth - Sun axis, and the Earth - Moon axis but I can not for the life of me figure the trigonometry for the remaining inner planets. Here is a working pen. https://codepen.io/pjdroopypants/pen/wvEKeOm

window.onload = function(){

    var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    cw = canvas.width,
    ch = canvas.height,
    time = 1;

function circle(radius,color,x,y){
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.arc(x,y,radius,0,2*Math.PI,true);
    ctx.fill();
    ctx.closePath();
}


function line(color,ax,ay,bx,by){

    ctx.beginPath();
    ctx.moveTo(ax*2,ay);
    ctx.lineTo(bx+0.5,by+0.5);
    ctx.strokeStyle = color;
    ctx.stroke();
    ctx.closePath();

}

var sunDegree = Math.floor(Math.random() * 359) + 1; //temporary, will use function from live planetary data
var merDegree = Math.floor(Math.random() * 359) + 1; //temporary, will use function from live planetary data 
var venDegree = Math.floor(Math.random() * 359) + 1; //temporary, will use function from live planetary data 
var marDegree = Math.floor(Math.random() * 359) + 1; //temporary, will use function from live planetary data 
var mooDegree = Math.floor(Math.random() * 359) + 1; //temporary, will use function from live planetary data 
var interval = 570.0930061551268;

function animate(){

    ctx.save();
    ctx.clearRect(0, 0, 1100, 1100);
    ctx.translate(cw/2,ch/2);

    //Earth 
    ctx.rotate(-(time / interval)+ Math.PI);
    circle(15,"blue",0,0);
    ctx.translate(480,0);
    line("blue",-240,0,0,0);
    ctx.translate(-480,0);
    ctx.rotate((time / interval)+ Math.PI);

        //Moon
        var moontime = (time / (interval / 13.36996336996337)+mooDegree);
        ctx.rotate(-moontime);
        ctx.translate(23,0);
        circle(3,"black",0,0);
        ctx.translate(457,0);
        line("#6a6a6a",-230,0,0,0);
        ctx.translate(-480,0);
        ctx.rotate(moontime);

    //Sun
    var suntime = time / interval;
    ctx.rotate(-suntime);
    ctx.translate(120,0);
    circle(15,"yellow",0,0);
    ctx.translate(360,0);
    line("yellow",-182,0,0,0);
    ctx.translate(-360,0);
    ctx.rotate(suntime);

    //Mercury
    var mertime = (time / (interval / 4.150568181818182))+merDegree;
    ctx.rotate(-(time / (interval / 4.150568181818182))+merDegree-suntime);
    ctx.translate(40,0);
    circle(15,"#898989",0,0);

    ctx.translate(-40,0);
    ctx.rotate((time / (interval / 4.150568181818182))+merDegree+suntime);

    //Venus
    ctx.rotate(-(time / (interval / 1.625500667556742))+venDegree-suntime);
    ctx.translate(80,0);
    circle(15,"#b9955b",0,0);
    ctx.translate(-80,0);
    ctx.rotate((time / (interval / 1.625500667556742))+venDegree);

    //Mars
    ctx.rotate(-(time / (interval / 0.5316593886462882))+marDegree);
    ctx.translate(160,0);
    circle(15,"#9f5e13",0,0);
    ctx.translate(-160,0);
    ctx.rotate((time / (interval / 0.5316593886462882))+marDegree);

    ctx.restore();
    time++;
    window.requestAnimationFrame(animate);

}

    window.requestAnimationFrame(animate);

}

I attempted to reverse rotate each animation iteration by subtracting the suns rotating angle but that just makes the axis line horizontal and not back to center.


Solution

  • Encapsulate using objects

    Your code is a mess

    You need to encapsulate the various parts so you can work with them individually.

    The example below defines planets using a factory pattern to create planets as objects.

    Each planet gets orbital ang, rate, and distance from the planet they orbit. Eg Mercury, Venus, Earth, Mars all orbit the Sun, and the Moon orbits the Earth.

    Planets are created in orbital order. IE can not create planet before you create the planet it orbits.

    On each animation frame each planet's position is calculated relative to the planet it orbits in the same order as they were created.

    Before rendering the planet we want to center the view on a selected planet (in this case the Earth). The centered planets position is used to create a global matrix that will move all planets to the correct position.

    Then when drawing each planet we multiply the global matrix by the planet's matrix ctx.setTransform(...globalMat); ctx.transform(this.cos, this.sin, -this.sin, this.cos, this.x, this.y); and draw the result

    requestAnimationFrame(mainLoop);
    const ctx = canvas.getContext("2d");
    const TAU = Math.PI * 2;
    const centerOnPlanetName = "Earth";  // Planet name to center view on
    const globalMatrix = [1,0,0,1,0,0];  // holds position of planet to track
    const solSystem = {};                // holds planets by name
    const planets = [];                  // holds planets in order of created
    const planetCommon = {               // common properties of planets
         x: 0, y: 0, cos: 1, sin: 0,
         update(time) {
             this.ang = this.startAng + this.orbitalSpeed * time;
             this.cos = Math.cos(this.ang);
             this.sin = Math.sin(this.ang);
             if (this.orbits) {          // Check if orbiting something
                 this.x = this.orbits.x + this.cos * this.dist;
                 this.y = this.orbits.y + this.sin * this.dist;
             } 
         },
         draw(ctx, globalMat) {
             ctx.setTransform(...globalMat);
             ctx.transform(this.cos, this.sin, -this.sin, this.cos, this.x, this.y);
             ctx.fillStyle = this.color;
             ctx.beginPath();
             ctx.arc(0, 0, this.radius, 0, TAU);
             ctx.fill();
             if (this.dirTo !== undefined) {
                 ctx.beginPath();
                 const ax = Math.cos(this.dirTo);
                 const ay = Math.sin(this.dirTo);
                 ctx.lineTo(ax * this.radius * 1.5, ay * this.radius * 1.5);
                 ctx.lineTo(ax * this.distTo, ay * this.distTo);
                 ctx.stroke();         
             }
         },
         directionTo(planet) {
             if (planet !== this) {
                  this.dirTo = Math.atan2(planet.y - this.y, planet.x - this.x) - this.ang;
                  this.distTo = Math.hypot(planet.y - this.y, planet.x - this.x);
             } else {
                  this.dirTo = undefined;
             }
         }
    };
    const planet = (name, orbitsName, dist, orbitalSpeed, startAng, radius, color) => {
        planets.push(solSystem[name] = {
            orbits: solSystem[orbitsName], // Set planet new planet orbits
            dist,                          // dist from orbiting body
            orbitalSpeed,                  // in radians per time unit
            startAng,                      // starting angle in radians
            radius,
            color,
            ...planetCommon,
         });
    };
    planet("Sun", undefined,     0, 0.8, - Math.PI, 20, "yellow");
    planet("Mercury", "Sun",    45,   2,         0,  6, "#888");
    planet("Venus",   "Sun",    70, 1.2,         0, 15, "#CC8");
    planet("Earth",   "Sun",   120, 0.8,         0, 16, "#39B");
    planet("Mars",    "Sun",   175, 0.4,         0, 10, "#B43");
    planet("Moon",    "Earth",  30,   5,         0,  3, "#444");
    
    function centerOn(cx, cy, planet) {
        globalMatrix[4] = cx - planet.x;
        globalMatrix[5] = cy - planet.y;
    }
    
    function mainLoop(time) {
        time /= 1000;
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        planets.forEach(planet => planet.update(time));
        planets.forEach(planet => planet.directionTo(solSystem[centerOnPlanetName]));
        centerOn(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5, solSystem[centerOnPlanetName]);
        planets.forEach(planet => planet.draw(ctx, globalMatrix));
        requestAnimationFrame(mainLoop);
    }
    canvas {
     border: 1px solid black;
    }
    <canvas id="canvas" width="600" height="600"></canvas>