2
\$\begingroup\$

I'm trying to implement a 3D camera of the "Orbit around the origin" variety in a game engine I'm developing in order to learn about 3D graphics and game programming. I have a basic handle on the required math, and have implemented my own crude 3D math library and matrix stack (functionally similar to the one in OpenGL's compatibility profile). My first attempts at a camera failed miserably, until I decided that I should use quaternions to represent rotation. Once I'd done that, I had a camera that moved in the direction I expected it to at about the right speed and fairly smoothly, but there is an odd spatial distortion effect that I can't seem to get rid of. I have posted a brief video on YouTube showing exactly what is happening.

I honestly don't know what could be causing this issue, the magnitude of the distortion appears to reach a maximum at certain angles, but beyond that I'm not sure what I should be looking at. Right now my chief suspect is my quaternion math code, because the matrix stack appears to be behaving itself and if something was wrong with my matrix math code I would be having more serious issues than this.

I'm not sure what information or code I can provide that would be of any use, so I'll start with the code that handles quaternion math.

public class Quaternion {
private float w;
private float x;
private float y; 
private float z;

public Quaternion(){
    this(1.0f,0.0f,0.0f,0.0f);
}

public Quaternion(float W, float X, float Y, float Z){
    w = W;
    x = X;
    y = Y;
    z = Z;
}

public void loadIdentity(){
    w = 1.0f;
    x = 0.0f;
    y = 0.0f;
    z = 0.0f;
}

public float magnitude(){
    //AKA the "norm"
    return (float) Math.sqrt(w*w + x*x + y*y + z*z);
}

public void qNormalize(){
    float mag = this.magnitude();
    w /= mag;
    x /= mag;
    y /= mag;
    z /= mag;
}

public void qMultiply(Quaternion q){
    this.qMultiply(q.w, q.x, q.y, q.z);
}

private void qMultiply(float w2, float x2, float y2, float z2){
    //Performs A*B, where this Quaternion is A
    this.w = w*w2 - x*x2 - y*y2 - z*z2;
    this.x = w*x2 + x*w2 + y*z2 - z*y2;
    this.y = w*y2 + y*w2 + z*x2 - x*z2;
    this.z = w*z2 + z*w2 + x*y2 - y*x2;
}

public float[] qMatrix(){
    //Return a 4x4 matrix representation of this Quaternion, column-major like OpenGL prefers
    float w2 = w*w;
    float x2 = x*x;
    float y2 = y*y;
    float z2 = z*z;

    if(Math.abs(w2+x2+y2+z2 - 1.0) >= 0.000001){
        this.qNormalize();
        return this.qMatrix();
    }

    return new float[]{
        1.0f - 2.0f*y2 -2.0f*z2, 2.0f*x*y + 2.0f*w*z, 2.0f*x*z - 2.0f*w*y, 0.0f,
        2.0f*x*y - 2.0f*w*z, 1.0f - 2.0f*x2 - 2.0f*z2, 2.0f*y*z + 2.0f*w*x,0.0f,
        2.0f*x*z - 2.0f*w*y, 2.0f*y*z + 2.0f*w*x, 1.0f - 2.0f*x2 - 2.0f*y2,0.0f,
        0.0f,0.0f,0.0f,1.0f
    };
}

public void qFromAA(float angle, float[] axis){
    //create a Quaternion from an axis-angle representation

    if(Vector.magnitude(axis) == 0.0f){
        this.loadIdentity();
        return;
    }

    float theta = angle/2.0f;
    axis = Vector.scale((float)Math.sin(theta), Vector.normalize(axis));

    w = (float)Math.cos(theta);
    x = axis[0];
    y = axis[1];
    z = axis[2];
}

public String toString(){
    return "w: "+w+"\tx: "+x+"\ty: "+y+"\tz: "+z+"\tmag: "+(x*x+y*y+z*z+w*w);
}

}

Any ideas about where I should start?

\$\endgroup\$
6
  • \$\begingroup\$ Is your camera really close and/or has a wide field of view angle? \$\endgroup\$ Jul 30, 2011 at 1:55
  • \$\begingroup\$ I guess that depends on what you mean by "really" close and "wide" field of view. I'm very new at this, so I don't have a good concept of what ranges of values are "close" or "wide". That being said, my FOV is set to 45 degrees, and the camera is set to be 60 units away from the origin, or about two grid squares (if the grid was square...) \$\endgroup\$
    – rjacks
    Jul 30, 2011 at 2:10
  • \$\begingroup\$ Just for laughs I quadrupled the distance between the "camera" and the origin, and the distortion is unchanged. \$\endgroup\$
    – rjacks
    Jul 30, 2011 at 2:15
  • \$\begingroup\$ The video looks like it might be the result of nonuniform scaling or shearing, which can be caused unintentionally if you accumulate matrices (due to floating point error). Do you store/modify matrices in an additive fashion across render frames? \$\endgroup\$
    – user1430
    Jul 30, 2011 at 2:19
  • \$\begingroup\$ To test @Josh Petrie's guess let it run for a minute or two, if the distortion gets worse then it's floating point accumulation error. It's been a while, but check that your normalizing operations isn't doing more than it should. \$\endgroup\$ Jul 30, 2011 at 2:27

2 Answers 2

3
\$\begingroup\$

Are you sure your code for quaternion multiplication and your code for quaternion matrix converion is correct?

I had a typo in one of my formulas once that cost me nearly half a day.

btw... http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm is a great reference for quaternion math.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the link, I'll definitely check my quaternion math. My math library is a huge mess atm, I've been meaning to refactor it for about a week but it hadn't broken in a big way until now, assuming that's the issue. \$\endgroup\$
    – rjacks
    Jul 30, 2011 at 3:05
  • 1
    \$\begingroup\$ Yep, that did it. The code that converted the quaternion to a matrix was broken. Thanks everyone! \$\endgroup\$
    – rjacks
    Jul 30, 2011 at 3:28
  • \$\begingroup\$ Make sure you know in what order the Quaternion matrix conversion (ie column or row major); and that it is the same as your own matrix format. This is a common problem when using online material for reference, they don't specify a lot of the time; and it gets confusing when moving between DX and OpenGL references. \$\endgroup\$ Jul 30, 2011 at 5:34
3
\$\begingroup\$

Your math is right, but you're doing an in-place multiply (which means you're writing to values you're using later in the multiplication)

private void qMultiply(float w2, float x2, float y2, float z2){
    //Performs A*B, where this Quaternion is A
    this.w = w*w2 - x*x2 - y*y2 - z*z2;
    this.x = w*x2 + x*w2 + y*z2 - z*y2;
    this.y = w*y2 + y*w2 + z*x2 - x*z2;
    this.z = w*z2 + z*w2 + x*y2 - y*x2;
}

Should be:

private void qMultiply(float w2, float x2, float y2, float z2){
    *this = Quaternion(
    w*w2 - x*x2 - y*y2 - z*z2,
    w*x2 + x*w2 + y*z2 - z*y2,
    w*y2 + y*w2 + z*x2 - x*z2,
    w*z2 + z*w2 + x*y2 - y*x2 );
}

Which will store the final values in temporary variables until the calculation is finished.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .