Hello,

I'm trying to make a simple 3D wireframe model viewer. This is being done using the good ol' Windows GD of I as opposed to Direct3D or OpenGL. Performance is not really my objective here, accuracy is a lot more important.

Reading around, quaternions seem all the rage. To that end, I have attempted to project the vertices of the model into 2D based on a global quaternion (the user can rotate the entire object and zoom in/out, nothing else. The object stays in the centre of the window).

The properties of the current scene are stored in pScene, as thus:-
Code:
typedef struct {
	unsigned short usWidth;
	unsigned short usHeight;
	t_angle3d aRotation; // roll, pitch, yaw (radians)
	double dZoom; // z translation
	double dQuaternion[4];
} t_scene;
The quaternion is initialized at the beginning of the render operation using the members of pScene->aRotation as input:-
Code:
void EulerToQuat(double dRoll, double dPitch, double dYaw, double *pdQuaternion)
{
	double cr, cp, cy, sr, sp, sy, cpcy, spsy;

	cr = cos(dRoll / 2);
	cp = cos(dPitch / 2);
	cy = cos(dYaw / 2);
	sr = sin(dRoll / 2);
	sp = sin(dPitch / 2);
	sy = sin(dYaw / 2);
	cpcy = cp * cy;
	spsy = sp * sy;
	pdQuaternion[0] = (cr * cpcy) + (sr * spsy);
	pdQuaternion[1] = (sr * cpcy) - (cr * spsy);
	pdQuaternion[2] = (cr * sp * cy) + (sr * cp * sy);
	pdQuaternion[3] = (cr * cp * sy) - (sr * sp * cy);
}
Each vertex in the model is projected using the below function. If they are found to be projected in or behind the camera (z <= 0), they are discarded:-
Code:
int ProjectVertex(t_scene *pScene, t_vertex3d *pV, t_vertex2d *pVout)
{
    double dWsquared, dXsquared, dYsquared, dZsquared;
    t_vertex3d vTemp;

    // I should probably store these in pScene if I'm rotating everything the same way
    dWsquared = pScene->dQuaternion[0] * pScene->dQuaternion[0];
    dXsquared = pScene->dQuaternion[1] * pScene->dQuaternion[1];
    dYsquared = pScene->dQuaternion[2] * pScene->dQuaternion[2];
    dZsquared = pScene->dQuaternion[3] * pScene->dQuaternion[3];
    vTemp.x = pV->x * (dWsquared + dXsquared - dYsquared - dZsquared) + (2 * ((pScene->dQuaternion[0] * pScene->dQuaternion[2] * pV->z) + (pScene->dQuaternion[1] * pScene->dQuaternion[3] * pV->z) + (pScene->dQuaternion[1] * pScene->dQuaternion[2] * pV->y) - (pScene->dQuaternion[0] * pScene->dQuaternion[3] * pV->y)));
    vTemp.y = pV->y * (dWsquared - dXsquared + dYsquared - dZsquared) + (2 * ((pScene->dQuaternion[1] * pScene->dQuaternion[2] * pV->x) + (pScene->dQuaternion[0] * pScene->dQuaternion[3] * pV->x) + (pScene->dQuaternion[2] * pScene->dQuaternion[3] * pV->z) - (pScene->dQuaternion[1] * pScene->dQuaternion[0] * pV->z)));
    vTemp.z = pV->z * (dWsquared - dXsquared - dYsquared + dZsquared) + (2 * ((pScene->dQuaternion[1] * pScene->dQuaternion[3] * pV->x) - (pScene->dQuaternion[0] * pScene->dQuaternion[2] * pV->x) + (pScene->dQuaternion[0] * pScene->dQuaternion[1] * pV->y) + (pScene->dQuaternion[3] * pScene->dQuaternion[2] * pV->y)));
    vTemp.z -= pScene->dZoom;
    if (vTemp.z <= 0)
        return -1;

    if (pVout)
    {
        // I'm not sure where "32" comes from, it just seems a reasonable scaling factor
        // Examples I have seen use (40 / 10) but where does this come from?!?
        pVout->x = ((vTemp.x / vTemp.z) * 32) + (pScene->usWidth / 2);
        pVout->y = ((vTemp.y / vTemp.z) * -32) + (pScene->usHeight / 2);
    }

    return 0;
}
This, er, almost works but doesn't.
There's something up with the projection. It seems to vary between horizontally squashed to stretched when rotated through 360 degrees pitch.

Can anyone help?