Skip to content

simple animation for rigid body movement for the webgl generated html file #535

@asmwarrior

Description

@asmwarrior

Hi, just followed the discussion in sourceforge forum, see here:

Asymptote / Discussion / Help: Animation the asymptote 3D output: Is it possible to hack the gl.js to make some object move or rotate in the html output?

I did some test, and what I need to find a way to tweak the transformation of each Geometry class instances.

Image

You can see the animation when I press the "Arrow up" and "Arrow down" key, so that the objects in the scene get shifted.

Here is the code/patch I used to modify the gs file, I'm not sure it is correct, so I hope the devs can help.

--- F:/code/asymptote/webgl/gl.js	Fri Mar 14 19:30:07 2025
+++ F:/code/asy-test/asygl.js	Sat Mar 15 19:47:39 2025
@@ -92,14 +92,16 @@
 
 let projViewMat=mat4.create(); // projection view matrix
 let normMat=mat3.create();
 let viewMat3=mat3.create(); // 3x3 view matrix
 let cjMatInv=mat4.create();
 let Temp=mat4.create();
 
+let gMotionMatrix = mat4.create(); // identity matrix
+
 let zmin,zmax;
 let center={x:0,y:0,z:0};
 let size2;
 let ArcballFactor;
 let shift={
   x:0,y:0
 };
@@ -450,14 +452,15 @@
                                     data.indicesBuffer,copy,
                                     gl.ELEMENT_ARRAY_BUFFER);
   data.rendered=true;
 
   gl.drawElements(normal ? (wireframe ? gl.LINES : data.type) : gl.POINTS,
                   indices.length,
                   indexExt ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT,0);
+  console.log("drawElements() function call: normal], wireframe], data.type], indices.length]", normal, wireframe, data.type, indices.length);
 }
 
 let TRIANGLES;
 
 class vertexBuffer {
   constructor(type) {
     this.type=type ? type : TRIANGLES;
@@ -579,14 +582,16 @@
 }
 
 class Geometry {
   constructor() {
     this.data=new vertexBuffer();
     this.Onscreen=false;
     this.m=[];
+      // Local transformation matrix (4x4) - initialized as identity matrix
+    this.localTransform = mat4.create();
   }
 
   // Is 2D bounding box formed by projecting 3d points in vector v offscreen?
   offscreen(v) {
     let m=projViewMat;
     let v0=v[0];
     let x=v0[0], y=v0[1], z=v0[2];
@@ -611,23 +616,35 @@
       this.Onscreen=false;
       return true;
     }
     return false;
   }
 
   T(v) {
-    let c0=this.c[0];
-    let c1=this.c[1];
-    let c2=this.c[2];
-    let x=v[0]-c0;
-    let y=v[1]-c1;
-    let z=v[2]-c2;
-    return [x*normMat[0]+y*normMat[3]+z*normMat[6]+c0,
-            x*normMat[1]+y*normMat[4]+z*normMat[7]+c1,
-            x*normMat[2]+y*normMat[5]+z*normMat[8]+c2];
+    let c0 = this.c[0];
+    let c1 = this.c[1];
+    let c2 = this.c[2];
+
+    // Translate vertex relative to the object center
+    let x = v[0] - c0;
+    let y = v[1] - c1;
+    let z = v[2] - c2;
+    let w = 1.0;  // Homogeneous coordinate
+
+    // Apply localTransform (4×4 matrix)
+    let lx = this.localTransform[0] * x + this.localTransform[4] * y + this.localTransform[8] * z  + this.localTransform[12] * w;
+    let ly = this.localTransform[1] * x + this.localTransform[5] * y + this.localTransform[9] * z  + this.localTransform[13] * w;
+    let lz = this.localTransform[2] * x + this.localTransform[6] * y + this.localTransform[10] * z + this.localTransform[14] * w;
+
+    // Apply normal transformation matrix
+    return [
+        lx * normMat[0] + ly * normMat[3] + lz * normMat[6] + c0,
+        lx * normMat[1] + ly * normMat[4] + lz * normMat[7] + c1,
+        lx * normMat[2] + ly * normMat[5] + lz * normMat[8] + c2
+    ];
   }
 
   Tcorners(m,M) {
     return [this.T(m),this.T([m[0],m[1],M[2]]),this.T([m[0],M[1],m[2]]),
             this.T([m[0],M[1],M[2]]),this.T([M[0],m[1],m[2]]),
             this.T([M[0],m[1],M[2]]),this.T([M[0],M[1],m[2]]),this.T(M)];
   }
@@ -663,18 +680,23 @@
     }
 
     let p=this.controlpoints;
     let P;
 
     if(this.CenterIndex == 0) {
       if(!remesh && this.Onscreen) { // Fully onscreen; no need to re-render
+        console.log("render(): calling this.append, remesh, this.Onscreen, but return early", remesh, this.Onscreen);
         this.append();
         return;
       }
-      P=p;
+      // modify every point's coordinates by local transform matrix
+      let n=p.length;
+      P=Array(n);
+      for(let i=0; i < n; ++i)
+        P[i]=this.localT(p[i]);
     } else { // Transform billboard labels
       let n=p.length;
       P=Array(n);
       for(let i=0; i < n; ++i)
         P[i]=this.T(p[i]);
     }
 
@@ -684,16 +706,34 @@
                    s*(viewParam.ymax-viewParam.ymin))/size2;
     this.res2=res*res;
     this.Epsilon=FillFactor*res;
 
     this.data.clear();
     this.notRendered();
     this.Onscreen=true;
+    console.log("render(): calling this.process(P), which copies data", P);
     this.process(P);
   }
+    // Function to modify local transformation matrix
+  setLocalTransform(matrix) {
+    this.localTransform = matrix;
+    // this.data.clear();  // Clear old data
+  }
+
+  localT(v) {
+    if (!this.localTransform) return v; // If no transformation, return original vertex
+
+    let x = v[0], y = v[1], z = v[2], w = 1.0;
+    // Apply the local transformation matrix (this.localTransform)
+    return [
+        this.localTransform[0] * x + this.localTransform[4] * y + this.localTransform[8] * z + this.localTransform[12] * w,
+        this.localTransform[1] * x + this.localTransform[5] * y + this.localTransform[9] * z + this.localTransform[13] * w,
+        this.localTransform[2] * x + this.localTransform[6] * y + this.localTransform[10] * z + this.localTransform[14] * w
+    ];
+  }
 }
 
 function boundPoints(p,m)
 {
   let b=p[0];
   let n=p.length;
   for(let i=1; i < n; ++i)
@@ -2830,14 +2870,16 @@
   if(W.embedded && zoomEnabled && event.keyCode == ESC) {
     disableZoom();
     return;
   }
 
   let keycode=event.key;
   let axis=[];
+  let translation = [0, 0, 0];  // Translation vector
+  let translMatrix = mat4.create();  // Create an empty 4x4 matrix
 
   switch(keycode) {
   case 'x':
     axis=[1,0,0];
     break;
   case 'y':
     axis=[0,1,0];
@@ -2868,14 +2910,34 @@
   case '_':
   case '<':
     shrink();
     break;
   case 'c':
     showCamera();
     break;
+  case 'ArrowUp':
+      translation = [0, 0, 5];
+      mat4.fromTranslation(translMatrix, translation);  // Fill translMatrix with the translation matrix
+      gMotionMatrix = translMatrix // Move up
+      console.log("up key arrow up, gMotionMatrix = ", gMotionMatrix);
+      remesh=true;
+      drawScene();
+      break;
+  case 'ArrowDown':
+      translation = [0, 0, -5];
+      mat4.fromTranslation(translMatrix, translation);  // Fill translMatrix with the translation matrix
+      gMotionMatrix = translMatrix // Move up
+      console.log("up key arrow down, gMotionMatrix = ", gMotionMatrix);
+      remesh=true;
+      drawScene();
+      break;
+  case 'ArrowLeft':
+      break;
+  case 'ArrowRight':
+      break;
   default:
     break;
   }
 
   if(axis.length > 0) {
     mat4.rotate(rotMat,rotMat,0.1,axis);
     updateViewMatrix();
@@ -3086,18 +3148,21 @@
     offscreen.width=W.canvasWidth;
     offscreen.height=W.canvasHeight;
     setViewport();
   }
 
   gl.clearColor(W.background[0],W.background[1],W.background[2],W.background[3]);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+  console.log("loop on all geometry P list");
+  for (let i = 0; i < P.length; i++) {
+    P[i].setLocalTransform(gMotionMatrix);
+    P[i].render();
+  }
 
-  for(const p of P)
-    p.render();
-
+  console.log("drawBuffers()");
   drawBuffers();
 
   if(W.embedded) {
     context.clearRect(0,0,W.canvasWidth,W.canvasHeight);
     context.drawImage(offscreen,0,0);
   }
 

The main change here is:

In the class Geometry, I added a new field named this.localTransform

So, the constructor becomes like below:

class Geometry {
  constructor() {
    this.data=new vertexBuffer();
    this.Onscreen=false;
    this.m=[];
      // Local transformation matrix (4x4) - initialized as identity matrix
    this.localTransform = mat4.create();
  }

In the event handler for the keyboard, I try to modify a global matrix variable:

  case 'ArrowUp':
      translation = [0, 0, 5];
      mat4.fromTranslation(translMatrix, translation);  // Fill translMatrix with the translation matrix
      gMotionMatrix = translMatrix // Move up
      console.log("up key arrow up, gMotionMatrix = ", gMotionMatrix);
      remesh=true;
      drawScene();
      break;
  case 'ArrowDown':
      translation = [0, 0, -5];
      mat4.fromTranslation(translMatrix, translation);  // Fill translMatrix with the translation matrix
      gMotionMatrix = translMatrix // Move up
      console.log("up key arrow down, gMotionMatrix = ", gMotionMatrix);
      remesh=true;
      drawScene();
      break;
  case 'ArrowLeft':
      break;
  case 'ArrowRight':
      break;

And when drawScene() get called inside the keyboard event handler, I try to update every object's localTransform matrix

function drawScene()
{
  if(W.embedded) {
    offscreen.width=W.canvasWidth;
    offscreen.height=W.canvasHeight;
    setViewport();
  }

  gl.clearColor(W.background[0],W.background[1],W.background[2],W.background[3]);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  console.log("loop on all geometry P list");
  for (let i = 0; i < P.length; i++) {
    P[i].setLocalTransform(gMotionMatrix);
    P[i].render();
  }
...

Now, you can see: P[i].setLocalTransform(gMotionMatrix); will copy the global matrix to the P[i], and later, inside the render() function call, I just re-calculate the position, like below:

  render() {
    this.setMaterialIndex();

    // First check if re-rendering is required
    let v;
    if(this.CenterIndex == 0)
      v=corners(this.Min,this.Max);
    else {
      this.c=W.Centers[this.CenterIndex-1];
      v=this.Tcorners(this.Min,this.Max);
    }

    if(this.offscreen(v)) { // Fully offscreen
      this.data.clear();
      this.notRendered();
      return;
    }

    let p=this.controlpoints;
    let P;

    if(this.CenterIndex == 0) {
      if(!remesh && this.Onscreen) { // Fully onscreen; no need to re-render
        console.log("render(): calling this.append, remesh, this.Onscreen, but return early", remesh, this.Onscreen);
        this.append();
        return;
      }
      // modify every point's coordinates by local transform matrix
      let n=p.length;
      P=Array(n);
      for(let i=0; i < n; ++i)
        P[i]=this.localT(p[i]);
    } else { // Transform billboard labels
      let n=p.length;
      P=Array(n);
      for(let i=0; i < n; ++i)
        P[i]=this.T(p[i]);
    }
...

So, here is the test asymptote code:

import three; // Import 3D drawing module

size(300);
//currentprojection = perspective(10, 10, 10);
currentprojection=
  perspective(camera=(0.26829265777219125,16.279223021307676,6.398560022438133),
              up=(0.00008257171178993584,-0.0013914064579275548,0.003844414421333313),
              target=(0.5042383,-0.004253902,0.5000156),
              zoom=0.5846792890864372,
              angle=6.953767995927329,
              autoadjust=false);


// Define triangle vertices
triple A = (0, 0, 0);
triple B = (0, 0, 1);
triple C = (1, 0, 0);
triple D = (1, 0, 1);

// Draw two triangles
draw(A--B, red);
draw(C--D, blue);

The issue here is:

In the event handler, I try to move the objects up and down, but it looks like for some unknown reasons I don't know, the objects dose not shifted as expected on the Z axis, not sure why.

In the future, I need to find a way to choose which geometry instances should move/rotate when I press the mouse. For example, if I have a robot arm to move, I need to know which pieces of the patches/triangles belongs to which robot links. so that I can assign a single/same mat4 matrix for those components, so that I can move the robot arms.

What do you think about the idea? Thanks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions