Writer, Choreographer, Game Developer

Bugs in Vector3D and Matrix3D

Posted on

This is part 4 of a 4 part series comparing Coral, a derivative of the 3D math classes that used to be in the Flint Particles project, and Flash's native 3D math classes.

In the first post I introduced Coral, explaining what it is and why I'm releasing it as an open source project. The next post looked at the key architectural differences between Coral and the native Vector3D and Matrix3D classes. The third post compared the performance of Coral and the native 3D math classes. This last post is about bugs.

The classes in Coral are covered by unit tests. I don't write unit tests for every bit of code I write, but for classes like these 3D Math classes, which will be used in many different projects, in many different circumstances, where bugs could be hard to track down, I consider unit tests essential.

I adapted the unit tests from Coral to test Flash's native Vector3D and Matrix3D classes. In so doing, I discovered a number of bugs in the native classes. All the test code is in the Coral project on Github. This is what I found.

Bugs in Vector3D

Vector3D passed most of the tests. These are the tests it failed.

X_AXIS, Y_AXIS & Z_ AXIS are mutable.

Consider this code

var v : Vector3D = Vector3D.X_AXIS;
v.scaleBy( 2 );
var u : Vector3D = Vector3D.X_AXIS;
trace( u );

It would be natural to expect u to be the vector ( 1 0 0 ), but it isn't. It's ( 2 0 0 ). Because X_AXIS, Y_AXIS & Z_AXIS return a reference to the same vector every time. They should return a new vector each time, so the value of the X_AXIS, Y_AXIS & Z_AXIS properties can't be changed.

Vote for this bug on Adobe's bug tracker

CrossProduct sets the w coordinate to 1

The default value for the w coordinate is supposed to be zero. So the w coordinate of the result of the crossProduct of two vectors should be zero. It isn't - it's one.

var u : Vector3D = new Vector3D( 1, 0, 0 );
var v : Vector3D = new Vector3D( 0, 1, 0 );
var r : Vector3D = u.crossProduct( v );
trace( u.w ); // 0
trace( v.w ); // 0
trace( r.w ); // 1

Vote for this bug on Adobe's bug tracker

Bugs in Matrix3D

Matrix3D contains more serious bugs than Vector3D. Here they are.

The determinant is incorrect

When the Matrix3D class calculates the determinant, it gets it wrong. Every time. Consider this matrix, for example.

[ 1 2 3 4 ]
[ 2 3 4 5 ]
[ 3 4 5 6 ]
[ 4 5 6 7 ]

The determinant of this matrix is zero. But the Matrix3D class thinks it is -36. Stranger, but reassuring, if you ask Matrix3D to calculate the inverse, it recognizes that the inverse can't be calculated because the determinant is zero!!

var m : Matrix3D = new Matrix3D(Vector.([1,2,3,4,2,3,4,5,3,4,5,6,4,5,6,7]));
trace( m.determinant ); // -36
var b : Boolean = m.invert();
trace( b ); // false

Matrix3D even gets the determinant of the identity matrix wrong.

var m : Matrix3D = new Matrix3D();
trace( m.determinant ); // -1

The correct value is 1.

Vote for this bug on Adobe's bug tracker

Floating point inaccuracies

The Matrix3D class introduces inaccuracies in floating point numbers that far exceed the normal floating point inaccuracies in Flash. You can see this without even touching the matrix after creating it...

var m : Matrix3D = new Matrix3D(Vector.([1.1,0,0,0,0,1.1,0,0,0,0,1.1,0,0,0,0,1]));
trace( m.rawData );

The output from this is

1.100000023841858,0,0,0,0,1.100000023841858,0,0,0,0,1.100000023841858,0,0,0,0,1

This appears to be caused by Flash internally using single-precision floating point numbers for Matrix3D, even though the type is specified as Number and Number is specified as double-precision floating point. Flash doesn't even have a single-precision floating point type. So, at the very least, the documentation for Matrix3D is incorrect, but I would suggest that using a data type that doesn't exist within the Actionscript language is a bug.

Anyway, the Matrix3D class intermittently fails some of Coral's unit tests because the results are not within 0.0001 of what they should be.

Vote for this bug on Adobe's bug tracker

appendRotation() and prependRotation() assume the axis is of unit length

The appendRotation() and prependRotation() methods only produce the correct matrix if the axis property passed to the method is of unit length.

This code specifies a 90° rotation about the x-axis.

var m : Matrix3D = new Matrix3D();
m.appendRotation( 90, new Vector3D( 1, 0, 0 ) );
trace( m.rawData );

The result is the matrix

[ 1 0 0 0 ]
[ 0 0-1 0 ]
[ 0 1 0 0 ]
[ 0 0 0 1 ]

This code also specifies a 90° rotation about the x-axis.

var m : Matrix3D = new Matrix3D();
m.appendRotation( 90, new Vector3D( 2, 0, 0 ) );
trace( m.rawData );

But the result is the matrix

[ 4 0 0 0 ]
[ 0 0-2 0 ]
[ 0 2 0 0 ]
[ 0 0 0 1 ]

Which is a rotation and scale.

That the axis should be a unit vector isn't specified as a requirement in the documentation and it isn't enforced in the code. These methods should calculate a unit vector from the axis and use that to calculate the rotation.

Vote for this bug on Adobe's bug tracker

appendTranslation() ignores the bottom row of the matrix

The appendTranslation() method simply adds the new translation coordinates to the translation coordinates in the last column of the matrix. This is only correct if the first three numbers in the bottom row of the matrix are zero.

[ 1 0 0 2 ]   [ 2 1 0 0 ]   [ 2 1 0 2 ]
[ 0 1 0 3 ] . [ 3 1 2 0 ] = [ 3 1 2 3 ]
[ 0 0 1 4 ]   [ 1 0 1 0 ]   [ 1 0 1 4 ]
[ 0 0 0 1 ]   [ 0 0 0 1 ]   [ 0 0 0 1 ]
[ 1 0 0 2 ]   [ 2 1 0 0 ]   [ 4 3 2 2 ]
[ 0 1 0 3 ] . [ 3 1 2 0 ] = [ 6 4 5 3 ]
[ 0 0 1 4 ]   [ 1 0 1 0 ]   [ 5 4 5 4 ]
[ 0 0 0 1 ]   [ 1 1 1 1 ]   [ 1 1 1 1 ]

The Matrix3D class gets the first calculation correct and gets the second one wrong.

var m1 : Matrix3D = new Matrix3D(Vector.([2,3,1,0,1,1,0,0,0,2,1,0,0,0,0,1]));
m1.appendTranslation( 2, 3, 4 );
trace( m1.rawData ); // 2,3,1,0,1,1,0,0,0,2,1,0,2,3,4,1 
var m2 : Matrix3D = new Matrix3D(Vector.([2,3,1,1,1,1,0,1,0,2,1,1,0,0,0,1]));
m2.appendTranslation( 2, 3, 4 );
trace( m2.rawData ); // 2,3,1,1,1,1,0,1,0,2,1,1,2,3,4,1

Vote for this bug on Adobe's bug tracker

transformVector() ignores the w coordinate

The transformVector() method ignores the value of the w property of the input vector. The transformVector() method will set the w property if you define a matrix that affects the fourth coordinate of the vector, for example a perspective projection matrix. However, it assumes the w coordinate of the input vector is 1, even though the default value for the w coordinate is 0.

Vote for this bug on Adobe's bug tracker

The end

That's all the bugs I found.

That's also the end of this series of posts about Coral and Flash's native 3D Math classes. Please feel free to use, fork and improve Coral. And let me know what you think, in the comments or via Twitter.

Footnote

The new Flash Player 11 Incubator pre-release became available on 28 February. I ran the test suite using this new player and all the bugs appear to still be present. That's not surprising since I only notified Adobe of the bugs a few days before the pre-release.


Also in the collection Coral, an Actionscript library for 3D Math