Bug in Transform3D (test-program included)

import javax.media.j3d.;
import javax.vecmath.
;

public class scaleTest {

   public scaleTest () {
   }

   public static void main (String[] args) {
           Transform3D t1 = new Transform3D();
           Transform3D t2 = new Transform3D();

           Vector3d s1 = new Vector3d(1.0,1.0,3.0);
           Vector3d s2 = new Vector3d();

           t1.set(new AxisAngle4d(0,1,0,1.57));

           t1.setScale (s1);
           t1.normalizeCP ();

           t2.set(t1); // even if normalizeCP is flawed this should make t1==t2
           t1.getScale (s1);
           t2.getScale (s2);

           System.out.println(s1+" == "+s2);
   }

}

Expected Output:

(1.0, 1.0, 3.0) == (1.0, 1.0, 3.0)

Actual Output:

(1.0, 1.0, 3.0) == (2.9999991544849074, 1.0, 1.0000025365417038)

Why normalizeCP() is important when we have normalize():

Because normalize() also fails, see bug 4751283 “Transform3D.normalize()
and Matrix4d.get(Matrix3d) permute matrix columns.”:
http://archives.java.sun.com/cgi-bin/wa?A2=ind0209&L=java3d-interest&D=0&I=-3&P=30861

So I am left with no working normalize method?

I will be grateful for any code suggestions able to cope with
non-uniform scales.

Btw. Rather strange Sun has removed bug 4751283 from the BugParade.
Does that mean they regard it as closed?

Normalize & normalize3d work just fine, they just aren’t doing the operation you seem to think they should be.
‘Normalize’ is simple ensuring the matrix is ‘ortho-normal’, i.e. that the rotation part (the 3x3 top left corner) contains 3 unit vectors. The scale is stored seperately, and should not be affected by a normalise operation, and the rotation & position will not be changed either. What exactly do you expect a normalize() to do?

The scale & normalise functions do behave in the normal way from what I can tell. NormalizeCp is a cheaper & slightly less accurate method to enforce an othonormal rotation matrix than the default SVP method. However, it is normally suffiecient for most 3d applications, particularly games.

Furthermore, you have to distinguish which space an operation is being performed in. You are applying the scale after the transform has been rotated out of unit position. The scale function you call attempts to set a world based scale, and thus back-transforms the scale vector by the transforms matrix to obtain the actual local scale required to give the requested world scale. This gives the (3,1,1) local scale that you observed.
Sadly I don’t have Java3d installed, & Im not going to install it, so I cannot check the validity of your code.
It seems the issue is more the ‘s2.set(s1)’ doesnt appear to be working properly, as that should force s1.scale == s2.scale as you suggest.

I would further suggest that Sun removed the bug report as it was based on your similarly flawed expectations of what the functions ‘should’ be doing. Bear in mind that there are many varying opinions worldwide on what a ‘normal’ matrix operation is - pre/post multiply, column/row ordering, Rigt/Left handedness in notation & rotation… Its a nightmare. However, if 95% of developers seem happy with an interface that you are having problems with, the weight of probability is that your basic assumptions on the matrix math are wrong. Direct3D & openGL can’t even agree on a standard, & PS2’s use a different coordinate system again.

Sorry if this seems a bit harsh, but Ive been in the situation you were in before many times (American & UK standards appear to be opposites with matrices. I have my own desk in Matrix Hell) & it is all to easy to start pointing fingers at other peoples work without correctly establishing the validity of your own assumptions.

  • Dom

[quote]Normalize & normalize3d work just fine, they just aren’t doing the operation you seem to think they should be.
‘Normalize’ is simple ensuring the matrix is ‘ortho-normal’, i.e. that the rotation part (the 3x3 top left corner) contains 3 unit vectors. The scale is stored seperately, and should not be affected by a normalise operation, and the rotation & position will not be changed either. What exactly do you expect a normalize() to do?
[/quote]
As you say yourself: “The scale is stored seperately, and should not be affected by a normalise operation”

[quote]Furthermore, you have to distinguish which space an operation is being performed in. You are applying the scale after the transform has been rotated out of unit position. The scale function you call attempts to set a world based scale, and thus back-transforms the scale vector by the transforms matrix to obtain the actual local scale required to give the requested world scale. This gives the (3,1,1) local scale that you observed.
[/quote]
Sorry you lost me there.

[quote]It seems the issue is more the ‘s2.set(s1)’ doesnt appear to be working properly, as that should force s1.scale == s2.scale as you suggest.
[/quote]
Maybe, but when I print out the matrixes and compare the doubles, I cannot find a single difference. If we asume the only information in a Transform3D is the 16 matrix doubles, there should be no difference at at all. It confuses me.

[quote]I would further suggest that Sun removed the bug report as it was based on your similarly flawed expectations of what the functions ‘should’ be doing.
[/quote]
As you can see above my expectation is the same as yours. I only expect it to preserve the scale, which it does not.

[quote]it is all to easy to start pointing fingers at other peoples work without correctly establishing the validity of your own assumptions.
[/quote]
I didn’t point fingers at anyone, I just reported a bug, And requested for people more skilled than myself to submit some code to do the job properly.

Regards
Nikolai

Here follows the reply from Sun:

Nikolai,
Thanks for your bug report and test program. Your program reveal the
same
bug as reported in BugId : 4751283 -
Transform3D.normalize() and Matrix4d.get(Matrix3d) permute matrix columns.

The SVD algorithm that Java 3D uses will sort the columns of the output
matrix by descending order of their scale factors. This is useful for
many types of problems, but is inappropriate for the normalization of
rotation matrices.

BTW, BugId : 4751283 is still open and I’m able to view it with our
internal
bug tool. I will try to resolve the discrepancy with BugParade.

thanks,
Chien Yang
Java 3D, Sun Microsystems Inc.

The bug itself can be viewed here:

http://developer.java.sun.com/developer/bugParade/bugs/4751283.html

Ah, ok.
The issue here is the bit you said I lost you with. That I think is the source of the problem.

The thing is, you are applying a scale of 3x in the x-axis only to the matrix.

However, the matrix is rotated 90 degrees around the y axis. This means the matrixes x axis now lies along the -z axis in world space, and the matrix z axis points along world space x. i.e. the matrix will be (using rows for axis, which is my personal favourite):
x - ( 0, 0, -1 )
y - ( 0, 1, 0 ) = R, a rotation matrix
z - ( 1, 0, 0 )
You then scale the WORLD z-axis by 3 using a matrix:
(1, 0, 0)
(0, 1, 0) = S, a scale matrix
(0, 0, 3)
This is the scale matrix. Multiplying these (R*S) gives:
(0, 0, -3)
(0, 1, 0)
(1, 0, 0) which looks like what you get.

Note that this is not the same as a LOCAL z axis scale (S * R), which would have given:
(0, 0, -1)
(0, 1, 0)
(3, 0, 0) which is what you seem to be after.

The only difference is how the scale is applied. The first way was post-multiplied, the second was pre-multiplied. This can be seen as ‘the order which you apply the matrices’, or (from a physics viewpoint) the space in which the scale is applied.

Admittedly, I would side with you on the expected behaviour of a function called ‘scale’, but my previous point was that conventions differ, and there is no real ‘right’ way. You just have to deal with the conventions used by the library your using.

The best way to ortho-normalise a matrix is this (in pseudo code):

vector x = matrix.getXaxis();
float xl = x.length();
x /= xl; // normalise
vector y = matrix.getYaxis();
float yl = y.length();
y /= yl; // normalise

vector z = matrix.getZaxis();
float zl = z.length();

// Now build a new z-axis that is orthogonal to x and y:
z.crossProduct( x, y ); // z = x ^ y
z.normalise();

// z is now a true z-axis, both ‘normal’, and orthogonal to x and y.
y.crossProduct( z, x ); // y = z ^ x

// Rebuild the matrix, multiplying scale back in:
matrix.setXaxis( x * xl );
matrix.setYaxis( y * yl );
matrxi.setZaxis( z * zl );

This should be what the normalizeCP() is doing, guessing from the name.

Hope this makes more sense :slight_smile:

  • Dom