Quaternion - method .setFromBasis( e1, e2, e3 )

These posts and examples are about movement on a curve.

Struggling with paths - #14 by hofk
Car Racing - For lovers of fast cars!

MotionAlongCurve
MovementOnCurve
CarRacing

However, the solution via matrices requires computational effort. I have been looking for a solution with quaternions that requires fewer formula steps.

The solution is a new method in the quaternion class.
I added it to three.module.129.Quaternion.js in the example BasisToQuaternion.

This makes the calculation of the correct orientation quite simple.

// t tangent, n normal, b binormal
t = curve.getTangent( f );
b.crossVectors( t, n ).normalize( );
n.crossVectors( t, b.negate( ).normalize( ) );

p = curve.getPoint( f );

// added method .quaternion.setFromBasis( ) from ../jsm/three.module.129.Quaternion.js;

dice.quaternion.setFromBasis( t, b, n );
dice.position.set( p.x, p.y - 0.1, p.z );	
 
box.quaternion.setFromBasis( t, b, n );	
box.position.set( p.x, p.y, p.z );

modelBee.quaternion.setFromBasis(  t.negate( ), n, b ); // another orientation
modelBee.position.set( p.x, p.y, p.z );


class Quaternion { 

...

	setFromBasis( e1, e2, e3 ) {
		
		const	m11 = e1.x, m12 = e1.y, m13 = e1.z,
				m21 = e2.x, m22 = e2.y, m23 = e2.z,
				m31 = e3.x, m32 = e3.y, m33 = e3.z,
				trace = m11 + m22 + m33;
	
		if ( trace > 0 ) {
	
			const s = 0.5 / Math.sqrt( trace + 1.0 );
	
			this._w = 0.25 / s;
			this._x = -( m32 - m23 ) * s;
			this._y = -( m13 - m31 ) * s;
			this._z = -( m21 - m12 ) * s;
	
		} else if ( m11 > m22 && m11 > m33 ) {
	
			const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
	
			this._w = ( m32 - m23 ) / s;
			this._x = -0.25 * s;
			this._y = -( m12 + m21 ) / s;
			this._z = -( m13 + m31 ) / s;
	
		} else if ( m22 > m33 ) {
	
			const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
	
			this._w = ( m13 - m31 ) / s;
			this._x = -( m12 + m21 ) / s;
			this._y = -0.25 * s;
			this._z = -( m23 + m32 ) / s;
	
		} else {
	
			const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
	
			this._w = ( m21 - m12 ) / s;
			this._x = -( m13 + m31 ) / s;
			this._y = -( m23 + m32 ) / s;
			this._z = -0.25 * s;
	
		}
		
		this._onChangeCallback();

		return this;

	}
...

}

UPDATE: typo Fom => From


If someone doesn’t want to change the original three.js, they can also integrate the method into their code using .prototype .

THREE.Quaternion.prototype.setFromBasis = function( e1, e2, e3 ) { ... }

See Car Racing - For lovers of fast cars!


:question:
On request ( Math experts: is there anything you wish Three.js math library had?) some hints:

In the docu we have some methods that set the quaternion as a representation of the rotation of different arguments.

.setFromAxisAngle
.setFromEuler
.setFromRotationMatrix
.setFromUnitVectors
.fromBufferAttribute

For the basis: Basis (linear algebra) - Wikipedia

The vectors of length 1 on the x, y z axis form the basis of the three-dimensional vector space.

If you want to rotate an object, you have the above-mentioned built-in methods at your disposal.

If I want to move an object on a CatmullRomCurve3 so that it does not “wobble” but always looks in the direction of the tangent, I calculate the tangent, normal and binormal. These three vectors form the basis of a new (local) vector space.

(By the way: With coordinate transformation you could now convert from the global system x,y,z into this system. )

The new quaternation method .setFromBasis( e1, e2, e3 ), on the other hand, determines the rotation of this system in relation to the global x,y,z system directly and without detours.

Example: FlightRouteQuaternion
( renewed/added by FlightRouteQuaternion from Collection of examples from discourse.threejs.org )

Set // before
shuttle.quaternion.setFromBasis( t[ iShuttle ], b[ iShuttle ], n[ iShuttle ] );
and see what happens.

For the visualization of rotation/quaternion:

Quaternion - Axis, Angle Visualization

quaternion

10 Likes

Really cool, very helpful. Thanks

This is HUGE… thanks a lot!!!

Just a small clarification for those who are confused.

“Basis” here refers to orthonormal basis vectors

This essentially defines your orientation space.
We define which way is up, which way is right and which way is forward.
Since these directions are orthogonal to one another, hence “ortho”, the space is normalized, that is - vectors are of unit length 1, hence “normal” and basis refers to vectors that define your dimensions, et voilà: Ortho-normal basis

Most of the time we just call these “basis” vectors

The vectors are usually denoted as TBN (tangent, bitangent, normal)

For those a bit confused by the code, the orthonormal basis vectors form a 3x3 matrix, often called “rotation matrix”, and the variable names are from that. m11 refers to m[1][1] first row and first column of the matrix

if we rewrite it, it makes it easier to see

m = [
   [m11, m12, m13],
   [m21, m22, m23],
   [m31, m32, m33]
]

and, quite elegantly, if we use TBN notation for out vectors instead of the e1,e2,e3 in the code, we get:

m = [
   [t.x, t.y, t.z]
   [b.x, b.y, b.z]
   [n.x, n.y, n.z]
]

I think this relationship is beautiful and makes you feel like you can almost understand 3d math :slight_smile:

2 Likes

2 Likes

m11 as in m[0][0] or m21 as in m[1][0]?

1 Like

Ha, I was hoping nobody would point this out. There is no right answer actually, depends on matrix conventions that you’re using. OpenGL/WebGL is column-major, that means that first access (A) is into a column, and second access (B) is into a row:
m[A][B]

DirectX is the opposite, and there generally is no standard on this out there. Some engines even let you choose your convention.

But really, it doesn’t matter. To be pedantic, for WebGL it would be

m = [
  t.x, t.y, t.z, // column 0
  b.x, b.y, b.z, // column 1
  n.x, n.y, n.z  // column 2
]

Where T is in column 0, B is in column 1 and N is in column 2

To address your original question, it would be mCR where C is column and R is row

Again, to be clear, actual matrix orientation has no meaning, it’s just a serialization convention.

2 Likes

Very insightful, thank you Usnul

1 Like