Wednesday, January 16, 2013

The Earth and the Moon in Three.js

‹prev | My Chain | next›

With deadlines looming, it is high time to get back to Gaming JavaScript (actually it looks as though it will be called 3D Game Programming for Kids). I am bit out of practice, so I pick back up with a variation of an earlier exercise in the book. Instead of simulating planetary motion, I am going to have kids simulate the Moon's revolution around the Earth.

I copy the Mars simulation to a "Moon Phases" project and start by renaming the mars variable to moon. The Moon's position over the course of time is going to be a circle around the Earth's position in the Solar System. So, in the Three.js animate() function, I express this with simple sines and cosines:
  function animate() {
    requestAnimationFrame(animate);
    var t = clock.getElapsedTime();

    /* set the earth position first */   

    // Now set the moon
    var m_angle = t * 0.2;
    moon.position.set(
      100* Math.cos(m_angle) + earth.position.x, 
      100* Math.sin(m_angle) + earth.position.y, 
      0
    );

    /* position various cameras */
        
    // Now, show what the camera sees on the screen:
    renderer.render(scene, camera);
  }
  
  animate();
That actually does the trick. I now have the Earth revolving around the Sun and the Moon revolving around the Earth:


And, thanks to the camera controls that I added to the Mars simulator, I can switch back and forth between above-the-solar-system view and from-the-earth view:


As can be seen, a waning crescent Moon is what you get when the Moon is almost in between the Earth and the Sun. Only it's a little hard to see in the simulator because I lack a pause button. So I add one (the "P" key), along with speed controls (1, 2, and 3):
  document.addEventListener("keydown", function(event) {
    var code = event.which || event.keyCode;

    if (code == 65) camera = above_cam; // A
    if (code == 69) camera = earth_cam; // E
    if (code == 77) camera = earth_cam; // M
    if (code == 80) pause = !pause; // P
    if (code == 49) speed = 1; // 1
    if (code == 50) speed = 2; // 2
    if (code == 51) speed = 10; // 3
    event.preventDefault();
    return false;
  });
To make those work, particularly the pause button, I need to drop my reliance on the Three.js clock time to calculate the Earth's position in it orbit:
  function animate() {
    requestAnimationFrame(animate);
    
    var t = clock.getElapsedTime();
    
    var e_angle = t * 0.8;
    earth.position.set(250* Math.cos(e_angle), 250* Math.sin(e_angle), 0);
    // ...
  }
The problem here is that the Three.js clock keeps ticking even if the pause button is pressed. Then, when the pause button is unpressed, the Earth and Moon jump way ahead in their orbit.

So instead, I introduce an independent time variable:
  var time = 0,
      speed = 1,
      pause = false;
  function animate() {
    requestAnimationFrame(animate);

    if (pause) return;
    time = time + speed;
    var e_angle = time * 0.001;
    earth.position.set(350* Math.cos(e_angle), 350* Math.sin(e_angle), 0);
    // ...
  }
This new time tracking variable is only updated if the pause button has not been pressed. And, if it is updated, it is increased by the current speed.

The end result seems pretty solid and can be seen in the ICE editor. It is 110 lines and mostly based on the previous solar system code, so kids ought to be able to handle it.

Tomorrow, I may give it a shot positioning the Moon relative to the Earth's frame of reference rather than offsetting it from the Earth's position. If I recall correctly, there was difficulty orienting the camera when doing this. Then again it has been a while so it's definitely worth revisiting.


Day #632

No comments:

Post a Comment