Bugs in Vector3D and Matrix3D

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. Please vote for them in the Adobe bug tracking site.

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.

15 thoughts on “Bugs in Vector3D and Matrix3D

  1. I thought I was just too stupid for this, when I first used Matrix3D. You pointed out that I’m not. Thanks :)
    I voted for all the bugs in jira. Hopefully Adobe will fix these bugs before Molehill is being released.

  2. Hello…
    crossing 1,0,0 and 0,1,0 gives 0,0,1 , why do you say that its a bug?
    Maybe I’m missing some subtleties in this example..

    And the bugs of m3d are serious, knowing that it’s buggy will save me some time debuggin my code, thx ; )

  3. teleranek: Because the w coordinate is set to 1. i.e. crossing (1,0,0,0) and (0,1,0,0) gives (0,0,1,1). It should be (0,0,1,0).

  4. Thanks Richard for creating an overview of all the bugs in these classes. I have spent a few hours doubting myself a few months ago because of the determinant bugs in Matrix3D and glad to hear from someone else its a bug in Adobe’s API.

  5. Pingback: Matrix3D: Rotation um einen lokalen Punkt - Seite 2 - Flashforum

  6. Floating point inaccuracies… this is not a bug – this is just known fact of digital computers – because numbers are (allways) “analog” value, special when you have manipulate with “infinity nonperiodical transcedential” numbers (i.e. PI, sinus, cosinus… etc). But, people like John Van Neuman was very very very clever people – so, in Flash, you have solution for your “problem” (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Number.html)
    Solution is:
    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]));
    for (var i = 0; i < m.rawData.length; i+=1 ) {trace(m.rawData[i].toPrecision(8));};

    I belive, all your above bugs have solution (in Flash, of course), but digital computers are not analog computers and analog computers aren't our mother Nature…. or, maybie father God

  7. Hi Brane

    I consider these floating point inaccuracies bugs because, as I describe above, they are far bigger than the normal inaccuracies introduced by the floating-point unit. They may be caused by Adobe using calculations that are far more complex than necessary and thus compounding small inaccuracies into much larger ones. This is a bug that they can fix in their code if they choose to, not by eliminating the inaccuracies but by reducing the size of them – the same calculations in Coral have much smaller inaccuracies.

  8. Not just Adobe use digital computers…
    Take a look at “Fixed-point arithmetic”, ” * Binary scaling”, “Q (number format)”…. and so on… and go back to 1993 (or much better idea – back to basic of “digital era” in 1960s, 1970s) – when we use computers with 286, 386, 486 Intel CPU, with or without “Math co-processors” (like today’s CPUs wih or without FPU for mobile devices, microcontrolers, or sensors in this mobiles device’s – http://www.hobbyengineering.com/specs/PX-29123.pdf) … and everybody PC’s owner in this time’s want to create or play early 3D games like “Wolfenstein 3D” or “Doom” of “Id Software”… Doom was the last “first-person shooter” title by id Software to use a 16.16 fixed point representation for all of its non-integer computations, including map system, geometry, rendering, player movement etc. This was done in order for the game to be playable on 386 and 486SX CPUs without an FPU. For compatibility reasons, this representation is still used in modern Doom source ports.
    * Binary scaling techniques were used in the 1970s and 80s for real time computing that was mathematically intensive, such as flight simulation. The code was often commented with the binary scalings of the intermediate results of equations.
    And so on … infinity (analog) values in limited (digital) world. But it doesen’t matter the 128 (^ n) computer’s coming… maybie, before end of human’s time…

  9. Brane

    Treating me like I’m stupid and don’t understand about inaccuracies in floating point calculations on computers really isn’t helping. Before you reply to this post bear in mind I do know about the inherent inaccuracies from storing floating point numbers in a fixed length binary format, I have been working with such inaccuracies during the thirty years that I have been programming computers professionally.

    What I describe in the article are inaccuracies far greater than would be introduced by normal use of floating point numbers in Flash (note that Flash uses doubles for all floating-point numbers, it has no mechanism for using floats), and far greater than experienced in Coral, my alternative to Adobe’s Vector3D and Matrix3D classes.

    The example I give in the article is setting a matrix property to 1.1 and finding it is stored as 1.100000023841858. That’s after doing no calculations on the value. It is likely that this is because Adobe have chosen to represent a Matrix3D internally using floats rather than doubles, even though floats are not a feature of Flash, but that is an explanation of the cause not an invalidation of the bug.

    My problem, and the reason for the bug report, is that this is not how the class is documented to work. The class is documented as using Numbers, i.e. doubles, for these properties. Using floats instead (if that is what’s happening), and not telling developers of this fact, means developers may make incorrect assumptions about the accuracies of calculations using these classes (I did in my tests, and that is why Matrix2D failed some tests).

    The difference also introduces far larger floating-point errors than expected or experienced anywhere else in Flash. These errors are compounded as one performs calculations with the matrix, to the extent that variations from the expected value are significant.

  10. OK Richard, execute this code (in Flash or something other PL):
    for (var i:int = 1; i <= 360; i+=1) {var rad:Number = Math.PI * (i / -180); trace((-rad+" \t\t\t"+(-rad/Math.PI)*180)); };
    Result is not bug, it just fact: digital limitation in analog world.
    There (in digital world) are lot of similar "bugs", but people, who make programs (i.e. you, and me and others coders) must know that limitations – but, I tell you one story: when I was a very young ( in 60s in ex. Yougoslavija) , in primary school, we learn about analog computers (and make practices, too, with lot of matematics, algebra, trigonometry, number…etc). I saw (not just saw) first digital computer at the end of 60s, and my first thing I was search for limiteds of digital proceedings of numbers, and, of course I found it.

    OK – test is: open Calculator (in Windows or Linux or Mac…) or take any other pocket calc and make math sqrt operation …. i.e. math sqrt of 2 = 1.41… , do math sqrt of 1.41 and so one for several times, such as 8 times – result is 1,0027112750502024854307455884504 Then make reverse operation with this number (n*) – result is 2, of course. Now, you make sqrt operation for more then 24 times (at 64 bit computer with 32 bit FPU (floating point unit) like my Xeon Quad core 3.2 Mhz) and make reverse operation (step by step, of course), and you will see very interesting result…. If you use this number (result of n steps of sqrt (of 2 or any other number) and copy/paste in another calculator and make multiply (n*) the result will be diferent. If you make loop in different program language on differents platforms – result will be very unpredictable… and maybie very danger in most cases (i.e. nuclear power plants, industry, robotics, war toys… … … etc).

    This is fucking nature of digital computers – but, it doesent matter, if we consider that fact – evolution of computers go on, and in my next reincarnation…. who know.

    I hope, that my reflections are not so boring, and sorry for my strange language….

  11. Sorry, Brane, but I don’t understand what point you’re trying to make. I don’t need to execute that code, I know what will happen. Read this very carefully – I understand how floating point mathematics works on computers. Don’t lecture me about it.

    Now read this too. When a company produces a library and states that the matrix class in that library uses 32-bit precision floating point numbers, but in fact it uses 16-bit precision floating point numbers, that is a bug. Do you agree?

  12. Na, I don’t agree, so I make my own matrix and libraries (like you) and, if is posible (and allways is possible, but need a lot of time…), just with bitwise operators (i.e. Machine code, assembly language…).

    Adobe, and all of big company, make a lot of bugs (I found much), but I don’t want to waste my time and energy for searching it – I know, ie exist, reason is allways same…
    I’m not treating you like a stupid, I just try to took with you about math. matrix, computers, analog and digital stuffs, exchange expiriences and so on…

  13. Flash’s Matrix3D ignoring the “w” property when using transform vector operations is expected given 2 seperate methods ( transformVector() and deltaTransformVector() ), so I don’t think it’s a bug but more of a design choice, especially for end-users that don’t understand the importance of the “w” property and ignores it altogether. It seems though that the “w” property doesn’t play a significant role in the class’s design, so it does make homonogenous matrix operations hard to do in certain cases.

Leave a Reply

Your email address will not be published. Required fields are marked *