Animation with WebGL and Three.js (5)

omega01pi

Please click the image for real-time animation.

Yet another simple case is when I1=I2≠I3. Again from Landau and Lifshitz:

eq36-6

Here is a nice figure from the MIT OpenCourseWare Course.

https://ocw.mit.edu/courses/aeronautics-and-astronautics/16-07-dynamics-fall-2009/lecture-notes/MIT16_07F09_Lec29.pdf

peraire

The simulation parameters are:

Ibody.set(1,0,0, 0,1,0, 0,0,2);
L.set(00, 01, 2.0*Math.PI);

The value of ω in equation (36.6), therefore, becomes pi, while A (=√ Ω1^2 + Ω2^2) becomes 1.0. See the curves of, wx, wy, wz in the graph.

Animation with WebGL and Three.js (4)

omega123

Let’s see with some of the special cases. Again from Landau and Lifshitz:

eq36-5

Obviously, the simplest case occurs when I1=I2=I3, in which case Omega becomes a constant vector, as is shown in the figure. The parameters employed are:

Ibody.set(1,0,0, 0,1,0, 0,0,1);
L.set(1, 2, 3);

If we let

L.set(1, 1, 1);

we will get:

omega111

Note that the scale of the y-axis is largely different in two figures.

Animation with WebGL and Three.js (3)

stable

Please click the image for real-time animation.

The parameters are:

Ibody.set(1,0,0, 0,1,0, 0,0,2);
L.set(2.0*Math.PI*0.0,2.0*Math.PI*0.0,2.0*Math.PI*5.0);
var dt = 1.0/1000.0;

So the body should rotate 2.5 times per second, and this is what is happening in the simulation. The graph shows the components of the unit quaternion, which rotates only 1.25 times per second.

euler

In the real-time animation, the body rotates rather slowly, but it is because the frame rate is only 30fps.

Animation with WebGL and Three.js (2)

monolith3

Please click either an unstable case or a stable case.

The source code may include some errors, bat basically it seems to be OK. I am not sure if I really have to write functions matrix4_from_matrix3() and matrix3_from_matrix4().

Please check the following documents, if you are in doubt.

https://threejs.org/docs/api/math/Vector3.html
https://threejs.org/docs/api/math/Quaternion.html
https://threejs.org/docs/api/math/Matrix3.html
https://threejs.org/docs/api/math/Matrix4.html

<html>
	<head>
		<title>My first Three.js app</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
	</head>
	<body>
		<script src="https://ajax.googleapis.com/ajax/libs/threejs/r76/three.min.js"></script>
		<script>
			function matrix4_from_matrix3 (m4, m3) {
				var se = m3.elements;
				var te = m4.elements;
				te[0] = se[0]; te[4] = se[3]; te[ 8] = se[6]; te[12] = 0;
				te[1] = se[1]; te[5] = se[4]; te[ 9] = se[7]; te[13] = 0;
				te[2] = se[2]; te[6] = se[5]; te[10] = se[8]; te[14] = 0;
				te[3] = 0;     te[7] =     0; te[11] =     0; te[15] = 0;
			}
			function matrix3_from_matrix4 (m3, m4) {
				var se = m4.elements;
				var te = m3.elements;
				te[0] = se[0]; te[3] = se[4]; te[6] = se[ 8];
				te[1] = se[1]; te[4] = se[5]; te[7] = se[ 9];
				te[2] = se[2]; te[5] = se[6]; te[8] = se[10];
			}

			var scene = new THREE.Scene();
			var camera = new THREE.PerspectiveCamera(7, window.innerWidth/window.innerHeight, 0.1, 1000 );

			var renderer = new THREE.WebGLRenderer();
			renderer.setSize( window.innerWidth, window.innerHeight );

			document.body.appendChild( renderer.domElement );

			var geometry = new THREE.BoxGeometry( 4, 9, 1 );

			var material2= new THREE.MeshLambertMaterial( { color: 0xf01010 } );
			var cube= new THREE.Mesh( geometry, material2 );
			cube.position.x = 0.0;
			scene.add( cube );


			var geometry0= new THREE.BoxGeometry( 1000, 1000, 1);
			var material0 = new THREE.MeshBasicMaterial( { color: 0x101040 } );
			var cube0 = new THREE.Mesh( geometry0, material0 );
			cube0.position.z = -10;
			scene.add( cube0 );

			camera.position.x = 100;
			camera.position.y = 100;
			camera.position.z = 250;
			camera.lookAt(cube.position);
			scene.add( camera );

			var light = new THREE.DirectionalLight(0xffffff);
			light.position.set(250, 250, 250);
			scene.add( light );
			var light0 = new THREE.AmbientLight( 0x404040 );
			scene.add( light0  );

			var Ibody     = new THREE.Matrix3();
			var IbodyInv  = new THREE.Matrix3();
			var IbodyInv4 = new THREE.Matrix4();
			Ibody.set(82,0,0, 0,17,0, 0,0,97);
			IbodyInv.getInverse(Ibody);
			matrix4_from_matrix3 (IbodyInv4, IbodyInv);

			var q    = new THREE.Quaternion();
			var qq   = new THREE.Quaternion();
			var qdot = new THREE.Quaternion();
			var axis = new THREE.Vector3(0,1,0).normalize();
			var angle = Math.PI * 0.0;
			q.setFromAxisAngle(axis,angle);
			var x, y, z, w;

			var L  = new THREE.Vector3();
			var L4 = new THREE.Vector4();
			L.set(13.0*2.0*Math.PI*82.0,2.0*Math.PI*0.1,2.0*Math.PI*0.1);
			L4.set(L.x, L.y, L.z, 0);

			var omega = new THREE.Vector3();
			var R     = new THREE.Matrix3();
			var RInv  = new THREE.Matrix3();
			var IInv  = new THREE.Matrix3();

			var R4    = new THREE.Matrix4();
			var RInv4 = new THREE.Matrix4();
			var IInv4 = new THREE.Matrix4();

			var dt = 0.001;

			var render = function () {
				requestAnimationFrame( render );
				x = q.x; y = q.y; z = q.z; w = q.w;
				R.set(1.0-2.0*y*y-2.0*z*z, 2.0*x*y+2.0*w*z, 2.0*x*z-2.0*w*y,
				      2.0*x*y-2.0*w*z, 1.0-2.0*x*x-2.0*z*z, 2.0*y*z+2.0*w*x,
				      2.0*x*z+2.0*w*y, 2.0*y*z-2.0*w*x, 1.0-2.0*x*x-2.0*y*y);
				RInv.getInverse(R);
				matrix4_from_matrix3 (R4   , R   );
				matrix4_from_matrix3 (RInv4, RInv);
//				IInv = R * IbodyInv * RInv;
				IInv4.multiplyMatrices(R4, IbodyInv4);
				IInv4.multiply        (RInv4);
				matrix3_from_matrix4 (IInv, IInv4);
//				omega = IInv * L;
				omega.copy(L);
				omega.applyMatrix3(IInv);
				qq.set(omega.x/2.0, omega.y/2.0, omega.z/2.0, 0.0);
//				qdot = qq * q;
				qdot.multiplyQuaternions(qq, q);
//				q += qdot * dt;
				q.set(q.x+qdot.x*dt, q.y+qdot.y*dt, q.z+qdot.z*dt, q.w+qdot.w*dt);
				q.normalize();
				cube.rotation.setFromQuaternion(q);
				renderer.render(scene, camera);
			}

			render();
		</script>
	</body>
</html>

Numerical stability

q111

Apart from the mechanical stability, we must also be concerned with numerical stability.

This is especially true in our case, because, right now, a simple Euler method is employed in integrating the quaternion that represent the rotation of the body.

        Quaterniond qq(0.0, omega.x()/2.0, omega.y()/2.0, omega.z()/2.0);
        qdot =  qq * q;
        q.w()   += qdot.w()   * dt;
        q.vec() += qdot.vec() * dt;
        q.normalize();

The figure shows the time evolution of the four components of the quaternion, q with the initial conditions of:

    Ibody << 1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0;
    Quaterniond q(AngleAxisd(M_PI*0.0, Vector3d::UnitX()));
    q.normalize();
    L << 0.00, 0.00, 2.0*M_PI;

Numerically, they are:

double dt = 0.001;
for(double t=0; t<4.0; t+= dt) {

     1   1         0   0   0
     2   0.999995  0   0   0.00314158
     3   0.99998   0   0   0.00628312

  3999   0.99998   0   0  -0.00632446
  4000   0.999995  0   0  -0.00318292
  4001   1         0   0  -4.13415e-05

Note that the quaternion, q, rotates only once in two seconds, while the unit X vector, fixed to the body, rotates once in very second.

x111

Now, we do 40sec simulation instead of 4sec.

double dt = 0.001;
for(double t=0; t<40.0; t+= dt) {

q111_40sec

So can we say that it is reasonably stable?

Stable or Unstable

stable1

Let’s see if your monolith tumbles or not in various situations.

The figure shows the tip of the Vector3d::UnitZ() fixed to the body. You can see that the vector is always up and the rotation is stable.

The initial conditions are:

    Ibody << 3.0,0.0,0.0, 0.0,4.0,0.0, 0.0,0.0,5.0;
    Quaterniond q(AngleAxisd(M_PI*0.03, Vector3d::UnitX()));
    L << 0.00, 0.00, 1.00;

Note that Ix < Iy < Iz, and therefore the rotation around the z-axis should be stable. The body is slightly rotated around the x-axis to give better visual impression. The angular momentum, L is given, and this vector does not change because there is no external force nor torque.

stable2

    Ibody &lt;&lt; 3.0,0.0,0.0, 0.0,5.0,0.0, 0.0,0.0,4.0;

Now, Ix < Iz < Iy, and the rotation around the z-axis becomes unstable.

A monolith tumbling in space

2010

Do you remember TMA-2 orbiting Jupiter? A rectangular cuboid whose sides extend in the ratio of 1 : 4 : 9 has the principal moments of the ratio 1^2+4^2:4^2+9^2:9^2+1^2, or 17:97:82.

An object whose three principal moments are all different is called an asymmetrical top. According to the tennis racket theorem, or as is described in Landau and Lifshitz, the rotation around axis with moment of inertia I2 is unstable, while the ones with I1 and I3 are both stable, assuming that I1 > I2 > I3.

By the way, I was wrong in writing Ibody=(1,2,4) in my previous article, because this can never happen. Again from Landau and Lifshitz:

noneof

Eigen and quaternion (2)

case1

Looks reasonable? Note the values of Ibody (line 19) and L (line 26) in the code.

#include &lt;iostream&gt;
#include &lt;Eigen/Dense&gt;
#include &lt;Eigen/Core&gt;
#include &lt;Eigen/Geometry&gt;
using namespace std;
using namespace Eigen;

Matrix3d    Ibody;
Matrix3d    Ibodyinv;
Quaterniond q;
Quaterniond qdot;
Vector3d    L;
Matrix3d    Iinv;
Matrix3d    R;
Vector3d    omega;

int main(int argc, const char * argv[]) {

    Ibody &lt;&lt; 1.0,0.0,0.0, 0.0,2.0,0.0, 0.0,0.0,4.0;
    Ibodyinv = Ibody.inverse();
    cout &lt;&lt; &quot;Ibody: &quot;    &lt;&lt; endl &lt;&lt; Ibody    &lt;&lt; endl;
    cout &lt;&lt; &quot;Ibodyinv: &quot; &lt;&lt; endl &lt;&lt; Ibodyinv &lt;&lt; endl;

    Quaterniond q(AngleAxisd(M_PI*0.0, Vector3d::UnitZ()));
    q.normalize();
    L &lt;&lt; 0.00, 0.00, 1.00;
    
    
    double dt = 0.1;
    
    for(double t=0; t&lt;200.0; t+= dt) {
        R     = q.toRotationMatrix();
        Iinv  = R * Ibodyinv * R.transpose();
        omega = Iinv * L;
        
        Quaterniond qq(0.0, omega.x()/2.0, omega.y()/2.0, omega.z()/2.0);
        qdot =  qq * q;
        
        cout &lt;&lt; &quot;t    : &quot; &lt;&lt; t &lt;&lt; endl;
        cout &lt;&lt; &quot;q    : &quot; &lt;&lt; q.w() &lt;&lt; &quot; &quot; &lt;&lt; q.vec().transpose() &lt;&lt; endl;
        cout &lt;&lt; &quot;L    : &quot; &lt;&lt; L.transpose() &lt;&lt; endl;
        cout &lt;&lt; &quot;R    : &quot; &lt;&lt; endl &lt;&lt; R &lt;&lt; endl;
        cout &lt;&lt; &quot;Iinv : &quot; &lt;&lt; endl &lt;&lt; Iinv &lt;&lt; endl;
        cout &lt;&lt; &quot;omega: &quot; &lt;&lt; omega.transpose() &lt;&lt; endl;
        cout &lt;&lt; &quot;qdot : &quot; &lt;&lt; qdot.w() &lt;&lt; &quot; &quot; &lt;&lt; qdot.vec().transpose() &lt;&lt; endl;

        q.w()   += qdot.w()   * dt;
        q.vec() += qdot.vec() * dt;
        q.normalize();
    }
    
    return 0;
}
% ./MyEigenTest1 | grep &quot;q    :&quot; | colrm 1 6 | cat -n &gt; t
% ./MyEigenTest1 | grep &quot;omega:&quot; | colrm 1 6 | cat -n &gt; u
% gnuplot
gnuplot&gt; plot &quot;t&quot; using 1:2, &quot;t&quot; using 1:5, &quot;u&quot; using 1:2, &quot;u&quot; using 1:3, &quot;u&quot; using 1:4

case3

Now both Ibody and L are modified slightly.

    Ibody &lt;&lt; 1.0,0.0,0.0, 0.0,4.0,0.0, 0.0,0.0,2.0;
    L &lt;&lt; 0.01, 0.00, 1.00;

case4

The solution seems to be stable.

Eigen and quaternion

eigen

Eigen is a C++ template library for linear algebra including the manipulation of quaternion.

#include &lt;iostream&gt;
#include &lt;Eigen/Dense&gt;
#include &lt;Eigen/Core&gt;
#include &lt;Eigen/Geometry&gt;
using namespace std;
using namespace Eigen;

int main(int argc, const char * argv[]) {
    Matrix3f A, B;
    A &lt;&lt; 1,0,0, 0,2,0, 0,0,4;
    B = A.inverse();
    cout &lt;&lt; A &lt;&lt; endl;
    cout &lt;&lt; B &lt;&lt; endl;
    
    Quaternionf q1(AngleAxisf(M_PI/3.0f, Vector3f::UnitZ()));
    cout &lt;&lt; q1.w() &lt;&lt; &quot;, &quot; &lt;&lt; q1.x() &lt;&lt; &quot;, &quot; &lt;&lt; q1.y() &lt;&lt; &quot;, &quot; &lt;&lt; q1.z() &lt;&lt; endl;
    return 0;
}
1 0 0
0 2 0
0 0 4
   1    0    0
   0  0.5    0
   0    0 0.25
0.866025, 0, 0, 0.5

Unit quaternions offer you a better way to represent orientations and rotations of objects in three dimensions than rotation matrices do.

euler