Search code examples
webglwebgl2

Making hierarchical transformation in WebGL


I am making the solar system simulation which simply contains the sun(point light source), the earth and the moon, I can see succesfully these stars on the screen, when I only apply translations to them

Here is the example screen

Example 1

but as soon as I added the orbit transformaion to the earth, I got this weird result...

Example 2

It seems that the earth sunddenly stuck in the sun but, at least I can see that somehow the moon is revolving the earth

When I applyed the orbit transformation to the moon also, I got this result..

Example 3

As you can see that both the earth and the moon stuck in the sun...

Here is the code and the tree how I want their hierarchical transformation to be organized..

function RenderPerSec(gl, sun, SunModelMatrix, SunNormalMat, 
  earth, EarthModelMatrix, EarthNormalMat,
  moon, MoonModelMatrix, MoonNormalMat, 
  MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix, 
  loc_uSunTrigger, loc_uEarthTrigger, loc_uMoonTrigger,
  w, h)
{
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


  ///Hierarchical Transformation///


  /*
                     P
                     |
                     V
                     |
                 SUN(Fixed)
                     |
                   EARTH
          Translation from the sun                      
           Rotation around the sun 
              /      |        \ 
tilted 23.5 degree  MOON     Rotation itself
 Along the X-axis    |
         Translation from the earth
          Rotation around the earth
              Rotation itself..

*/

  let MVPStack = [];
  MvpMatrix.setIdentity();


  //Projection
  MvpMatrix.setPerspective(60, w/h, 1, 1000);
  //View
  MvpMatrix.lookAt(0, 15, 10, 0, 0, 0, 0, 1, 0);

  //Model Transforms...

  //Sun

  SunModelMatrix.setTranslate(0, 0, 0);

  MvpMatrix.multiply(SunModelMatrix);

  MVPStack.push(new Matrix4(MvpMatrix));


  gl.uniform1i(loc_uSunTrigger, 1);
  gl.uniform1i(loc_uEarthTrigger, 0);
  gl.uniform1i(loc_uMoonTrigger, 0);

  RenderSun(gl, sun, SunModelMatrix, SunNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix);

  //Earth, translation and rotation 23.5 along the X-axis, rotation itself
  EarthModelMatrix.setTranslate(5, 0, 0); //Translation from the sun
  EarthModelMatrix.setRotate(CURRENT_EATHR_REVOLVING_ANGLE, 0, 1, 0); //Revolving around the sun

  MvpMatrix = MVPStack.pop();
  MvpMatrix.multiply(EarthModelMatrix);

  MVPStack.push(new Matrix4(MvpMatrix));

  gl.uniform1i(loc_uSunTrigger, 0);
  gl.uniform1i(loc_uEarthTrigger, 1);
  gl.uniform1i(loc_uMoonTrigger, 0);

  RenderEarth(gl, earth, EarthModelMatrix, EarthNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix);


  //Moon, translation and rotation itself
  MoonModelMatrix.setTranslate(2.5, 0, 0);
  MoonModelMatrix.setRotate(CURRENT_MOON_REVOLVING_ANGLE, 0, 1, 0);

  MvpMatrix = MVPStack.pop();
  MvpMatrix.multiply(MoonModelMatrix, EarthModelMatrix);

  gl.uniform1i(loc_uSunTrigger, 0);
  gl.uniform1i(loc_uEarthTrigger, 0);
  gl.uniform1i(loc_uMoonTrigger, 1);

  RenderMoon(gl, moon, MoonModelMatrix, MoonNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix);
}

if I remove setRotate functions in this code I can get the first result..

and here is the render function

function RenderSun(gl, sun, SunModelMatrix, SunNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix)
{
  gl.bindVertexArray(sun.vao);


  SunNormalMat.setInverseOf(SunModelMatrix);
  SunNormalMat.transpose();

  // Pass the model matrix to uModelMatrix
  gl.uniformMatrix4fv(loc_uModelMatrix, false, SunModelMatrix.elements);

  // Pass the model view projection matrix to umvpMatrix
  gl.uniformMatrix4fv(loc_uMvpMatrix, false, MvpMatrix.elements);

  // Pass the transformation matrix for normals to uNormalMatrix
  gl.uniformMatrix4fv(loc_uNormalMatrix, false, SunNormalMat.elements);

  gl.drawElements(gl.TRIANGLES, sun.n, sun.type, 0);
}

function RenderEarth(gl, earth, EarthModelMatrix, EarthNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix)
{
  gl.bindVertexArray(earth.vao);

  EarthNormalMat.setInverseOf(EarthModelMatrix);
  EarthNormalMat.transpose();

  gl.uniformMatrix4fv(loc_uModelMatrix, false, EarthModelMatrix.elements);

  gl.uniformMatrix4fv(loc_uMvpMatrix, false, MvpMatrix.elements);

  gl.uniformMatrix4fv(loc_uNormalMatrix, false, EarthNormalMat.elements);

  gl.drawElements(gl.TRIANGLES, earth.n, earth.type, 0);
}

function RenderMoon(gl, moon, MoonModelMatrix, MoonNormalMat, MvpMatrix, loc_uModelMatrix, loc_uNormalMatrix, loc_uMvpMatrix)
{
  gl.bindVertexArray(moon.vao);

  MoonNormalMat.setInverseOf(MoonModelMatrix);
  MoonNormalMat.transpose();

  gl.uniformMatrix4fv(loc_uModelMatrix, false, MoonModelMatrix.elements);

  gl.uniformMatrix4fv(loc_uMvpMatrix, false, MvpMatrix.elements);

  gl.uniformMatrix4fv(loc_uNormalMatrix, false, MoonNormalMat.elements);

  gl.drawElements(gl.TRIANGLES, moon.n, moon.type, 0);
}

I am using a stack to implement the hierarchical transformations... could somebody help me?


Solution

  • There's an article on this here and stack based one here

    In general you would need to do something like

    push(0,0,0)
      push(sun rotation)
        draw sun
      pop()  // sun rotation
      push(earth orbit rotation)
        push(earth translation)
          push(earth rotation)
            draw earth
          pop() // earth rotation
          push(moon orbit rotation)
            push(moon translation)
              push(moon rotation)
                draw moon
              pop()  // moon rotation
            pop()  // moon translation
          pop()  // moon orbit rotation
        pop()  // earth translation
      pop()  // earth orbit rotation
    pop()  // center of solar system
    

    Your matrix math library is not familar to me but functions named setTranslate and setRotate do not sound like the appropriate functions. They sound like they set translation or set the rotation rather than multiply an existing matrix by a translation matrix or a rotation matrix.

    In most 3D math libraries to make a sphere appear at some distance from the center of rotation it would be something like

      mathlib.translate(centerOfRotation)  // move 0,0,0 to center of rotation
      mathlib.rotate(orbitRotation)        // rotate space
      mathlib.translate(orbitRadius)       // move some distance from center in rotated space
    

    Or a longer form

      mat = identity();
      mat = mathlib.multiply(mat, mathlib.translation(centerOfRotation))
      mat = mathlib.multiply(mat, mathlib.rotatation(orbitRotation))
      mat = mathlib.multiply(mat, mathlib.translation(orbitRadius))
    

    If it's a auto stacking library then the shorter form would have already stacked 3 matrices.

    Note since the only part you seem to be having issues with is the matrix stack part here's some code using a matrix stack to compute the 3 matrices (sun, earth, moon) and then it uses those matrices with 2D canvas (which you can ignore)

    'use strict';
    const ctx = document.querySelector('canvas').getContext('2d');
    const m4 = twgl.m4;
    
    const stack = [];
    const current = () => stack[stack.length - 1];
    const pop = () => stack.pop();
    const identity = () => stack.push(m4.identity());
    const translate = (t) => stack.push(m4.translate(current(), t));
    const rotate = (axis, r) => stack.push(m4.axisRotate(current(), axis, r));
    const zAxis = [0, 0, 1];
    
    function render(time) {
      time *= 0.001; // convert to seconds
      
      twgl.resizeCanvasToDisplaySize(ctx.canvas);
      const {width, height} = ctx.canvas;
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, width, height);
      
      let sunMat;
      let earthMat;
      let moonMat;
      
      identity();
        // translate to center of solar system
        translate([width / 2, height / 2, 0]);
          rotate(zAxis, time * 4);  // sun rotation
            sunMat = current();
          pop();  // sun rotation
      
          rotate(zAxis, time / 2) // push(earth orbit rotation)
            translate([height / 4, 0, 0]); //push(earth translation)
              rotate(zAxis, time * 8); // push(earth rotation)
                earthMat = current();  //
              pop(); // earth rotation
              rotate(zAxis, time * 2); //push(moon orbit rotation)
                translate([height / 8, 0, 0]); // push(moon translation)
                  rotate(zAxis, time * 16); //push(moon rotation)
                    moonMat = current(); 
                  pop();  // moon rotation
                pop();  // moon translation
              pop();  // moon orbit rotation
            pop();  // earth translation
          pop();  // earth orbit rotation
        pop();  // center of solar system  
      pop();  // identity
      
      drawSquare(sunMat, 40, 'orange');
      drawSquare(earthMat, 20, 'blue');
      drawSquare(moonMat, 10, 'gray');
    
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    function drawSquare(mat, size, color) {
      ctx.setTransform(mat[0], mat[1], mat[4], mat[5], mat[12], mat[13]);
      ctx.strokeStyle = color;
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
    body { margin: 0; }
    canvas { width: 100vw; height: 100vh; display: block; }
    <script src="https://twgljs.org/dist/4.x/twgl-full.js"></script>
    <canvas></canvas>

    here's one with explicit push

    'use strict';
    const ctx = document.querySelector('canvas').getContext('2d');
    const m4 = twgl.m4;
    
    const stack = [m4.identity()];
    const current = () => stack[stack.length - 1];
    const currentCopy = () => stack[stack.length - 1].slice();
    const push = () => stack.push(currentCopy());
    const pop = () => stack.pop();
    const identity = () => m4.identity(current());
    const translate = (t) => m4.translate(current(), t, current());
    const rotate = (axis, r) => m4.axisRotate(current(), axis, r, current());
    const zAxis = [0, 0, 1];
    
    function render(time) {
      time *= 0.001; // convert to seconds
      
      twgl.resizeCanvasToDisplaySize(ctx.canvas);
      const {width, height} = ctx.canvas;
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, width, height);
      
      let sunMat;
      let earthMat;
      let moonMat;
      
      push();
      // translate to center of solar system
      translate([width / 2, height / 2, 0]);
        push();
          rotate(zAxis, time * 4);  // sun rotation
          sunMat = currentCopy();
        pop();  // sun rotation
    
        rotate(zAxis, time / 2) // earth orbit rotation
        translate([height / 4, 0, 0]); // earth translation
        
        push();
          rotate(zAxis, time * 8); // earth rotation
          earthMat = currentCopy();  //
        pop(); // earth rotation
     
        push();
          rotate(zAxis, time * 2); // moon orbit rotation
          translate([height / 8, 0, 0]); // moon translation
          rotate(zAxis, time * 16); // moon rotation
          moonMat = currentCopy(); 
        pop(); 
      pop();  
      
      drawSquare(sunMat, 40, 'orange');
      drawSquare(earthMat, 20, 'blue');
      drawSquare(moonMat, 10, 'gray');
    
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    function drawSquare(mat, size, color) {
      ctx.setTransform(mat[0], mat[1], mat[4], mat[5], mat[12], mat[13]);
      ctx.strokeStyle = color;
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
    body { margin: 0; }
    canvas { width: 100vw; height: 100vh; display: block; }
    <script src="https://twgljs.org/dist/4.x/twgl-full.js"></script>
    <canvas></canvas>