NDS WiFi Programming with devkitARM — Part 2
So the idea I had to display the potential for socket programming on the NDS was to simply pipe the input data from the NDS over the network to a remote host. To make this appealing from beyond just a technical standpoint, I figured the remote host should be running some sort of visualization that the user could interact with remotely by using the NDS touchscreen. Making an OpenGL application with a 3D cube the user could spin using the touchscreen would work nicely. Lets figure out how to do that from an OpenGL perspective.
We want to rotate the cube along an axis perpendicular to the mouse motion vector. This will give the impression that the cube is spinning as a direct result of the motion, as if you had quickly ran your hand along it. So we first need to construct a vector that is perpendicular to the motion, and lies in the plane of the screen. Lets assume we have a default OpenGL view setup, where we are looking along the negative z-axis, and the screen lies in the xy-plane. Thus, we want a vector that is perpendicular to the mouse motion and lies in the xy-plane.
Anyone with some math background may immediately recognize the cross-product as a candidate to construct the perpendicular vector. Indeed, if we create a vector from the relative mouse motion (conveniently in our environment the mouse motion is on the xy-plane, so the screen coordinates align nicely), then take the cross-product of this vector with the z-axis, the result is a vector that is perpendicular to the mouse motion and lies in the xy-plane.
//iRelX and iRelY is the relative mouse motion float fTheta; float vCross[3]; float fCrossMag; // Assign theta to the magnitude of the mouse motion vector // This is rather arbitrary fTheta = (iRelX*iRelX + iRelY*iRelY)/16.0; /* vCrossX = v1y*v2z - v1z*v2y vCrossY = v1z*v2x - v1x*v2z vCrossZ = v1x*v2y - v1y*v2x */ // Perform cross-product. // Here v1 is the mouse motion vector and v2 is the z-axis. // This gives us a vector that is perpendicular to //the mouse motion and lies in the xy-plane. vCross[0] = iRelY*1.0 - 0.0*0.0; vCross[1] = 0.0*0.0 - -iRelX*1.0; vCross[2] = -iRelX*0.0 - iRelY*0.0; // Normalize this vector for use as axis in rotation matrix fCrossMag = sqrt(vCross[0]*vCross[0] + vCross[1]*vCross[1] + vCross[2]*vCross[2]); vCross[0] = vCross[0] / fCrossMag; vCross[1] = vCross[1] / fCrossMag; vCross[2] = vCross[2] / fCrossMag;
Great. So now we can just take this vector and use it when calling glRotatef(...) in order to transform the modelview matrix, right? Well, not quite. We CAN do this, but if you've played around with glRotatef(...) to manipulate objects in your scene via the modelview matrix, you may have noticed that when you rotate an object on one axis, it actually transforms the other two axes. This basically means that using glRotatef(...) or any other matrix transform in this manner is actually operating in the model's local coordinate system. If we tried to rotate our cube around our constructed axis using glRotatef(...), it would work as expected initially but then subsequent rotations would rotate around the already transformed axis. We don't want this, we want to operate on a fixed coordinate system, what is often referred to as the world coordinate system.
This issue arises because matrix operations post-multiply on the matrix stack in OpenGL. If we could pre-multiply the operations, we would be good, but unfortunately there is no way to tell OpenGL to pre-multiply instead, so we really have to do this operation manually. In order to accomplish this, we need to maintain our own modelview matrix accumulation, and each frame multiply our rotation matrix by this accumulation matrix to get the new modelview transform. Note that if we used glRotatef(...), it would multiply the accumulation matrix by the rotation matrix. Matrix multiplication isn't commutative, and in our situation it makes all the difference in the world.
So we need to get our hands dirty with a little linear algebra. First we need to define a few 4x4 matrices that we can hand to OpenGL:
// OpenGL likes this format for a 4x4 matrix //as declared in the Redbook float matWorld[16]; float matWorldAccum[16]; float matBuff[16];
Also define a function that can construct a rotation matrix when rotating around an arbitrary axis:
// Theta is in radians void MatrixRotation(float *matRot, float fTheta, float fAxisX, float fAxisY, float fAxisZ) { float fCosTheta, fSinTheta; // m0 m4 m8 m12 // m1 m5 m9 m13 // m2 m6 m10 m14 // m3 m7 m11 m15 fCosTheta = cos(fTheta); fSinTheta = sin(fTheta); matRot[0] = fAxisX*fAxisX+(1.0-fAxisX*fAxisX)*fCosTheta; matRot[4] = fAxisX*fAxisY*(1.0-fCosTheta)-fAxisZ*fSinTheta; matRot[8] = fAxisX*fAxisZ*(1.0-fCosTheta)+fAxisY*fSinTheta; matRot[12] = 0.0; // m12,m13,m14 can be used for translation matRot[1] = fAxisX*fAxisY*(1.0-fCosTheta)+fAxisZ*fSinTheta; matRot[5] = fAxisY*fAxisY+(1.0-fAxisY*fAxisY)*fCosTheta; matRot[9] = fAxisY*fAxisZ*(1.0-fCosTheta)-fAxisX*fSinTheta; matRot[13] = 0.0; matRot[2] = fAxisX*fAxisZ*(1.0-fCosTheta)-fAxisY*fSinTheta; matRot[6] = fAxisY*fAxisZ*(1.0-fCosTheta)+fAxisX*fSinTheta; matRot[10] = fAxisZ*fAxisZ+(1.0-fAxisZ*fAxisZ)*fCosTheta; matRot[14] = 0.0; matRot[3] = 0.0; matRot[7] = 0.0; matRot[11] = 0.0; matRot[15] = 1.0; }
Now we need to be able to multiply our rotation matrix by the accumulated modelview transformation matrix:
void MatrixMultiply(float *matDest, const float *matA, const float *matB) { // m0 m4 m8 m12 // m1 m5 m9 m13 // m2 m6 m10 m14 // m3 m7 m11 m15 // Inner product of each row with each column matDest[0]=matA[0]*matB[0] + matA[4]*matB[1] + matA[8]*matB[2] + matA[12]*matB[3]; matDest[4]=matA[0]*matB[4] + matA[4]*matB[5] + matA[8]*matB[6] + matA[12]*matB[7]; matDest[8]=matA[0]*matB[8] + matA[4]*matB[9] + matA[8]*matB[10] + matA[12]*matB[11]; matDest[12]=matA[0]*matB[12] + matA[4]*matB[13] + matA[8]*matB[14] + matA[12]*matB[15]; matDest[1]=matA[1]*matB[0] + matA[5]*matB[1] + matA[9]*matB[2] + matA[13]*matB[3]; matDest[5]=matA[1]*matB[4] + matA[5]*matB[5] + matA[9]*matB[6] + matA[13]*matB[7]; matDest[9]=matA[1]*matB[8] + matA[5]*matB[9] + matA[9]*matB[10] + matA[13]*matB[11]; matDest[13]=matA[1]*matB[12] + matA[5]*matB[13] + matA[9]*matB[14] + matA[13]*matB[15]; matDest[2]=matA[2]*matB[0] + matA[6]*matB[1] + matA[10]*matB[2] + matA[14]*matB[3]; matDest[6]=matA[2]*matB[4] + matA[6]*matB[5] + matA[10]*matB[6] + matA[14]*matB[7]; matDest[10]=matA[2]*matB[8] + matA[6]*matB[9] + matA[10]*matB[10] + matA[14]*matB[11]; matDest[14]=matA[2]*matB[12] + matA[6]*matB[13] + matA[10]*matB[14] + matA[14]*matB[15]; matDest[3]=matA[3]*matB[0] + matA[7]*matB[1] + matA[11]*matB[2] + matA[15]*matB[3]; matDest[7]=matA[3]*matB[4] + matA[7]*matB[5] + matA[11]*matB[6] + matA[15]*matB[7]; matDest[11]=matA[3]*matB[8] + matA[7]*matB[9] + matA[11]*matB[10] + matA[15]*matB[11]; matDest[15]=matA[3]*matB[12] + matA[7]*matB[13] + matA[11]*matB[14] + matA[15]*matB[15]; }
Great, so we are now equipped to construct and maintain our own modelview matrix! This means we no longer have to ask OpenGL for transformations. We can do them ourselves, and just hand the transformation matrix to OpenGL for rendering the current scene.
// Now we contruct a matrix to rotate around this axis MatrixRotation(matWorld, fTheta*PI/180.0, vCross[0], vCross[1], vCross[2]); // Notice this is matWorld * matWorldAccum, instead of //matWorldAccum * matWorld, as it would be if we just used //glMultMatrix with the constructed matWorld rotation matrix MatrixMultiply(matBuff, matWorld, matWorldAccum); // Assign the world matrix accumulation to the modelview matrix glMatrixMode(GL_MODELVIEW); glLoadMatrixf(matBuff); // Update accumulated matrix for(i=0; i<16; i++) matWorldAccum[i] = matBuff[i];
Fun stuff, eh? I always feel empowered by doing more things in my client code instead of processing data in a black box. Next part of the article we will get into the NDS and socket programming. I know you are holding your breath. =)
Tags: 3d, dswifi, matrix, OpenGL, ui