This is our chance to throw some real science into our project. The following table shows the actual distance, size, rotation, and orbit values for each of the planets. (Most of this data came from http://www.enchantedlearning.com/subjects/astronomy/planets/.)
Planet |
Distance from Sun (millions km) |
Radius size (km) |
Day length (Earth hours) |
Year length (Earth years) |
---|---|---|---|---|
Mercury |
57.9 |
2440 |
1408.8 |
0.24 |
Venus |
108.2 |
6052 |
5832 |
0.615 |
Earth |
147.1 |
6371 |
24 |
1.0 |
Earth's Moon |
0.363 (from Earth) |
1737 |
0 | |
Mars |
227.9 |
3390 |
24.6 |
2.379 |
Jupiter |
778.3 |
69911 |
9.84 |
11.862 |
Saturn |
1427.0 |
58232 |
10.2 |
29.456 |
Uranus |
2871.0 |
25362 |
17.9 |
84.07 |
Neptune |
4497 |
24622 |
19.1 |
164.81 |
Pluto (still counts) |
5913 |
1186 |
6.39 |
247.7 |
We also have texture images for each of the planets. These files are included with the downloads for this book. They should be added to the res/drawable
folder, named mercury_tex.png
, venus_tex.png
, and so on. The following table identifies the sources we have used and where you can find them as well:
We're going to set up all the planets in MainActivity
using a setupPlanets
method that will be called from setup
. Let's go for it.
At the top of the class, declare a planets
array:
Planet[] planets;
Then, we declare a number of constants which we'll explain in a moment:
// tighten up the distances (millions km) float DISTANCE_FACTOR = 0.5f; // this is 100x relative to interplanetary distances float SCALE_FACTOR = 0.0001f; // animation rate for one earth rotation (seconds per rotation) float EDAY_RATE = 10f; // rotation scale factor e.g. to animate earth: dt * 24 * // DEG_PER_EHOUR float DEG_PER_EHOUR = (360f / 24f / EDAY_RATE); // animation rate for one earth rotation (seconds per orbit)// (real is EDAY_RATE * 365.26) float EYEAR_RATE = 1500f; // orbit scale factorfloat DEG_PER_EYEAR = (360f / EYEAR_RATE);
The setupPlanets
method uses our celestial data and builds new planets accordingly. First, let's define the physical data, as follows:
public void setupPlanets(Transform origin) { float[] distances = new float[] { 57.9f, 108.2f, 149.6f, 227.9f, 778.3f, 1427f, 2871f, 4497f, 5913f }; float[] fudged_distances = new float[] { 57.9f, 108.2f, 149.6f, 227.9f, 400f, 500f, 600f, 700f, 800f }; float[] radii = new float[] { 2440f, 6052f, 6371f, 3390f, 69911f, 58232f, 25362f, 24622f, 1186f }; float[] rotations = new float[] { 1408.8f * 0.05f, 5832f * 0.01f, 24f, 24.6f, 9.84f, 10.2f, 17.9f, 19.1f, 6.39f }; float[] orbits = new float[] { 0.24f, 0.615f, 1.0f, 2.379f, 11.862f, 29.456f, 84.07f, 164.81f, 247.7f };
The
distances
array has the distance of each planet from the Sun in millions of km. This is really huge, especially for the outer planets that are really far away and are not very visible relative to other planets. To make things more interesting, we'll fudge the distance of those planets (Jupiter through Pluto), so the values that we'll use are in the fudged_distances
array.
The radii
array has the actual size of each planet in kms.
The rotations
array has the day length, in Earth hours. Since Mercury and Venus spin really fast compared to the Earth, we'll artificially slow them down by arbitrary scale factors.
The orbits
array has the length of each planet's year in Earth years and the time it takes for one complete rotation around the Sun.
Now, let's set up the texture IDs for each planet's materials:
int[] texIds = new int[]{ R.drawable.mercury_tex, R.drawable.venus_tex, R.drawable.earth_tex, R.drawable.mars_tex, R.drawable.jupiter_tex, R.drawable.saturn_tex, R.drawable.uranus_tex, R.drawable.neptune_tex, R.drawable.pluto_tex };
Now initialize the planets
array, creating a new Planet
object for each:
planets = new Planet[distances.length + 1]; for(int i = 0; i < distances.length; i++){ planets[i] = new Planet( fudged_distances[i] * DISTANCE_FACTOR, radii[i] * SCALE_FACTOR, rotations[i] * DEG_PER_EHOUR, orbits[i] * DEG_PER_EYEAR * fudged_distances[i]/distances[i], texIds[i], origin); }
While we fudged some of the planets' actual distances so that they'd be closer to the inner Solar System, we also multiply all the distances by a DISTANCE_FACTOR
scalar, mostly to not blow up our float precision calculations. We scale all the planet sizes by a different SCALE_FACTOR
variable to make them relatively larger than life (a factor of 0.0001 is actually a factor of 100 because radii are calculated in km while the distance is calculated in millions of km).
The rotation animation rate is the actual length of the day of the planet scaled by how fast we want to animate a day in VR. We default to 10 seconds per Earth day.
Lastly, the planetary orbit animation has its own scale factor. We've sped it up about 2 X. You can also adjust the orbit rate of the distance fudge factors (for example, Pluto orbits the Sun once every 247 Earth years, but we've moved it a lot closer so it needs to slow down).
Then, we add the Earth's moon. We've used some artistic license here as well, adjusting the distance and radius and speeding up its orbit rate to make it compelling to watch in VR:
// Create the moon planets[distances.length] = new Planet(7.5f, 0.5f, 0, - 0.516f, R.drawable.moon_tex, planets[2].getTransform());}
Let's take a look at one more method: goToPlanet
. It'll be convenient to position the Camera
near a specific planet. Since the planets are located at data-driven positions and will be moving in orbit, it's best to make the camera a child of the planet's transform. This is one of the reasons why we separated out the orbiting transform from the planet's transform. We don't want the camera to spin around with the planet—you might get sick! Here's the implementation:
void goToPlanet(int index){ RenderBox.mainCamera.getTransform().setParent( planets[index].getOrbitransform(), false); RenderBox.mainCamera.getTransform().setLocalPosition( planets[index].distance, planets[index].radius * 1.5f, planets[index].radius * 2f); }
Note that the scale and distance values we finally use in the code are derived from but not the actual celestial measurements. For a lovely VR experience of the Solar System with real educational value, check out Titans of Space (http://www.titansofspacevr.com/).
The
gotoPlanet
function is called with a planet index (for example, Earth is 2), so we can position the camera near the specified planet. The Camera
component gets parented to the planet's orbitTransform
variable as a way to obtain the planet's current orbit rotation. Then, it's positioned as the planet's distance from the Sun, and then offset a bit, relative to the planet's size.
In MainActivity
class's setup method, we have already set up the Sun and the Earth. We'll replace the Earth sphere with a call to a setupPlanets
helper method:
public void setup() { //Sun ... // Planets setupPlanets(origin); // Start looking at Earth goToPlanet(2); }
If you build and run the project now, you will see the Earth, the Sun, and maybe some of the planets. But not until they're moving in their orbits will they come to life.
Now that we have all the planets instantiated, we can animate their orbit and axis rotations. All it takes is updating their transforms in the MainAcitvity
class's preDraw
method:
@Override public void preDraw() { float dt = Time.getDeltaTime(); for(int i = 0; i < planets.length; i++){ planets[i].preDraw(dt); } }
Run! Oh, wow! I feel like a god. Well, not exactly, because it's dark outside. We need stars!