Source: frame-hw2.mjs

/**
 * @file
 *
 * Summary.
 * <p>A change of frame using {@link https://glmatrix.net gl-matrix}.</p>
 *
 * <p>To use ES6 modules in nodejs (version ≥ 13):</p>
 * <ol>
 *  <li> file extension should be ".mjs" </li>
 *  <li> add {"type": "module",} to package.json (located in current or parent directory)</li>
 *  <li> should be used ES6 module syntax: import and export </li>
 * </ol>
 *
 * <pre>
 *  Usage:
 *  - npm init
 *  - npm install gl-matrix
 *  - node frame-hw2.mjs
 * </pre>
 *
 * @author Paulo Roma
 * @since 03/02/2023
 *
 * @see https://techsparx.com/nodejs/esnext/dynamic-import-2.html
 * @see https://nodejs.medium.com/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663
 * @see <a href="/cwdc/10-html5css3/hw2/frame-hw2.mjs">source</a>
 * @see <a href="/cwdc/10-html5css3/hw2/hw2.pdf">doc</a>
 * @see <a href="/cwdc/13-webgl/examples/lighting/content/doc-lighting2/global.html#viewMatrix">view matrix</a>
 * @see <img src="../hw2.png" width="512">
 */

// nodejs aware
import { vec3, vec4, mat3, mat4 } from "gl-matrix";

/**
 * An IIFE (Immediately Invoked Function Expression)
 * for running the hw2 example of frame change.
 * @function
 * @global
 * @name main_IIFE
 */
(function main() {
  /**
   * Returns a string representation of a Float32Array,
   * formatted as a matrix, for printing to the console.
   * @param {vec2|vec3|mat3|mat4} m Float32array.
   * @return {String} m values arranged in rows and columns.
   * @global
   */
  function format(m) {
    if (m == undefined) {
      console.log();
      return;
    }

    let strMat = "";
    if (m.length <= 4) {
      strMat = "".concat(
        ...m.map((c) => `${c.toFixed(2).replace(/\.00$/, "")}  `),
      );
    } else if (m.length <= 16) {
      let inc = Math.sqrt(m.length);
      let st = "";
      for (let i = 0; i < inc; ++i) {
        for (let j = 0; j < m.length; j += inc) {
          st += `${m[i + j].toFixed(2).replace(/\.00$/, "")}  `;
        }
        strMat += `${st}\n`;
        st = "";
      }
    }
    return strMat.trim();
  }

  // -------------------------------------------

  var camera = mat3.create();
  var viewMatrix = mat3.create();

  /*
   * f (world space), f' (camera)
   *
   * f to f': f' = f * viewMatrix
   * world space to camera
   * viewMatrix = Translate(11, 4) * RotateZ(90) * Scale(1, 2)
   * T(1, 0) = (0,1)
   * T(0, 1) = (-2,0)
   *
   * viewMatrix
   *   0.0 -2.0 11.0
   *   1.0 0.0 4.0
   *   0.0 0.0 1.0
   *
   *  P' = 11.0 9.0 1.0
   *  Q' = 11.0 11.0 1.0
   *  R' = 7.0 11.0 1.0
   *  O' = 0 0 1.0
   */

  var pnts = {
    R: [5, 0, 1],
    P: [7, 0, 1],
    Q: [7, 2, 1],
    V: [-4, 11 / 2, 1],
  };
  var s = mat3.fromScaling([], [1, 2]);
  var r = mat3.fromRotation([], Math.PI / 2);
  var t = mat3.fromTranslation([], [11, 4]);
  mat3.multiply(viewMatrix, r, s);
  mat3.multiply(viewMatrix, t, viewMatrix);

  console.log("viewMatrix");
  console.log(format(viewMatrix));
  console.log();

  let R_ = vec3.transformMat3([], pnts.R, viewMatrix);
  let P_ = vec3.transformMat3([], pnts.P, viewMatrix);
  let Q_ = vec3.transformMat3([], pnts.Q, viewMatrix);
  // camera position in world space
  let O_ = vec3.transformMat3([], pnts.V, viewMatrix);

  console.log(`R → R': ${format(pnts.R)} → ${format(R_)}`);
  console.log(`P → P': ${format(pnts.P)} → ${format(P_)}`);
  console.log(`Q → Q': ${format(pnts.Q)} → ${format(Q_)}`);
  console.log(`V → O': ${format(pnts.V)} → ${format(O_)}`);
  console.log();

  /*
   * f' to f: f = f' * camera
   * camera to world space
   * camera = mat3.invert([], viewMatrix); - camera is not ortho-normal (there is a scale)
   * camera = Scale(1, 1/2) * RotateZ(-90) * Translate(-11, -4)
   * T(1, 0) = (0, -1/2)
   * T(0, 1) = (1, 0)
   *
   * camera
   *    0.0 1.0 -4.0
   *    -0.5 0.0 5.5
   *    0.0 0.0 1.0
   *
   *  P = 5.0 0.0 1.0
   *  Q = 7.0 0.0 1.0
   *  R = 7.0 2.0 1.0
   *  V = -4.0 5.5 1.0
   *
   * s = mat3.fromScaling([], [1, 1 / 2]);
   * r = mat3.fromRotation([], -Math.PI / 2);
   * t = mat3.fromTranslation([], [-11, -4]);
   * mat3.multiply(camera, r, t);
   * mat3.multiply(camera, s, camera);
   */

  mat3.invert(camera, viewMatrix);

  console.log("camera");
  console.log(format(camera));
  console.log();

  let R = vec3.transformMat3([], R_, camera);
  let P = vec3.transformMat3([], P_, camera);
  let Q = vec3.transformMat3([], Q_, camera);
  let V = vec3.transformMat3([], O_, camera);

  console.log(`R' → R: ${format(R_)} → ${format(R)}`);
  console.log(`P' → P: ${format(P_)} → ${format(P)}`);
  console.log(`Q' → Q: ${format(Q_)} → ${format(Q)}`);
  console.log(`O' → V: ${format(O_)} → ${format(V)}`);
  console.log();

  // -------------------------------------------

  camera = mat4.create();
  viewMatrix = mat4.create();

  /**
   * view = translate(0, 0, -5) * rotate(45, 1, 0, 0) * rotate(-30, 0, 1, 0)
   * world space to camera
   */

  pnts = {
    R: [5, 0, 0, 1],
    P: [7, 0, 0, 1],
    Q: [7, 2, 0, 1],
    V: [1.77, 3.54, 3.06, 1], // eye
  };

  var s = mat4.fromScaling([], [1, 1, 1]);
  var ry = mat4.fromYRotation([], (-30 * Math.PI) / 180);
  var rx = mat4.fromXRotation([], (45 * Math.PI) / 180);
  var t = mat4.fromTranslation([], [0, 0, -5]);

  mat4.multiply(viewMatrix, ry, s);
  mat4.multiply(viewMatrix, rx, viewMatrix);
  mat4.multiply(viewMatrix, t, viewMatrix);
  console.log("viewMatrix");
  console.log(format(viewMatrix));
  console.log();

  console.log("viewMatrix - lookAt");
  console.log(format(mat4.lookAt([], pnts.V, [0, 0, 0], [0, 1, 0])));
  console.log();

  R_ = vec4.transformMat4([], pnts.R, viewMatrix);
  P_ = vec4.transformMat4([], pnts.P, viewMatrix);
  Q_ = vec4.transformMat4([], pnts.Q, viewMatrix);
  O_ = vec4.transformMat4([], pnts.V, viewMatrix);

  console.log(`R → R': ${format(pnts.R)} → ${format(R_)}`);
  console.log(`P → P': ${format(pnts.P)} → ${format(P_)}`);
  console.log(`Q → Q': ${format(pnts.Q)} → ${format(Q_)}`);
  console.log(`V → O': ${format(pnts.V)} → ${format(O_)}`);
  console.log();

  /**
   * camera = rotate(30, 0, 1, 0) * rotate(-45, 1, 0, 0) * translate(0, 0, 5)
   * camera to world space
   *
   * s = mat4.fromScaling([], [1, 1, 1]);
   * ry = mat4.fromYRotation([], (30 * Math.PI) / 180);
   * rx = mat4.fromXRotation([], (-45 * Math.PI) / 180);
   * t = mat4.fromTranslation([], [0, 0, 5]);
   * mat4.multiply(camera, rx, t);
   * mat4.multiply(camera, ry, camera);
   * mat4.multiply(camera, s, camera);
   */

  mat4.invert(camera, viewMatrix);

  console.log("camera");
  console.log(format(camera));
  console.log();

  R = vec4.transformMat4([], R_, camera);
  P = vec4.transformMat4([], P_, camera);
  Q = vec4.transformMat4([], Q_, camera);
  V = vec4.transformMat4([], O_, camera);

  console.log(`R' → R: ${format(R_)} → ${format(R)}`);
  console.log(`P' → P: ${format(P_)} → ${format(P)}`);
  console.log(`Q' → Q: ${format(Q_)} → ${format(Q)}`);
  console.log(`O' → V: ${format(O_)} → ${format(V)}`);
  console.log();
})();