Saturday, January 11, 2020

java - How to move an object along a circumference of another object?


I am so out of math that it hurts, but for some of you this should be a piece of cake. I want to move an object around another along its ages or circumference on a simple circular path. At the moment my game algorithm knows how to move and position a sprite just at the edge of an obstacle and now it waits for the next point to move depending on various conditions.


So the mathematical problem here is how to get (aX, aY) and (bX, bY) positions, when I know the Centre (cX, cY), the object position (oX, oY) and the distance required to move (d)


enter image description here



Answer



(CAVEAT: I'm using two approximations here: the first takes d as an arc length, and the second takes it as an orthogonal length. Both of these approximations should be good for relatively small values of d, but they don't fulfill the precise question as clarified in comments.)


The math on this, fortunately, is relatively straightforward. First of all, we can find the relative vector from our center position to our current position:



deltaX = oX-cX;
deltaY = oY-cY;

And once we have this relative vector, then we can know the radius of the circle we're working on by finding the length of it:


radius = sqrt(deltaX*deltaX+deltaY*deltaY);

What's more, from our relative vector we can find the precise angle that the line from cX to oX is at:


curTheta = atan2(deltaX, deltaY);

Now things get a little bit trickier. First of all, understand that the circumference of a circle — that is, the 'arc length' of an arc with an angular measure of 2π — is 2πr. In general, the arc length of an arc with an angular measure of θ along a circle of radius r is just θr. If we were to use the d in your diagram as the arc length, and since we know the radius, we can find the change in theta to get us to the new position by just dividing out:



deltaTheta = d/radius; // treats d as a distance along the arc

For the case where d needs to be a linear distance, things are a little more complicated, but fortunately not much. There, d is one side of an isoceles triangle whose other two sides are the radius of the circle (from cX/cY to oX/oY and aX/aY respectively), and bisecting this isoceles triangle gives us two right triangles, each of which has d/2 as one side and radius as the hypotenuse; this means that the sine of half our angle is (d/2)/radius, and so the full angle is just twice this:


deltaTheta = 2*asin(d/(2*radius)); // treats d as a linear distance

Notice how if you took the asin out of this formula, and cancelled the 2s, this would be the same as the last formula; this is the same as saying that sin(x) is approximately x for small values of x, which is a useful approximation to know.


Now we can find the new angle by just adding or subtracting:


newTheta = curTheta+deltaTheta; // This will take you to aX, aY. For bX/bY, use curTheta-deltaTheta

Once we have the new angle, then we can use some basic trig to find our updated relative vector:



newDeltaX = radius*cos(newTheta);
newDeltaY = radius*sin(newTheta);

and from our center position and our relative vector we can (finally) find the target point:


aX = cX+newDeltaX;
aY = cY+newDeltaY;

Now, with all this, there are some big caveats to be aware of. For one, you'll notice that this math is mostly floating-point, and in fact it almost has to be; trying to use this method to update in a loop and rounding back to integer values at every step can do everything from making your circle not close (either spiralling inward or outward every time you go around the loop) to not getting it started in the first place! (If your d is too small, then you might discover that the rounded versions of aX/aY or bX/bY are exactly where your start position oX/oY was.) For another, this is very expensive, especially for what it's trying to do; in general, if you know your character is going to be moving in a circular arc, you should plan out the whole arc in advance and not tick it from frame to frame like this, since many of the most expensive calculations here can be front-loaded to cut down on costs. Another good way to trim back the costs, if you really want to update incrementally like this, is to not use trig in the first place; if d is small and you don't need it to be exact but just very close, then you can do a 'trick' by adding a vector of length d to oX/oY, orthogonal to the vector towards your center (note that a vector orthogonal to (dX, dY) is given by (-dY, dX) ), and then shrink it down to the right length. I won't explain this code quite so step-by-step, but hopefully it'll make sense given what you've seen so far. Note that we 'shrink down' the new delta vector implicitly in the last step, where we add it to our center to get the updated point:


deltaX = oX-cX; deltaY = oY-cY;
radius = sqrt(deltaX*deltaX+deltaY*deltaY);

orthoX = -deltaY*d/radius;
orthoY = deltaX*d/radius;
newDeltaX = deltaX+orthoX; newDeltaY = deltaY+orthoY;
newLength = sqrt(newDeltaX*newDeltaX+newDeltaY*newDeltaY);
aX = cX+newDeltaX*radius/newLength; aY = cY+newDeltaY*radius/newLength;

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...