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?