Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulate gravitational pull on a body in orbit

I'm trying to simulate gravitational pull/acceleration in the following animation. earth's angular velocity should increase as it gets closer to sol and decrease as it gets far. I think I'll need an easing function to modify the earth.angularVelocity but no idea how.

I don't know if already defined easing functions or a custom one would work. The easing function I need should work like in this graph:

pull/relase easing function graph

earth's perihelion is at 180°, and aphelion is at 0/360°. How can I create such a function and make this work?

function pullRelease(angularPosition, begin, change, maxVelocity) {
    // ?
}
earth.angularVelocity = pullRelease(earth.angularPosition, 0, 360, 3);

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var sol = {
  x: 125,
  y: 150,
  r: 30,
  fill: "gold",
};

var orbit = {
  x: 200,
  y: 150,
  semiMajor: 150,
  semiMinor: 75,
};

var earth = {
  r: 15,
  fill: "dodgerblue",
  angularPosition: 0,
  angularVelocity: 1,
};

// draw sun
context.beginPath();
context.arc(sol.x, sol.y, sol.r, 0, 360 * Math.PI / 180);
context.fillStyle = sol.fill;
context.fill();
// draw fake sun to mark the orbit center
context.beginPath();
context.arc(orbit.x, orbit.y, sol.r, 0, 360 * Math.PI / 180);
context.fillStyle = "rgba(255,215,0,.1)";
context.fill();
// draw earth's orbit path
context.beginPath();
context.ellipse(orbit.x, orbit.y, orbit.semiMajor, orbit.semiMinor, 0, 0, 2 * Math.PI);
context.stroke();
// these are fixed, so save them as background-image
canvas.style.backgroundImage = "url(" + canvas.toDataURL() + ")";

function draw() {
  context.resetTransform();
  context.clearRect(0, 0, canvas.width, canvas.height);

  var newPosition = rotate(-earth.angularPosition, orbit.semiMajor, orbit.semiMinor, orbit.x, orbit.y);

  earth.x = newPosition.x;
  earth.y = newPosition.y;
  // earth.angularVelocity = pullRelease(earth.angularPosition, 0, 360, 3);
  earth.angularPosition += earth.angularVelocity;

  if (earth.angularPosition >= 360) {
    earth.angularPosition = 0;
  }
  position.innerHTML = earth.angularPosition + "°";

  context.translate(earth.x, earth.y);

  context.beginPath();
  context.arc(0, 0, earth.r, 0, 360 * Math.PI / 180);
  context.closePath();
  context.fillStyle = earth.fill;
  context.fill();

  requestAnimationFrame(draw);
}

requestAnimationFrame(draw);

function rotate(angle, distanceX, distanceY, originX, originY) {
  return {
    x: originX + Math.cos(angle * Math.PI / 180) * distanceX,
    y: originY + Math.sin(angle * Math.PI / 180) * distanceY,
  }
}
body {
  background: gainsboro;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .1);
}
#position {
  display: inline-block;
  width: 35px;
  text-align: right;
}
<canvas id="canvas" class="box" width="400" height="300"></canvas>
<p>Position: <span id="position">0</span></p>
like image 443
akinuri Avatar asked Oct 29 '22 21:10

akinuri


1 Answers

After some thinking, I've came up with a function which seems to work, but still I don't think the effect feels natural. Function produces the graph at left. An ideal one would be the one at right. So the function still needs some work.

enter image description here

function pullRelease(angularPosition, begin, change, minVelocity, maxVelocity) {
    var midWay = Math.floor(change/2);
    if (angularPosition >= begin && angularPosition < midWay) {
        var percent = angularPosition / midWay;
        return minVelocity + (maxVelocity-minVelocity) * percent;
    }
    else if (angularPosition == midWay) {
        return maxVelocity;
    }
    else if (angularPosition > midWay && angularPosition <= change) {
        var midWayOffset = angularPosition - midWay;
        var remaining    = midWay - midWayOffset;
        var percent      = remaining / midWay;
        return minVelocity + (maxVelocity-minVelocity) * percent;
    }
}

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var sol = {
  x: 125,
  y: 150,
  r: 30,
  fill: "gold",
};

var orbit = {
  x: 200,
  y: 150,
  semiMajor: 150,
  semiMinor: 75,
};

var earth = {
  x: 0,
  y: 0,
  r: 15,
  fill: "dodgerblue",
  angularPosition: 0,
  angularVelocity: 0,
};

// draw sun
context.beginPath();
context.arc(sol.x, sol.y, sol.r, 0, 360 * Math.PI / 180);
context.fillStyle = sol.fill;
context.fill();
// draw fake sun to mark the orbit center
context.beginPath();
context.arc(orbit.x, orbit.y, sol.r, 0, 360 * Math.PI / 180);
context.fillStyle = "rgba(255,215,0,.1)";
context.fill();
// draw earth's orbit path
context.beginPath();
context.ellipse(orbit.x, orbit.y, orbit.semiMajor, orbit.semiMinor, 0, 0, 2 * Math.PI);
context.stroke();
// these are fixed, so save them as background-image
canvas.style.backgroundImage = "url(" + canvas.toDataURL() + ")";

function draw() {
  context.resetTransform();
  context.clearRect(0, 0, canvas.width, canvas.height);

  var newPosition = rotate(-earth.angularPosition, orbit.semiMajor, orbit.semiMinor, orbit.x, orbit.y);
  earth.x = newPosition.x;
  earth.y = newPosition.y;

  earth.angularVelocity = pullRelease(earth.angularPosition, 0, 360, 0.5, 5);
  earth.angularPosition += earth.angularVelocity;

  if (earth.angularPosition >= 360) {
    earth.angularPosition = 0;
  }
  position.innerHTML = Math.floor(earth.angularPosition) + "°";
  velocity.innerHTML = (earth.angularVelocity).toFixed(2) + "°";

  context.translate(earth.x, earth.y);

  context.beginPath();
  context.arc(0, 0, earth.r, 0, 360 * Math.PI / 180);
  context.closePath();
  context.fillStyle = earth.fill;
  context.fill();

  requestAnimationFrame(draw);
}

requestAnimationFrame(draw);

function rotate(angle, distanceX, distanceY, originX, originY) {
  return {
    x: originX + Math.cos(angle * Math.PI / 180) * distanceX,
    y: originY + Math.sin(angle * Math.PI / 180) * distanceY,
  }
}

function pullRelease(angularPosition, begin, change, minVelocity, maxVelocity) {
  var midWay = Math.floor(change / 2);
  if (angularPosition >= begin && angularPosition < midWay) {
    var percent = angularPosition / midWay;
    return minVelocity + (maxVelocity - minVelocity) * percent;
  } else if (angularPosition == midWay) {
    return maxVelocity;
  } else if (angularPosition > midWay && angularPosition <= change) {
    var midWayOffset = angularPosition - midWay;
    var remaining = midWay - midWayOffset;
    var percent = remaining / midWay;
    return minVelocity + (maxVelocity - minVelocity) * percent;
  }
}
body {
  background: gainsboro;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .1);
}
#position {
  display: inline-block;
  width: 35px;
  text-align: right;
}
#velocity {
  display: inline-block;
  width: 35px;
  text-align: right;
}
<canvas id="canvas" class="box" width="400" height="300"></canvas>
<p>Position: <span id="position">0</span></p>
<p>Velocity: <span id="velocity">0</span></p>
like image 60
akinuri Avatar answered Nov 12 '22 19:11

akinuri