I’ve been adding some neat features to SkyCars, including lasers, jump pads, and lava. But one of the most formidable development challenges so far has been orbits. By orbits, I’m talking about asteroids, comets, or even platforms that rotate around an arbitrary point in space. I’d like to share with you my solution for 3 dimensional orbits.
Visualizing the orbit path
Let us imagine an asteroid with a circular orbit of an arbitrary orientation (or axis angle). We may visualize the orbit path like so:
Note that the asteroid has a circular orbit of radius R around an origin point. At any given time, the asteroid will have the coordinates (Rx, Ry, Rz). We will treat R as a vector from the center of the orbit’s path to the asteroid’s position. Applying the distance formula, we know that R = sqrt ( Rx2 + Ry2 + Rz2 ). However, we also know that R casts a shadow (or projection) onto the x/z plane, which we will call Rxz. We also know the distance of Rxz = sqrt ( Rx2 + Rz2 ). This will be important later.
Now I’m going to jump ahead a bit so that we know what our goal is. If we want our asteroid to orbit the center point, we need to find out the orbit’s normal vector (N), which has Nx, Ny, Nz components and also originates from the orbit path’s center. Here’s what this looks like:
This vector describes an imaginary line perpendicular to the orbit plane. If we know the values of this vector, we can use a “rotate by axis angle” command on our asteroid. In Shiva, the command is simply:
object.rotateAxisAngle ( hObject, nAxisX, nAxisY, nAxisZ, nAngle, kSpace )
nAxisX, nAxisY, nAxisZ will take the values of Nx, Ny, Nz. nAngle is the asteroid’s orbital angular velocity, and kSpace should be set to global. In other game engines, there are similar commands. If you do not have such libraries, here is a good tutorial on how to perform such rotations using a rotation matrix. Shiva users: As object.rotateAxisAngle does not accept position offsets, I had to parent the asteroid to a helper object placed in the center of the orbit.
Now let’s discuss how to get from our asteroid vector R to the normal vector N. Notice that R and N are orthogonal (perpendicular. You may be tempted to say that if we know R, we can just rotate this vector back by 90 degrees to get N. However, we do not have enough information because there are an infinite number of normals orthogonal to R! Or if you prefer, we could spin N around the R axis by all values of the angle phi and still obtain an orthogonal normal. This is actually a good thing. We can choose our own value for phi and in effect choose the amount of bank or tilt in our orbit.
If we want to find N which is orthogonal vector to R, we can use the vector cross product. The cross product takes two input vectors (together which define a plane), and outputs an orthogonal vector (to this plane). R is our first input vector; we can use phi to determine the necessary secondary vector which we will call Q.
Let’s create a vector aligned with the Z+ axis called Phi0. First we’ll rotate it upward around the X axis by angle phi to reach an intermediate vector Phi1.
Next, we’ll rotate this vector around the Y-axis so that it is tangent to the asteroid. The result of this second transformation will give us Q.
In order to perform this second transform, we need to know the angle of Rxz. As this vector is composed of Rx and Rz, we can determine the angle we need to rotate Phi1 by to get Q, or angle rho. While we do not need to calculate this angle, we do need to make note of the following identities:
sin rho = Rz / Rxz
cos rho = Rx / Rxz
Also remember that Rxy = sqrt ( Rx2 + Rz2 ) and that Rx and Rz are given.
Doing a little mathemagics, we can determine the components of Q. The transformation from Phi0 to Phi1 has the components:
( 0, sin phi, cos phi )
When we add in the second transformation, the resultant vector Q takes on the form:
( cos phi * sin rho,
cos phi * cos rho )
We can simplify further using substitution:
( cos phi * Rz / Rxz ,
cos phi * Rx / Rxz )
Also note that Q is now tangent to the asteroid’s orbit. This means that we now have two valid input vectors, Q and R, which describe the plane of the orbit path! We can now use the vector cross product to solve for N:
N = Q × R
Don’t forget to normalize N (scale the vector to a magnitude of 1) when we’re done solving. Now just rotate the asteroid about the normal, which is the axis angle for our orbit path! We’re done.
In Shiva, this all looks somewhat like this:
-- When initializing your rotating object: local Rxz = math.sqrt ( Rx*Rx + Rz*Rz ) local Qx = math.cos ( phi ) * Rz/Rxz local Qy = math.sin ( phi ) local Qz = math.cos ( phi ) * Rx/Rxz local Nx, Ny, Nz = math.vectorNormalize ( math.vectorCrossProduct ( Qx, Qy, Qz, Rx, Ry, Rz ) ) -- Store Nx, Ny, Nz somewhere. During the game loop, rotate the object using: object.rotateAxisAngle ( hObject, Nx, Ny, Nz, dR, object.kGlobalSpace ) -- note: dR is the orbit speed that you choose.
A few final thoughts…
We have created a function that allows an object to orbit in a circular path of any orientation around a center point. However, if we choose a center point directly above the object, we cannot use the above method perform an orbit because vectors R and Q will either be identical or incorrect, meaning that N cannot be computed. A similar problem may occur with a center point directly below the object. I resolved this issue by manually setting appropriate values for Q so that it is parallel to the x/z plane.
If you see “circular path” as a constraint, you can achieve elliptical paths by having a center point that moves with time. Specifically, you would want a center point that moves back and forth along a line. The velocity of the center point along this line should be sinusoidal, with the same frequency (or dR) and phase as your orbit. You could even have the center point of your orbit move on an arbitrary path to create a complex orbit, such as a moon orbiting a planet orbiting a sun.
I would like to thank my good friend Kyle Jewhurst for helping me work through and verify the math on this topic. Also now that I’ve nailed down the orbit code, videos of SkyCars are coming soon!