peppebck's picture

GL.Rotate and GL.GetFloat or GL.GetDouble gives unprecise result

Hi everybody,
this is my first post and I'm happy to become part of the group...hope I can give some help in the future.

Now I need some information on the reason why I recieve an unprecise Matrix on this simple operation.
This is a big problem for my program and I'd like to know if there's a solution.

Double Ang=90;
Double[]Axis=new double[]{0,0,1};
Double[]rot=new double[16];

GL.Rotate(Ang, Axis[0], Axis[1], Axis[2]);

GL.GetFloat(GetPName.ModelviewMatrix, rot);

This is my matrix, as you can see rot[0] and rot[5] are not zero.
[0] 0.000000012167964413833943 double
[1] 1.0 double
[2] 0.0 double
[3] 0.0 double
[4] -1.0 double
[5] 0.000000012167964413833943 double
[6] 0.0 double
[7] 0.0 double
[8] 0.0 double
[9] 0.0 double
[10] 1.0 double
[11] 0.0 double
[12] 0.0 double
[13] 0.0 double
[14] 0.0 double
[15] 1.0 double

Thanks, and regards.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

An amount of numeric imprecision is expected with floating point numbers. This can become an issue when chaining operations - the OpenGL redbook suggests avoiding long chains for precisely that reason. In other words, instead of doing this:

GL.LoadIdentity();
for (int i = 0; i < 100; i++)
{
    GL.Rotate(1, Vector3.Up);
    Render();
}

prefer this:

for (int i = 0; i < 100; i++)
{
    GL.LoadIdentity();
    GL.Rotate(i, Vector3.Up);
    Render();
}

You might be able to get better precision by using OpenTK.Matrix4d instead of the built-in OpenGL matrix functionality. With this approach, you would calculate the modelview matrix by multiplying Matrix4d instances and would only load the end result with GL.LoadMatrix. For example, the following code:

GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();
GL.Rotate(90, 0, 1, 0);
GL.Translate(10, 0, 0);
Render();

would become:

Matrix4d modelview, temp;
Matrix4d.CreateRotationY(MathHelper.PiOver2, out modelview);
Matrix4d.CreateTranslation(10, 0, 0, out temp);
Matrix4d.Multiply(ref modelview, ref temp, out modelview);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadMatrix(ref modelview);
Render();

Pretty verbose but that's necessary to maintain performance. The short version is more readable but also significantly slower:

Matrix4 modelview = 
    Matrix4d.CreateRotationY(MathHelper.PiOver2) *
    Matrix4d.CreateTranslation(10, 0, 0);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadMatrix(ref modelview);
Render();
peppebck's picture

Fiddler, thanks for he answer but...the result is just more coerent because the result is orthonormal but it is jet not precise.
I've found the formula used to calculate for example M11:
ux^2 + (1-ux^2)*cos(angle)
and I've found that the problem is on the function Math.Cos (Math.PI).
Math.Cos (Math.PI/2.0) gives the result 6.12303176911189E-17 and not 0 as it should be.
Math.Cos(OpenTK.MathHelper.PiOver2) gives the result 4.37113900018644E-08-

I have to threat my matrix after all the operations...I think (sob)
This is a huge problem. It means a big work of approximation avoiding as you said to chain the operations.

M11 -0.000000043711390001864437 double
M12 0.999999999999999 double
M13 0.0 double
M14 0.0 double
M21 -0.999999999999999 double
M22 -0.000000043711390001864437 double
M23 0.0 double
M24 0.0 double
M31 0.0 double
M32 0.0 double
M33 1.0 double
M34 0.0 double
M41 0.0 double
M42 0.0 double
M43 0.0 double
M44 1.0 double

the Fiddler's picture

Ah, OpenTK.MathHelper.PiOver2 is a float, whereas System.Math.PI / 2.0 is a double, which explains the different result. Unfortunately, I don't think it's possible to do any better using floating point numbers, this kind of imprecision is an inherent limitation. Note that OpenGL itself only works with single-precision floats - any double-precision data you pass is converted to single-precision behind the scenes (i.e. when you upload a vertex buffer or a matrix).

I'm afraid I'm not well-versed in this area of computing but hopefully someone else will have better advice to offer.

Out of curiosity, what kind of software is this? (And what kind of precision does it require?)

peppebck's picture

Duplicate

peppebck's picture

Fiddler, I need to place some pieces around models coming from cad. The result has to go back in the cad at the end. So everything has to be precise as much is possible.
I think I'll use the following function to clean my matrix after each rotation.
The idea is to obtain the angle in degrees, clean it if neede and put the Cosine back to the matrix.
Should work but I HAVEN'T tested it jet. Maybe someone can take advantage from this as I did from you.

Thanks a lot.

public static void Clean_Matrix(double[] Matrix)
{
double
ang;
for (int ii = 0; ii < 12; ii += 4)
for (int jj = 0; jj < 4; jj++)
{
ang = Math.Acos(Matrix[ii + jj]) * 180.0 / Math.PI;
if (Math.Round(ang, 1) == Math.Round(ang, 4))
{
if (Math.Abs(ang) == 90)
Matrix[ii + jj] = 0;
else
{
if(ang>90)
Matrix[ii + jj] = Math.Cos(Math.Round(ang, 1));
else
Matrix[ii + jj] = Math.Cos(Math.Round(18.0-ang, 1));
}

}
}

}