1
votes

I'm modelling a two body orbit using JavaScript and HTML canvas. I have a animation function which draws a circle which is the sun and a second circle at set co-ordinates which is the earth. I've transformed the co-ordinate system so the centre of the page is (0,0). I also have an unfinished leapfrog function which will eventually update the position and velocity each time.

My issue is that the second circle no longer shows when its in the animate function but the sun still shows. I think its something to do with the requestAnimationFrame() method but i'm really not to sure what to do about it.

this is the code for the animate function and also the end of the leapfrog function (values are returned which are used in the animate function) and the main function which calls the others:

//retrieving the DOM
var canvas = document.getElementById("solarsys");
// context variable
var c = canvas.getContext('2d');

//initial positions for the earth unit m
// two sets so that data can be stored for reset function
var initialXPos = (-7.564432386799773E-01 * 1.496e+11);
var initialYPos = (-6.718892710848022E-01 * 1.496e+11);
var xPos = (-7.564432386799773E-01 * 1.496e+11);
var yPos = (-6.718892710848022E-01 * 1.496e+11);
//initial velocities for the earth unit ms-1
var initialXVel = ((1.110596439833439E-02 * 1.496e+11) / 86400);
var initialYVel = ((-1.296782278648682E-02 * 1.496e+11) / 86400);
var xVel = ((1.110596439833439E-02 * 1.496e+11) / 86400);
var yVel = ((-1.296782278648682E-02 * 1.496e+11) / 86400);

//position and velocity scales
var posScale = 1E9;
var velScale = 1000;

function axis(c) {
  //this function translates the canvas co-ordinates to cartesian co-ordinates
  //moves the origin to the centre of the page
  c.translate(400, 275);
  //makes the y axis grow up and shrink down
  c.scale(1, -1);
};

function leapfrog(xPos, yPos, posScale, xVel, yVel, velScale) {
  //gravitational constant m3kg-1s-2
  var G = 6.67E-11;
  //mass of earth kg
  var m2 = 5.972E24;
  //mass of sun kg
  var m1 = 1.989E30;
  // earth sun distance 1AU = 149 600 000 000 m but depends on where in orbit 
  // therefore do pythagoras on current x and y co-ordinates of position
  //first covert xPos and yPos to m so force equation dimensionally constant
  var xPosm = xPos * 1.496E+11;
  var yPosm = yPos * 1.496e+11;
  var r = (Math.sqrt((Math.pow(xPosm, 2)) + (Math.pow(yPosm, 2))));
  //calculate the force on the earth using F = Gm1m2/r^2
  //force is towards the centre of the sun
  var F = ((G * m1 * m2) / (Math.pow(r, 2)));
  //calculate earths acceleration using Newton 2nd a = F / m 
  var a = (F / m2);
  //leapfrog loop for new position and velocity
  //position new = previous position + previous half velocity * time step
  //velocity new +1/2 from pos = previous velocity 1/2 from pos + acceleration

  // calculates scaled positions
  var xPosScaled = xPos / posScale;
  var yPosScaled = yPos / posScale;

  //calculates scaled velocities
  var xVelScaled = xVel / velScale;
  var yVelScaled = yVel / velScale;

  return [xPosScaled, yPosScaled, xVelScaled, yVelScaled];
};

function drawEarth(c, xPosScaled, yPosScaled) {
  //draws a blue circle - the earth
  c.beginPath();
  c.arc(xPosScaled, yPosScaled, 10, Math.PI * 0, Math.PI * 2, false);
  c.fillStyle = '#003399';
  c.fill();
  c.stroke();
  c.closePath();
};

function drawSun(c) {
  //draw a yellow circle - the sun
  c.beginPath();
  c.arc(0, 0, 30, Math.PI * 0, Math.PI * 2, false);
  c.fillStyle = '#ffcc00';
  c.fill();
  c.stroke();
  c.closePath();
};

function animate(c, xPosScaled, yPosScaled) {
  requestAnimationFrame(animate);

  c.clearRect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
  //if want a black background
  //c.fillRect(-innerWidth/2,-innerHeight/2,innerWidth,innerHeight); 
  drawSun(c);
  drawEarth(c, xPosScaled, yPosScaled);
};

function main(c, xPos, yPos, posScale, xVel, yVel, velScale) {
  axis(c);
  var posVelArray = leapfrog(xPos, yPos, posScale, xVel, yVel, velScale);
  var xPosScaled = posVelArray[0];
  var yPosScaled = posVelArray[1];
  var xVelScaled = posVelArray[2];
  var yVelScaled = posVelArray[3];
  animate(c, xPosScaled, yPosScaled);
};
<canvas id="solarsys"></canvas>

any help would be great!

1
The function you do pass to requestAnimationFrame will receive only one argument, which is a timestamp of when the rAF callbacks started to be called. So your moving circle will use this timestamp as x value and undefined as y value. Also, you don't seem to update these values anywhere in the anim loop, you'll probably want to include main too in this loop.Kaiido
Please provide full code! preferably in a code snippetHelder Sepulveda
@HelderSepu i have updated it to show the whole piece of code, at the moment the leapfrog function doesnt actually find new positions and velocities because i'm still trying to work out how to do itJane
I now have an i issue that its not recognising my variable c but is drawing both planetsJane
I transformed your code into a snippet, I do not see anything drawn...Helder Sepulveda

1 Answers

0
votes

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			body {
				background-color: black;
			}
			
			canvas {
				display: block;
				margin: auto;
				border: solid 1px white;
				border-radius: 10px;
			}
			
			div {
				display: block;
				margin: auto;
				margin-top: 20px;
				padding-left: 10px;
				padding-bottom: 10px;
				width: 150px;
				height: 40px;
				background-color: gray;
				border: solid 1px white;
				border-radius: 10px;
			}
			
			p {
				display: inline-block;
				color: white;
			}
			
			input {
				display: inline-block;
			}
		</style>
	</head>
	
	<body>
		<canvas id="canvas"></canvas>
		<div>
			<p>Enable Collision:</p>
			<input type="checkbox" onclick="callbacks.toggleCollision();"></input>
		</div>
		<script type="application/javascript">
		
		var callbacks = function() {
			
			"use strict";
			
			/*
				Calculate a body's acceleration due to gravity
				
				f = G * ((m1 * m2) / r^2)
				f = m1 * a1
				
				=> m1 * a1 = G * ((m1 * m2) / r^2)
				=> a1 = G * m2 / r^2 -> m2 being the mass of the other object
				
				Below this formula is integrated using the verlet method
			*/
			
			// Constants
			var canvasWidth = 180;
			var canvasHeight = 160;
			
			// Classes
			function StaticBody(x,y,r,m) {
				this.x = x || console.error("Static body missing x coordinate");
				this.y = y || console.error("Static body missing y coordinate");
				this.r = r || console.error("Static body missing radius");
				this.m = m || console.error("Static body missing mass");
			}
			
			StaticBody.prototype = {
				STATIC: true,
			
				preTick: function(bodies) {
					// Something static doesn't really need to update
				},
			
				tick: function() {
					// Something static doesn't really need to update
				},
				
				render: function(ctx) {
					ctx.strokeStyle = "black";
					ctx.fillStyle = "#FDB813FF";
					ctx.beginPath();
					ctx.arc(this.x,this.y,this.r,0.0,2.0 * Math.PI,false);
					ctx.fill();
					ctx.stroke();
				}
			};
			
			function DynamicBody(x,y,r,m) {
				this.x = x || console.error("Dynamic body missing x coordinate");
				this.y = y || console.error("Dynamic body missing y coordinate");
				this.r = r || console.error("Dynamic body missing radius");
				this.m = m || console.error("Dynamic body missing mass");
				this.ox = this.x + Math.random() * 1.0 - 0.5;
				this.oy = this.y + Math.random() * 1.0 - 0.5;
				this.ax = 0.0;
				this.ay = 0.0;
			}
			
			DynamicBody.prototype = {
				STATIC: false,
				GRAVITY_CONSTANT: 0.1, // I've tickered with this value to get better looking behaviour
				MAX_VELOCITY: 1.0,
			
				preTick: function(bodies) {
					this.ax = 0.0;
					this.ay = 0.0;
				
					for (var i = 0; i < bodies.length; ++i) {
						var body = bodies[i];
						
						if (body !== this) {
							var x = body.x - this.x;
							var y = body.y - this.y;
							var invR = 1.0 / Math.max(1.0,Math.sqrt(Math.pow(x,2.0) + Math.pow(y,2.0)));
							var a = this.GRAVITY_CONSTANT * body.m * invR;
							this.ax += x * invR * a;
							this.ay += y * invR * a;
						}
					}
				},
			
				tick: function(bodies,isCollisionEnabled) {
					var nx = this.x + (this.x - this.ox) + this.ax * 0.016;
					var ny = this.y + (this.y - this.oy) + this.ay * 0.016;
					this.ox = this.x;
					this.oy = this.y;
					this.x = nx;
					this.y = ny;
					
					if (this.x < this.r) {
						this.x = this.ox = this.r;
					} else if (this.x > canvasWidth - this.r) {
						this.x = this.ox = canvasWidth - this.r;
					}
				
					if (this.y < this.r) {
						this.y = this.oy = this.r;
					} else if (this.y > canvasHeight - this.r) {
						this.y = this.oy = canvasHeight - this.r;
					}
				
					if (isCollisionEnabled) {
						for (var i = 0; i < bodies.length; ++i) {
							var body = bodies[i];
							
							if (body !== this) {
								var x = body.x - this.x;
								var y = body.y - this.y;
								var r = Math.sqrt(Math.pow(x,2.0) + Math.pow(y,2.0));
								var invR = 1.0 / r;
								var cr = this.r + body.r;
								
								if (r < cr) {
									var d = cr - r;
									
									if (body.STATIC) {
										this.x -= x * invR * d;
										this.y -= y * invR * d;
									} else {
										this.x -= x * invR * d * 0.5;
										this.y -= y * invR * d * 0.5;
										body.x += x * invR * d * 0.5;
										body.y += y * invR * d * 0.5;
									}
								}
							}
						}
					}
				},
				
				render: function(ctx) {
					ctx.strokeStyle = "black";
					ctx.fillStyle = "#888888FF";
					ctx.beginPath();
					ctx.arc(this.x,this.y,this.r,0.0,2.0 * Math.PI,false);
					ctx.fill();
					ctx.stroke();
				}
			};
			
			// Variables
			var canvas = null;
			var ctx = null;
			var bodies = [];
			var isCollisionEnabled = false;
			
			// functions
			function toggleCollision() {
				isCollisionEnabled = !isCollisionEnabled;
			}
			
			// Game loop
			function loop() {
				// Tick
				for (var i = 0; i < bodies.length; ++i) {
					bodies[i].preTick(bodies);
				}
				
				for (var i = 0; i < bodies.length; ++i) {
					bodies[i].tick(bodies,isCollisionEnabled);
				}
				
				// Render
				ctx.fillStyle = "gray";
				ctx.fillRect(0,0,canvasWidth,canvasHeight);
				
				for (var i = 0; i < bodies.length; ++i) {
					bodies[i].render(ctx);
				}
				
				//
				requestAnimationFrame(loop);
			}
			
			// Event Listeners
			function onMouseDown(e) {
				var bounds = canvas.getBoundingClientRect();
				var r = 2.0 + Math.random() * 2.0;
				
				bodies.push(new DynamicBody(
					e.clientX - bounds.left,
					e.clientY - bounds.top,
					r,
					r
				));
			}
			
			// Entry point
			onload = function() {
				canvas = document.getElementById("canvas");
				canvas.width = canvasWidth;
				canvas.height = canvasHeight;
				canvas.onmousedown = onMouseDown;
				ctx = canvas.getContext("2d");
				
				bodies.push(new StaticBody(90.0,80.0,10.0,50.0));
				
				for (var i = 0, l = 10 + (Math.random() * 20) | 0; i < l; ++i) {
					var r = 2.0 + Math.random() * 2.0
				
					bodies.push(new DynamicBody(
						Math.random() * canvasWidth,
						Math.random() * canvasHeight,
						r,
						r * 2
					));
				}
				
				loop();
			}
			
			return {
				toggleCollision: toggleCollision
			};
			
		}();
		
		</script>
	</body>
</html>