Hi all,
This particular issue has been bugging me far longer than I’d like to admit.
Essentially I’m trying to put a Mover onto a circular path around a centerpoint.
I know how to do this using an incrementing angle and a radius and finding the x/y-coordinates from this. But in this particular setup I don’t want use that approach. Instead, I’d like to only rely on forces to move the Mover and send it on a circular path (orbit) around a centerpoint.
To illustrate, I’ve created the following PoC sketch: There is a mover bouncing around the stage, at a constant velocity. If I press and hold the mouse, a random centerpoint is defined and the Mover “goes into orbit” around this point. When the mouse is released, the Mover is released from this orbit and continues straight ahead.
I like how the Mover can immediately flip in and out of the orbit, hence why I’m trying to solve this only using forces. (In a later iteration, there might also be other forces acting on the Mover).
Expected behaviour – the Mover remains in a perfect circular and stable orbit around the centerpoint
Faulty behaviour – the Mover slowly spirals towards the centerpoint and eventually comes to an equilibrium.
I do not understand how to prevent this spiralling towards the centerpont. I’ve tried several approaches to mitigate this. One is “clamping” the Mover onto the expected orbit if it shifts too far. But this feels not much different than finding x/y from angle/radius. Another approach was to have the velocity constantly autocorrect itself, increasing/decreasing based on how far the Mover diverges from the ideal radius. This worked somewhat but was insanely fickle and never became a viable option.
In my mind, this is a simple calculation – a forwards velocity, a centripetal force and a know radius together make a perfect circular path. But for some reason I’m not getting it to work.
I’m now not even sure if using the centripetal force is indeed the right approach here.
Lastly, even if this may feel like it, I’m not aiming to create a physically correct orbiting simulation that allows objects to slingshot around each other. (So maybe using “orbit” is misleading?) I’m just trying to find an elegant way for a Mover to go in and out of circular paths.
Any ideas on what I’m missing are highly appreciated! Thanks!
Mover mover;
ArrayList<PVector> trail; // store trail
PVector orbitPoint; // randomly selected orbiting point
void setup() {
size(600, 600);
mover = new Mover();
trail = new ArrayList<>();
background(30);
}
void draw() {
background(30);
displayTrail();
if (mousePressed) {
if (orbitPoint == null) {
orbitPoint = mover.calculateOrbitPoint(); // calculate orbit point
}
mover.orbit(orbitPoint);
} else {
mover.move();
orbitPoint = null; // reset when the mouse is released
}
mover.display();
mover.edgeBounce();
}
void displayTrail() {
noFill();
stroke(0,255,0);
beginShape();
for (PVector p : trail) {
vertex(p.x, p.y);
}
endShape();
}
class Mover {
PVector location;
PVector velocity;
PVector acceleration;
float speed = 4; // mover speed
Mover() {
location = new PVector(random(width), random(height));
velocity = PVector.fromAngle(random(TWO_PI)).mult(speed); // Random direction
acceleration = new PVector();
}
void move() {
velocity.add(acceleration);
velocity.setMag(speed); // Keep velocity constant
location.add(velocity);
acceleration.mult(0); // clear acceleration
// Add the current position to the trail
trail.add(location.copy());
if (trail.size() > 300) { // Limit the trail length
trail.remove(0);
}
}
void applyForce(PVector force) {
acceleration.add(force);
}
PVector calculateOrbitPoint() {
// calculate two perpendicular points 100 units away
PVector left = velocity.copy().rotate(-HALF_PI).setMag(100).add(location);
PVector right = velocity.copy().rotate(HALF_PI).setMag(100).add(location);
// randomly select one
return random(1) < 0.5 ? left : right;
}
void orbit(PVector center) {
PVector toCenter = PVector.sub(center, location); // towards the orbit center
float radius = toCenter.mag();
// centripetal force: F = (v^2) / r
float centripetalStrength = (speed * speed) / radius;
toCenter.setMag(centripetalStrength);
applyForce(toCenter);
move(); // update position based on forces
// radius
stroke(255, 150);
line(location.x, location.y, center.x, center.y);
}
void edgeBounce() {
if (location.x > width || location.x < 0) {
velocity.x *= -1; // Reverse x-direction
location.x = constrain(location.x, 1, width - 1);
}
if (location.y > height || location.y < 0) {
velocity.y *= -1; // Reverse y-direction
location.y = constrain(location.y, 1, height - 1);
}
}
void display() {
fill(0,255,0);
noStroke();
ellipse(location.x, location.y, 12, 12);
// Visualize the orbit point
if (orbitPoint != null) {
fill(255, 100);
ellipse(orbitPoint.x, orbitPoint.y, 6, 6);
}
}
}