/******************************Module*Header*******************************\ * Module Name: gentex.c * * The Textured Flag style of the 3D Flying Objects screen saver. * * Texture maps .BMP files onto a simulation of a flag waving in the breeze. * * Copyright (c) 1994-1995 Microsoft Corporation * \**************************************************************************/ #include #include #include #include #include #include #include "ssopengl.h" #include "image.h" static float winTotalwidth = (float)0.75; static float winTotalheight = (float)0.75 * (float)0.75; #define MAX_FRAMES 20 // IPREC is the number of faces in the mesh that models the flag. #define IPREC 15 static int Frames = 10; static MESH winMesh[MAX_FRAMES]; static float sinAngle = (float)0.0; static float xTrans = (float)0.0; static int curMatl = 0; // Material properties. static RGBA matlBrightSpecular = {1.0f, 1.0f, 1.0f, 1.0f}; static RGBA matlDimSpecular = {0.5f, 0.5f, 0.5f, 1.0f}; static RGBA matlNoSpecular = {0.0f, 0.0f, 0.0f, 0.0f}; // Lighting properties. static GLfloat light0Pos[] = {20.0f, 5.0f, 20.0f, 0.0f}; static GLfloat light1Pos[] = {-20.0f, 5.0f, 0.0f, 0.0f}; static RGBA light1Ambient = {0.0f, 0.0f, 0.0f, 0.0f}; static RGBA light1Diffuse = {0.4f, 0.4f, 0.4f, 1.0f}; static RGBA light1Specular = {0.0f, 0.0f, 0.0f, 0.0f}; static RGBA flagColors[] = {{1.0f, 1.0f, 1.0f, 1.0f}, {0.94f, 0.37f, 0.13f, 1.0f}, // red }; static RGBIMAGEREC *image; /******************************Public*Routine******************************\ * iPtInList * * Add a vertex and its normal to the mesh. If the vertex already exists, * add in the normal to the existing normal (we to accumulate the average * normal at each vertex). Normalization of the normals is the * responsibility of the caller. * \**************************************************************************/ static int iPtInList(MESH *mesh, int start, POINT3D *p, POINT3D *norm, BOOL blend) { int i; POINT3D *pts = mesh->pts + start; if (blend) { for (i = start; i < mesh->numPoints; i++, pts++) { if ((pts->x == p->x) && (pts->y == p->y) && (pts->z == p->z)) { mesh->norms[i].x += norm->x; mesh->norms[i].y += norm->y; mesh->norms[i].z += norm->z; return i; } } } else { i = mesh->numPoints; } mesh->pts[i] = *p; mesh->norms[i] = *norm; mesh->numPoints++; return i; } /******************************Public*Routine******************************\ * getZpos * * Get the z-position (depth) of the "wavy" flag component at the given x. * * The function used to model the wave is: * * 1/2 * z = x * sin((2*PI*x + sinAngle) / 4) * * The shape of the wave varies from frame to frame by changing the * phase, sinAngle. * \**************************************************************************/ float getZpos(float x) { float xAbs = x - xTrans; float angle = sinAngle + ((float) (2.0 * PI) * (xAbs / winTotalwidth)); xAbs = winTotalwidth - xAbs; // xAbs += (winTotalwidth / 2.0); return (float)((sin((double)angle) / 4.0) * sqrt((double)(xAbs / winTotalwidth ))); } /******************************Public*Routine******************************\ * genTex * * Generate a mesh representing a frame of the flag. The phase, sinAngle, * is a global variable. * \**************************************************************************/ void genTex(MESH *winMesh) { POINT3D pos; POINT3D pts[4]; float w, h; int i; newMesh(winMesh, IPREC * IPREC, IPREC * IPREC); // Width and height of each face w = (winTotalwidth) / (float)(IPREC + 1); h = winTotalheight; // Generate the mesh data. At equally spaced intervals along the x-axis, // we compute the z-position of the flag surface. pos.y = (float) 0.0; pos.z = (float) 0.0; for (i = 0, pos.x = xTrans; i < IPREC; i++, pos.x += w) { int faceCount = winMesh->numFaces; pts[0].x = (float)pos.x; pts[0].y = (float)(pos.y); pts[0].z = getZpos(pos.x); pts[1].x = (float)pos.x; pts[1].y = (float)(pos.y + h); pts[1].z = getZpos(pos.x); pts[2].x = (float)(pos.x + w); pts[2].y = (float)(pos.y); pts[2].z = getZpos(pos.x + w); pts[3].x = (float)(pos.x + w); pts[3].y = (float)(pos.y + h); pts[3].z = getZpos(pos.x + w); // Compute the face normal. calcNorm(&winMesh->faces[faceCount].norm, pts + 2, pts + 1, pts); // Add the face to the mesh. winMesh->faces[faceCount].material = 0; winMesh->faces[faceCount].p[0] = iPtInList(winMesh, 0, pts, &winMesh->faces[faceCount].norm, TRUE); winMesh->faces[faceCount].p[1] = iPtInList(winMesh, 0, pts + 1, &winMesh->faces[faceCount].norm, TRUE); winMesh->faces[faceCount].p[2] = iPtInList(winMesh, 0, pts + 2, &winMesh->faces[faceCount].norm, TRUE); winMesh->faces[faceCount].p[3] = iPtInList(winMesh, 0, pts + 3, &winMesh->faces[faceCount].norm, TRUE); winMesh->numFaces++; } // Normalize the vertex normals in the mesh. normalizeNorms(winMesh->norms, winMesh->numPoints); } /******************************Public*Routine******************************\ * initTexScene * * Initialize the screen saver. * * This function is exported to the main module in ss3dfo.c. * \**************************************************************************/ void initTexScene() { int i; float angleDelta; #ifdef UNICODE BOOL fUnicode = TRUE; #else BOOL fUnicode = FALSE; #endif // Initialize the transform. glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-0.25, 1.0, -0.25, 1.0, 0.0, 3.0); glTranslatef(0.0f, 0.0f, -1.5f); // Initialize and turn on lighting. glLightfv(GL_LIGHT0, GL_POSITION, light0Pos); glLightfv(GL_LIGHT1, GL_AMBIENT, (GLfloat *) &light1Ambient); glLightfv(GL_LIGHT1, GL_DIFFUSE, (GLfloat *) &light1Diffuse); glLightfv(GL_LIGHT1, GL_SPECULAR, (GLfloat *) &light1Specular); glLightfv(GL_LIGHT1, GL_POSITION, light1Pos); glEnable(GL_LIGHT1); glDisable(GL_DEPTH_TEST); // Leave OpenGL in a state ready to accept the model view transform (we // are going to have the flag vary its orientation from frame to frame). glMatrixMode(GL_MODELVIEW); // Define orientation of polygon faces. glFrontFace(GL_CW); // glEnable(GL_CULL_FACE); Frames = (int)((float)(MAX_FRAMES / 2) * fTesselFact); // Load texture from DIB file. Scale image to a power-of-2 size. image = tkDIBImageLoadAW((char *) achTexPathname, fUnicode); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); if (image) { double xPow2, yPow2; int ixPow2, iyPow2; int xSize2, ySize2; BYTE *pData; xPow2 = log((double)image->sizeX) / log((double)2.0); yPow2 = log((double)image->sizeY) / log((double)2.0); ixPow2 = (int)xPow2; iyPow2 = (int)yPow2; if (xPow2 != (double)ixPow2) ixPow2++; if (yPow2 != (double)iyPow2) iyPow2++; xSize2 = 1 << ixPow2; ySize2 = 1 << iyPow2; pData = LocalAlloc(LMEM_FIXED, xSize2 * ySize2 * 3 * sizeof(BYTE)); if (!pData) goto no_texture; gluScaleImage(GL_RGB, image->sizeX, image->sizeY, GL_UNSIGNED_BYTE, image->data, xSize2, ySize2, GL_UNSIGNED_BYTE, pData); glTexImage2D(GL_TEXTURE_2D, 0, 3, xSize2, ySize2, 0, GL_RGB, GL_UNSIGNED_BYTE, pData); LocalFree(image->data); image->data = pData; // Correct aspect ratio of flag to match image. // // The 1.4 and 0.7 are correction factors to account for the length of // the curve that models the surface ripple of the waving flag. These // factors are the lenghts of the curve at zero phase. It would be more // accurate to determine the length of the curve at each phase, but // this is a sufficient approximation for our purposes. if (image->sizeX > image->sizeY) { winTotalwidth = (float)0.75; winTotalheight = winTotalwidth * ((float) image->sizeY / (float) image->sizeX) * (float) 1.4; } else { winTotalheight = (float)0.75; winTotalwidth = winTotalheight * ((float) image->sizeX / (float) image->sizeY) * (float) 0.7; } } // Setup texturing parameters. glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); no_texture: if (Frames < 5) Frames = 5; if (Frames > MAX_FRAMES) Frames = MAX_FRAMES; // Generate the geometry data (stored in the array of mesh structures), // for each frame of the animation. The shape of the flag is varied by // changing the global variable sinAngle. angleDelta = (float)(2.0 * PI) / (float)Frames; sinAngle = (float) 0.0; for (i = 0; i < Frames; i++) { genTex(&winMesh[i]); sinAngle += angleDelta; } } /******************************Public*Routine******************************\ * delTexScene * * Cleanup the data associated with this screen saver. * * This function is exported to the main module in ss3dfo.c. * \**************************************************************************/ void delTexScene() { int i; for (i = 0; i < Frames; i++) delMesh(&winMesh[i]); if (image) { LocalFree(image->data); free(image); } } /******************************Public*Routine******************************\ * updateTexScene * * Generate a scene by taking one of the meshes and rendering it with * OpenGL. * * This function is exported to the main module in ss3dfo.c. * \**************************************************************************/ void updateTexScene(HWND hwnd, int flags) { MESH *mesh; MFACE *faces; int i; static double mxrot = 23.0; static double myrot = 23.0; static double mzrot = 5.7; static double mxrotInc = 0.0; static double myrotInc = 3.0; static double mzrotInc = 0.0; static int h = 0; static int frameNum = 0; POINT3D *pp; POINT3D *pn; int lastC, lastD; int aOffs, bOffs, cOffs, dOffs; int a, b; GLfloat s = (GLfloat) 0.0; GLfloat ds; // In addition to having the flag wave (an effect acheived by switching // meshes from frame to frame), the flag changes its orientation from // frame to frame. This is done by applying a model view transform. glLoadIdentity(); glRotatef((float)mxrot, 1.0f, 0.0f, 0.0f); glRotatef((float)myrot, 0.0f, 1.0f, 0.0f); glRotatef((float)mzrot, 0.0f, 0.0f, 1.0f); // Divide the texture into IPREC slices. ds is the texture coordinate // delta we apply as we move along the x-axis. ds = (GLfloat)1.0 / (GLfloat)IPREC; // Setup the material property of the flag. The material property, light // properties, and polygon orientation will interact with the texture. curMatl = 0; // glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, &flagColors[0]); // glMaterialfv(GL_FRONT, GL_SPECULAR, &matlBrightSpecular); // glMaterialf(GL_FRONT, GL_SHININESS, 60.0); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &flagColors[0]); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &matlBrightSpecular); glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, (float) 60.0); // Pick the mesh for the current frame. mesh = &winMesh[frameNum]; // Take the geometry data is the mesh and convert it to a single OpenGL // quad strip. If smooth shading is required, use the vertex normals stored // in the mesh. Otherwise, use the face normals. // // As we define each vertex, we also define a corresponding vertex and // texture coordinate. glBegin(GL_QUAD_STRIP); pp = mesh->pts; pn = mesh->norms; for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1]; i < mesh->numFaces; i++, faces++) { a = faces->p[0]; b = faces->p[1]; if (!bSmoothShading) { // Since flag is a single quad strip, this isn't needed. // But lets keep it in case we ever change to a more // complex model (ie., one that uses more than one quad // strip). #if 0 if ((a != lastC) || (b != lastD)) { glNormal3fv((GLfloat *)&(faces - 1)->norm); glTexCoord2f(s, (float) 0.0); glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2))); glTexCoord2f(s, (float) 1.0); glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2))); s += ds; glEnd(); glBegin(GL_QUAD_STRIP); } #endif if (faces->material != curMatl) { curMatl = faces->material; glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &matlNoSpecular); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &flagColors[curMatl]); } glNormal3fv((GLfloat *)&faces->norm); glTexCoord2f(s, (float) 0.0); glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2))); glTexCoord2f(s, (float) 1.0); glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2))); s += ds; } else { // Since flag is a single quad strip, this isn't needed. // But lets keep it in case we ever change to a more // complex model (ie., one that uses more than one quad // strip). #if 0 if ((a != lastC) || (b != lastD)) { cOffs = (lastC << 3) + (lastC << 2); dOffs = (lastD << 3) + (lastD << 2); glTexCoord2f(s, (float) 0.0); glNormal3fv((GLfloat *)((char *)pn + cOffs)); glVertex3fv((GLfloat *)((char *)pp + cOffs)); glTexCoord2f(s, (float) 1.0); glNormal3fv((GLfloat *)((char *)pn + dOffs)); glVertex3fv((GLfloat *)((char *)pp + dOffs)); s += ds; glEnd(); glBegin(GL_QUAD_STRIP); } #endif aOffs = (a << 3) + (a << 2); bOffs = (b << 3) + (b << 2); if (faces->material != curMatl) { curMatl = faces->material; glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &matlNoSpecular); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &flagColors[curMatl]); } glTexCoord2f(s, (float) 0.0); glNormal3fv((GLfloat *)((char *)pn + aOffs)); glVertex3fv((GLfloat *)((char *)pp + aOffs)); glTexCoord2f(s, (float) 1.0); glNormal3fv((GLfloat *)((char *)pn + bOffs)); glVertex3fv((GLfloat *)((char *)pp + bOffs)); s += ds; } lastC = faces->p[2]; lastD = faces->p[3]; } if (!bSmoothShading) { glNormal3fv((GLfloat *)&(faces - 1)->norm); glTexCoord2f(s, (float) 0.0); glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2))); glTexCoord2f(s, (float) 1.0); glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2))); } else { cOffs = (lastC << 3) + (lastC << 2); dOffs = (lastD << 3) + (lastD << 2); glTexCoord2f(s, (float) 0.0); glNormal3fv((GLfloat *)((char *)pn + cOffs)); glVertex3fv((GLfloat *)((char *)pp + cOffs)); glTexCoord2f(s, (float) 1.0); glNormal3fv((GLfloat *)((char *)pn + dOffs)); glVertex3fv((GLfloat *)((char *)pp + dOffs)); } glEnd(); // Transfer the image to the floating OpenGL window. vShowBuffer(hwnd); // Determine the flag orientation for the next frame. // What we are doing is an oscillating rotation about the y-axis // (mxrotInc and mzrotInc are currently 0). mxrot += mxrotInc; myrot += myrotInc; mzrot += mzrotInc; if ((myrot < -65.0) || (myrot > 25.0)) myrotInc = -myrotInc; frameNum++; if (frameNum >= Frames) frameNum = 0; }