/* * Copyright (C) 1997-2003, R3vis Corporation. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, * or visit http://www.gnu.org/copyleft/gpl.html. * * Contributor(s): * Wes Bethel, R3vis Corporation, Marin County, California * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: tfly.c,v 1.17 2004/02/23 02:58:02 wes Exp $ * Version: $Name: OpenRM-1-5-2-RC1 $ * $Revision: 1.17 $ * $Log: tfly.c,v $ * Revision 1.17 2004/02/23 02:58:02 wes * Many updates: * (1) Added sky field using image of cassiopeia, * (2) added boundary walls using a panorma of an Illinois prairie, * (3) changed placement of posts and generation of floor to a radial grid. * * Revision 1.16 2004/01/17 03:19:08 wes * No significant changes. * * Revision 1.15 2003/12/06 03:26:47 wes * Added command line argument "-fr NN" to permit use of constant-frame * rendering capabilities. * * Revision 1.14 2003/07/20 14:56:57 wes * Minor tweaks to fix compile warnings on Solaris. * * Revision 1.13 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.12 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.11 2003/01/16 22:22:45 wes * Updated all source files to reflect new organization of header files: all * headers that were formerly located in include/rmaux, include/rmv and * include/rmi are now located in include/rm. * * Revision 1.10 2002/06/17 00:44:37 wes * Added -s command line flag to generate per-frame rendering statistics. * Added -n NN command line argument to set the number of cylinders * that are generated for the scene graph. * * Revision 1.9 2001/07/15 22:33:19 wes * Added rmPipeDelete to the end of all demo progs. For those that use * an initfunc, added a new RMnode * parm (which is unused, except for rm2screen). * * Revision 1.8 2001/06/03 19:44:05 wes * Add calls to new rmaux routines to handle window resize events, and * for keyboard event handling. * * Revision 1.7 2001/03/31 16:55:18 wes * Added procmode.h, which defines an RMpipe processing mode used in * most demonstration programs. The default processing mode is * RM_PIPE_MULTISTAGE_VIEW_PARALLEL. * * Revision 1.6 2000/12/02 17:24:32 wes * Version 1.4.0-alpha-1 checkin. See the RELEASENOTES file for * a summary of changes. With this checkin, these demo programs * are no longer compatible with versions of the OpenRM API that * are pre-1.4.0. * * Revision 1.5 2000/09/02 19:26:36 wes * Changed sky to RM_SHADER_NOLIGHT. * * Revision 1.4 2000/08/31 02:59:07 wes * Added new parms to Win32 rmauxEventLoop. * * Revision 1.3 2000/08/28 01:38:18 wes * Updated rmaux* interfaces - rmauxEventLoop now takes two additional * parameters (keypress and window resize app callbacks); replaced * rmauxUI with rmauxSetGeomTransform and, where appropriate, * rmauxSetCamera3DTransform. * * Revision 1.2 2000/04/20 18:04:34 wes * Minor tweaks and reorg for OpenRM 1.2.1. * * Revision 1.1.1.1 2000/02/28 21:55:30 wes * OpenRM 1.2 Release * * Revision 1.2 2000/02/28 17:21:55 wes * RM 1.2, pre-OpenRM * */ #define NUM_POSTS 512 #include <stdio.h> #ifdef RM_X #include <strings.h> #endif #include <stdlib.h> #include <rm/rm.h> #include <rm/rmv.h> #include <rm/rmi.h> #include <rm/rmaux.h> #include "procmode.h" static int imgWidth=640, imgHeight=512; RMnode *myRoot=NULL; RMnode *myOpaqueRoot=NULL, *myTransparentRoot=NULL; RMnode *myPostsRoot=NULL; int doFog = 0; static char cloudsTextureFilename[]={"data/clouds.x"}; static char panoramaTextureFilename[]={"data/prairie.jpg"}; static char ceilingTextureFilename[]={"data/cassio.jpg"}; /* * this is an array of pointers to the post primitives. we create * the array inside the addPostsToFloor() routine, and use the * pointers later during the traversal callback. */ static RMprimitive **postPrims=NULL; static int *postPrimIndices=NULL; static float *lastDistance=NULL; static int doStats=0; static int numPosts=NUM_POSTS; static int frameRate = -1; void usage(char *progName) { char buffer[1024]; sprintf(buffer," usage: %s [-w imgWidth (default=640)] [-h imgHeight (default=512)] [-f (turn on fog - see tfly.c code for fogging details )] [-s (turn on frame rate statistics)] [-n NNN (set number of cylinders to NNN, default is %d)] [-fr NN (set frame rate to NN frames/second. Default is to go as fast as possible.)]\n ",progName, NUM_POSTS); rmWarning(buffer); } int parseArgs(int ac, char *av[]) { int i; i = 1; ac--; while (ac > 0) { if (strcmp(av[i],"-w") == 0) { i++; ac--; sscanf(av[i],"%d",&imgWidth); } else if (strcmp(av[i],"-h") == 0) { i++; ac--; sscanf(av[i],"%d",&imgHeight); } else if (strcmp(av[i],"-n") == 0) { i++; ac--; sscanf(av[i],"%d",&numPosts); } else if (strcmp(av[i],"-f") == 0) { doFog = 1; } else if (strcmp(av[i],"-s") == 0) { doStats = 1; } else if (strcmp(av[i],"-fr") == 0) { i++; ac--; sscanf(av[i],"%d", &frameRate); if (frameRate < 0) { fprintf(stderr," please set the framerate value to be a positive integer indicating the number of frames per second. \n"); return -1; } } else { usage(av[0]); return(-1); } i++; ac--; } return(1); } /* * the scene for this demo program consists of a floor, and * some simple geometric objects. the floor is positioned at * z=0, and is the x/y plane. the range of the floor is from * (-10,-10,0) to (10,10,0). a simple auto-gen'ed texture is * applied to the floor (a checkered pattern). * * the initial location of the camera is at (0,0,1) looking * in the (0,1,0) direction. */ void addFloorTileTexture(RMnode *n) { /* * create an RMimage consisting of a simple checkered pattern, * then create an RMtexture using the new image, add the * texture as a scene parm to the node */ #define USE_MIPMAPS 0 #if USE_MIPMAPS RMimage **mipMaps; int nmipmaps; #endif RMimage *t; int i,j; unsigned char *dst; RMcolor4D blue={0.1,0.1,0.4,1.0}; RMcolor4D white={0.9,0.9,0.9,1.0}; RMtexture *texture; unsigned char ubBlue[4], ubWhite[4]; int rows=256,cols=256; t = rmImageNew(2, cols, rows, 1, RM_IMAGE_RGBA, RM_UNSIGNED_BYTE, RM_COPY_DATA); dst = (unsigned char *)rmImageGetPixelData(t); ubBlue[0] = (unsigned char)(blue.r * 255.0); ubBlue[1] = (unsigned char)(blue.g * 255.0); ubBlue[2] = (unsigned char)(blue.b * 255.0); ubBlue[3] = (unsigned char)(blue.a * 255.0); ubWhite[0] = (unsigned char)(white.r * 255.0); ubWhite[1] = (unsigned char)(white.g * 255.0); ubWhite[2] = (unsigned char)(white.b * 255.0); ubWhite[3] = (unsigned char)(white.a * 255.0); for (j=0;j<rows;j++) { for (i=0;i<cols;i++) { if (j & 0x08) { if (i & 0x08) memcpy(dst,ubBlue,4); else memcpy(dst,ubWhite,4); } else { if (i & 0x08) memcpy(dst,ubWhite,4); else memcpy(dst,ubBlue,4); } dst += 4; } } texture = rmTextureNew(2); #if USE_MIPMAPS nmipmaps = rmuImageBuildMipmaps(t,&mipMaps,RM_SOFTWARE); rmTextureSetImages(texture,mipMaps, nmipmaps, RM_FALSE); rmTextureSetFilterMode(texture,GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST); #else rmTextureSetImages(texture, &t, 1, RM_FALSE); /* * set the OpenGL texture minification mode to GL_LINEAR, * texture magnification mode to GL_NEAREST. see man glTexParameteri. * * when the following line of code is commented out, we're using * the default OpenGL texture filter modes which are GL_NEAREST, GL_NEAREST */ /* rmTextureSetFilterMode(texture,GL_LINEAR, GL_NEAREST); */ #endif rmNodeSetSceneTexture(n,texture); rmTextureDelete(texture, RM_FALSE); } void addFloor2(RMnode *addTo, float radius, float height, int subdivisions) { RMvertex3D *v = NULL; RMvertex3D *n = NULL; RMvertex2D *tc = NULL; RMvertex3D refNormal={0.0F, 0.0F, 1.0F}; int i; double c, s, a, da, t, dt; RMprimitive *p = NULL; RMnode *floor = NULL; v = rmVertex3DNew(subdivisions+2); n = rmVertex3DNew(subdivisions+2); tc = rmVertex2DNew(subdivisions+2); a = 0.0; da = 2.0*RM_PI/(double)(subdivisions-1); t = 0.0; dt = 1./(double)(subdivisions-1); v[0].x = v[0].y = 0.0F; v[0].z = height; n[0] = refNormal; tc[0].x = 0.5; tc[0].y = 0.5; for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt) { c = cos(a); s = sin(a); v[i].x = c * radius; v[i].y = s * radius; v[i].z = height; n[i] = refNormal; tc[i].x = (c + 1.0)*0.5; tc[i].y = (s + 1.0)*0.5; } floor = rmNodeNew("floor", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_TRIANGLE_FAN); rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL); rmPrimitiveSetNormal3D(p, subdivisions+2, n, RM_COPY_DATA, NULL); rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL); rmNodeAddPrimitive(floor, p); rmNodeAddChild(addTo, floor); addFloorTileTexture(floor); } void addFloor(RMnode *addToNode, RMvertex3D *fmin, RMvertex3D *fmax) { RMnode *floor; RMprimitive *p; RMvertex3D v[4]; RMvertex2D tc[4]; RMvertex3D n; floor = rmNodeNew("floor",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_QUADS); /* * note that we're using the min z coordinate for the entire surface. */ v[0].x = fmin->x; tc[0].x = 0.0; v[0].y = fmin->y; tc[0].y = 0.0; v[0].z = fmin->z; v[1].x = fmax->x; tc[1].x = 1.0; v[1].y = fmin->y; tc[1].y = 0.0; v[1].z = fmin->z; v[2].x = fmax->x; tc[2].x = 1.0; v[2].y = fmax->y; tc[2].y = 1.0; v[2].z = fmin->z; v[3].x = fmin->x; tc[3].x = 0.; v[3].y = fmax->y; tc[3].y = 1.; v[3].z = fmin->z; n.x = n.y = 0.; n.z = 1.0; rmPrimitiveSetVertex3D(p,4,v,RM_COPY_DATA,NULL); rmPrimitiveSetNormal3D(p,1,&n,RM_COPY_DATA,NULL); rmPrimitiveSetTexcoord2D(p,4,tc,RM_COPY_DATA,NULL); addFloorTileTexture(floor); rmNodeAddPrimitive(floor,p); rmNodeAddChild(addToNode,floor); } RMimage * readAVSImage(char *fname) { RMimage *t; int w,h; int i,j,indx, indx2; FILE *f; unsigned char *static_image_data; f = fopen(fname,"r"); if (f == NULL) { fprintf(stderr," can't open the AVS image file named <%s> \n",fname); return(NULL); } { unsigned char buf[4]; read(fileno(f),(void *)buf,4); w = buf[3] | (buf[2] << 8); read(fileno(f),(void *)buf,4); h = buf[3] | (buf[2] << 8); } /* sanity check w,h */ if ((w < 0) || (w > 4096) || (h < 0) || (h > 4096)) { fprintf(stderr," dubious image dimensions: %d, %d, aborting image read. \n",w,h); fclose(f); return(NULL); } static_image_data = (unsigned char *)malloc(sizeof(unsigned char)*w*h*4); /* this reads everything at once */ read(fileno(f),(void *)static_image_data,w*h*4); fclose(f); #define ONE_OVER_255 0.0039215686 /* hack to change from ARGB to RGBA */ indx = 0; indx2 = 3; for (i=0;i<w*h;i++) { unsigned char t, maxAlpha; t = static_image_data[indx]; for (j=0;j<3;j++) static_image_data[indx+j] = static_image_data[indx+j+1]; /* maxAlpha = (unsigned char)((float)static_image_data[indx] * .4 + (float)static_image_data[indx+1]*.5 + (float)static_image_data[indx+2] * .1); static_image_data[indx+3] = maxAlpha; */ /* static_image_data[indx+3] = 0xFF; */ static_image_data[indx+3] = RM_MIN(static_image_data[indx] * 2, 0xFF); indx += 4; } /* * problem w/RGB mode in glDrawPixels . it looks like it wants all * the image data to be 4-byte word aligned. */ t = rmImageNew(2,w,h,1,RM_IMAGE_RGBA, RM_UNSIGNED_BYTE, RM_COPY_DATA); rmImageSetPixelData(t, (void *)static_image_data, RM_COPY_DATA, NULL); rmImageMirror(t,RM_IMAGE_MIRROR_HEIGHT); free((void *)static_image_data); return(t); } void addCloudTexture(RMnode *addToNode, char *fname) { RMimage *img; RMtexture *texture; img = readAVSImage(fname); texture = rmTextureNew(2); rmTextureSetImages(texture, &img, 1, RM_FALSE); rmTextureSetWrapMode(texture, GL_REPEAT); rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR); rmNodeSetSceneTexture(addToNode,texture); rmTextureDelete(texture, RM_FALSE); } void addCloudLayer(RMnode *addTo, float radius, float height, int subdivisions, float tcOffsetU, float tcOffsetV) { RMvertex3D *v = NULL; RMvertex2D *tc = NULL; int i; double c, s, a, da, t, dt; RMprimitive *p = NULL; RMnode *sky = NULL; RMcolor4D unlit={1.0,1.0,1.0,0.75}; v = rmVertex3DNew(subdivisions+2); tc = rmVertex2DNew(subdivisions+2); a = 0.0; da = 2.0*RM_PI/(double)(subdivisions); t = 0.0; dt = 1./(double)(subdivisions); v[0].x = v[0].y = 0.0F; v[0].z = height; tc[0].x = 0.5 + tcOffsetU; tc[0].y = 0.5 + tcOffsetV; for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt) { c = cos(a); s = sin(a); v[i].x = c * radius; v[i].y = s * radius; v[i].z = height; tc[i].x = (c + 1.0)*0.5 + tcOffsetU; tc[i].y = (s + 1.0)*0.5 + tcOffsetV; } sky = rmNodeNew("skylayer", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT); p = rmPrimitiveNew(RM_TRIANGLE_FAN); rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL); rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL); rmNodeSetUnlitColor(sky, &unlit); rmNodeAddPrimitive(sky, p); rmNodeAddChild(addTo, sky); addCloudTexture(sky, cloudsTextureFilename); } void addCamera(RMnode *addToNode, int imgWidth, int imgHeight) { RMcamera3D *c = rmCamera3DNew(); RMvertex3D eye, at, up; float fov, hither, yon; eye.x = eye.y = 0.; eye.z = 1.0; at = eye; at.y += 1.0; up.x = up.y = 0.; up.z = 1.; fov = 45.0F; hither = 0.25; yon = 40.0; rmCamera3DSetProjection(c,RM_PROJECTION_PERSPECTIVE); rmCamera3DSetEye(c,&eye); rmCamera3DSetUpVector(c,&up); rmCamera3DSetAt(c,&at); rmCamera3DSetFOV(c,fov); rmCamera3DSetHither(c, hither); rmCamera3DSetYon(c,yon); rmCamera3DSetFocalDistance(c,1.0F); rmCamera3DSetAspectRatio(c,(float)imgWidth/(float)imgHeight); rmNodeSetSceneCamera3D(addToNode,c); rmCamera3DDelete(c); } void addLighting(RMnode *toModify) { RMlight *l0; /* * light source is a directional light pointing from the +Z direction */ RMcolor4D diffuse = { .7,.7,.7,1.0}; RMcolor4D specular = { .5,0.5,0.5,1.}; RMcolor4D diffuse2 = { .3, 0.3, 0.5, 1.0 }; RMvertex3D direction = { 0.5, -0.5, 1.0}; l0 = rmLightNew(); rmLightSetType(l0, RM_LIGHT_DIRECTIONAL); rmLightSetColor(l0, NULL, &diffuse, &specular); rmLightSetXYZ (l0, &direction); rmNodeSetSceneLight(toModify, RM_LIGHT0, l0); rmLightDelete(l0); /* then set the light model parms. */ { RMcolor4D defAmbient = { .2,.2,.2,1.0}; RMlightModel *lm; lm = rmLightModelNew(); rmLightModelSetAmbient(lm, &defAmbient); rmLightModelSetTwoSided (lm, RM_FALSE); rmLightModelSetLocalViewer(lm, RM_FALSE); rmNodeSetSceneLightModel(toModify, lm); rmLightModelDelete (lm); } } void addFog(RMnode *n) { RMfog *f; RMcolor4D fogColor={0.15,0.06,0.02,1.0}; f = rmFogNew(); rmFogSetDensity(f,0.15F); rmFogSetDensity(f,0.05F); rmFogSetMode(f,GL_EXP); #if 0 /* don't use linear fog - most opengl implementations don't do it correctly, whereas most do GL_EXP & GL_EXP2 correctly. */ rmFogSetMode(f,GL_LINEAR); rmFogSetStartEnd(f, 10.0F, 40.0F); #endif rmFogSetColor(f,&fogColor); rmNodeSetSceneFog(n,f); } int myPreTraversalCallback(const RMnode *n, const RMstate *s) { int i; float v[4],d[4]; RMvertex3D c; RMprimitive *p; RMmatrix renderStateViewAndProjectionMatrix; int *indxPtr, indx; int isVisible; #define distanceThreshold 10.0F /* * this routine implements two things: * 1. rudimentary view frustum culling * 2. model property switching/modification based upon view * dependent parms. when a given object comes closer to the * viewer than a predefined threshold (define above, units * measured in world coords) we will change two things about * the object: a) we'll change it's color to yellow, and b) * we'll use a higher-res tesselation of the underlying * procedural model. these two things implement a form of * rudimentary view-dependent LOD control. */ isVisible = rmNodeFrustumCullCallback(n,s); if (isVisible == 0) return 0; /* * for frustum culling, we're going to use the Node's center point. * a more robust test would use an area-based metric (eg, bounding * box or sphere). */ rmNodeGetCenter(n,&c); v[0] = c.x; v[1] = c.y; v[2] = c.z; v[3] = 1.F; /* * transform this point through the combined view+projection matrix * supplied to us via the RMstate object. we don't need to worry * about the model matrix since we're not modifying any model * matrices in this demo. */ { const RMmatrix *mv, *p; mv = rmStateGetModelViewMatrix(s); p = rmStateGetProjectionMatrix(s); rmMatrixMultiply(mv,p,&renderStateViewAndProjectionMatrix); } rmPoint4MatrixTransform(v,&renderStateViewAndProjectionMatrix,d); /* * the coordinate contained in "d" is now in "unscaled" ndc space. * need more documentation here. */ #if 0 if ((d[2] < 0.) || (d[2] > d[3])) /* behind us or too far away */ isVisible = 0; else if ((fabs(d[0]) > d[2]) || (fabs(d[1]) > d[2])) /* outside view volume? */ isVisible = 0; else isVisible = 1; if (isVisible == 0) return(0); /* object "not visible", don't process this node */ #endif indxPtr = (int *)rmNodeGetClientData(n); indx = *indxPtr; p = postPrims[indx]; if (d[2] <= distanceThreshold) { RMcolor4D c={1.0,1.0,0.0,1.0}; /* * only change color when we cross the threshold. when the object * is closer to us than the threshold, we'll ask for a cylinder with * a higher res tesselation level than is used when they're farther * away from us. */ if ((lastDistance[indx] > distanceThreshold) || (lastDistance[indx] == RM_MINFLOAT)) { rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL); rmPrimitiveSetModelFlag(p,RM_CYLINDERS_16); } } else { RMcolor4D c={1.0,1.0,1.0,1.0}; /* * only change color when we cross the threshold */ if ((lastDistance[indx] <= distanceThreshold) || (lastDistance[indx] == RM_MINFLOAT)) { rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL); rmPrimitiveSetModelFlag(p,RM_CYLINDERS_4); } } lastDistance[indx] = d[2]; /* * improvements that could be made here include keeping around info * on each prim so that we wouldn't need to set the color each time * of every single primitive. */ return(1); } RMnode * makePost2(int nthPost, /* which post */ float worldRadius, float zlimit, /* the distance between floor and sky */ RMprimitive **appPrimArray, /* the static app RMprim * array */ int *indexList) { RMprimitive *p; RMnode *n; char buf[32]; int r1,r2,r3,r4; RMvertex3D v[2]; RMcolor4D c={1.0F,1.0F,1.0F,1.0F}; float radius,scale; double a, cs, ss, tr; sprintf(buf,"Post%d",nthPost); n = rmNodeNew(buf,RM_RENDERPASS_3D,RM_RENDERPASS_OPAQUE); *appPrimArray = p = rmPrimitiveNew(RM_CYLINDERS); /* * use the client data attribute of RMprimitives to store some * info for later use. we're storing an address which is the * location of an integer, that integer contains an index * into an array of RMprimitive pointers. we'll use that info * during the pretraversal callback if the primitive is visible. */ rmNodeSetClientData(n,(void *)(indexList),NULL); /* rand numbers in range 0..32767 */ r1 = rand() & 0x07FFF; r2 = rand() & 0x07FFF; r3 = rand() & 0x07FFF; r4 = rand() & 0x07FFF; /* * r1 & r2 are in the range 0..32767. * We will first convert them to the range -1..1, * then use them as input to acos() and asin() to produce an * x/y plan-view coordinate. */ /* * convert r1 from the range 0..32767 to the range 0..2pi, * use the value to place the post at some theta position */ a = (double)(r1)/32767.0; /* 0..1 */ a *= 2.0*RM_PI; /* 0..2pi */ tr = (double)(r2)/32767.0; /* 0..1 */ cs = cos(a) * worldRadius * tr; ss = sin(a) * worldRadius * tr; v[0].x = cs; v[0].y = ss; v[0].z = 0.0; /* * r3 gives us the height of the post. we must convert from * 0..32767 to the range defined by zlimit. */ v[1] = v[0]; scale = zlimit/32767.0F; v[1].z = (float)r3 * scale; /* * r4 gives us the radius. we'll convert from 0..32767 to * a maximum of 1/60 the input world radius value. * this choice is completely arbitrary. */ scale = (worldRadius*.5)/(32767.0F * 60.0F); radius = (float)r4 * scale; rmPrimitiveSetVertex3D(p,2,v,RM_COPY_DATA,NULL); rmPrimitiveSetRadii(p,1,&radius,RM_COPY_DATA,NULL); /* do color later */ rmPrimitiveSetColor4D(p,1,&c,RM_COPY_DATA,NULL); rmPrimitiveSetModelFlag(p,RM_CYLINDERS_4); rmNodeAddPrimitive(n,p); { RMvertex3D center; center.x = v[0].x + 0.5 * (v[1].x - v[0].x); center.y = v[0].y + 0.5 * (v[1].y - v[0].y); center.z = v[0].z + 0.5 * (v[1].z - v[0].z); rmNodeSetCenter(n,¢er); } rmNodeComputeBoundingBox(n); rmNodeComputeCenterFromBoundingBox(n); rmNodeSetPreTraversalCallback(n,RM_VIEW, myPreTraversalCallback); return(n); } RMnode * addPostsToFloor2(RMnode *addToNode, int nposts, float radius, float zlimit) { RMnode *postsRoot; int i; /* * in this routine, we're going to create a bunch of objects that * will sit on the floor - gives us something to look at while we * fly around. each object is going to be added as a node to the * postsRoot object. we're using one node per object so we can play * some games using view-dependent operations. */ postsRoot = rmNodeNew("postsRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); rmNodeAddChild(addToNode, postsRoot); if (postPrimIndices != NULL) free((void *)postPrimIndices); if (postPrims != NULL) free((void *)postPrims); if (lastDistance != NULL) free((void *)lastDistance); postPrims = (RMprimitive **)malloc(sizeof(RMprimitive *)*nposts); postPrimIndices = (int *)malloc(sizeof(int)*nposts); lastDistance = (float *)malloc(sizeof(float)*nposts); /* * */ srand(time(NULL)); for (i=0;i<nposts;i++) { RMnode *post; postPrimIndices[i] = i; lastDistance[i] = RM_MINFLOAT; /* an unlikely-to-occur number */ rmNodeAddChild(postsRoot, makePost2(i,radius*0.75,zlimit,postPrims+i, postPrimIndices+i)); } } void myrenderfunc(RMpipe *p, RMnode *n) { if (doStats != 0) rmStatsStartTime(); rmFrame(p, n); if (doStats != 0) { rmStatsEndTime(); rmStatsPrint(); } } void addTopCap(RMnode *addTo, float radius, float baseHeight, float centerHeight, int subdivisions) { RMvertex3D *v = NULL; RMvertex2D *tc = NULL; int i; double c, s, a, da, t, dt; RMprimitive *p = NULL; RMnode *n = NULL; RMimage *textureImage = rmiReadJPEG(ceilingTextureFilename); RMtexture *texture = rmTextureNew(2); v = rmVertex3DNew(subdivisions+2); tc = rmVertex2DNew(subdivisions+2); a = 0.0; da = 2.0*RM_PI/(double)(subdivisions-1); t = 0.0; dt = 1./(double)(subdivisions-1); v[0].x = v[0].y = 0.0F; v[0].z = centerHeight; tc[0].x = tc[0].y = 0.5; for (i=1; i <= subdivisions+1; i++, a+=da, t+= dt) { c = cos(a); s = sin(a); v[i].x = c * radius; v[i].y = s * radius; v[i].z = baseHeight; tc[i].x = (c + 1.0)*0.5; tc[i].y = (s + 1.0)*0.5; } n = rmNodeNew("topCap", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_TRIANGLE_FAN); rmPrimitiveSetVertex3D(p, subdivisions+2, v, RM_COPY_DATA, NULL); rmPrimitiveSetTexcoord2D(p, subdivisions+2, tc, RM_COPY_DATA, NULL); rmTextureSetImages(texture, &textureImage, 1, 0); rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR); rmNodeSetSceneTexture(n, texture); rmNodeAddPrimitive(n, p); rmNodeAddChild(addTo, n); rmImageDelete(textureImage); rmTextureDelete(texture, RM_TRUE); } void addOutsideBoundaries(RMnode *addTo, float radius, float height, int subdivisions) { RMvertex3D *v = NULL; RMvertex2D *tc = NULL; int i; double c, s, a, da, t, dt; RMprimitive *p = NULL; RMnode *n = NULL; RMimage *textureImage = rmiReadJPEG(panoramaTextureFilename); RMtexture *texture = rmTextureNew(2); v = rmVertex3DNew((subdivisions+1)*2); tc = rmVertex2DNew((subdivisions+1)*2); a = 0.0; da = 2.0*RM_PI/(double)(subdivisions-1); t = 0.0; dt = 1./(double)(subdivisions-1); for (i=0; i <= subdivisions; i++, a+=da, t+= dt) { c = cos(a) * radius; s = sin(a) * radius; v[i*2].x = c; v[i*2].y = s; v[i*2].z = 0.0; tc[i*2].x = t; tc[i*2].y = 1.0; v[i*2+1].x = c; v[i*2+1].y = s; v[i*2+1].z = height; tc[i*2+1].x = t; tc[i*2+1].y = 0.39; } n = rmNodeNew("outsideBoundary", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_QUAD_STRIP); rmPrimitiveSetVertex3D(p, (subdivisions+1)*2, v, RM_COPY_DATA, NULL); rmPrimitiveSetTexcoord2D(p, (subdivisions+1)*2, tc, RM_COPY_DATA, NULL); rmTextureSetImages(texture, &textureImage, 1, 0); rmTextureSetFilterMode(texture, GL_LINEAR, GL_LINEAR); rmNodeSetSceneTexture(n, texture); rmNodeAddPrimitive(n, p); rmNodeAddChild(addTo, n); rmImageDelete(textureImage); rmTextureDelete(texture, RM_TRUE); } void myInitFunc(RMpipe *p, RMnode *n) { float orientScale, translateScale; RMvertex3D floorMin, floorMax; RMvertex3D skyMin, skyMax; RMpipe *currentPipe; int imgWidth, imgHeight; float worldRadius=60.0F; int subdivisions = 90; currentPipe = p; rmPipeGetWindowSize(currentPipe, &imgWidth, &imgHeight); orientScale = 1./30.0; translateScale = 1./60.0; myRoot = rmNodeNew("myRoot",RM_RENDERPASS_ALL, RM_RENDERPASS_ALL); myOpaqueRoot = rmNodeNew("opaqueRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); myTransparentRoot = rmNodeNew("transparentRoot", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT); rmNodeAddChild(myRoot, myOpaqueRoot); rmNodeAddChild(myRoot, myTransparentRoot); /* * define the extents of the floor object */ floorMin.x = -30.0; floorMin.y = -30.0; floorMin.z = 0.0F; floorMax.x = 30.0; floorMax.y = 30.0; floorMax.z = 0.0F; addFloor2(myOpaqueRoot,worldRadius, 0.0F, subdivisions); /* addFloor(myOpaqueRoot,&floorMin, &floorMax); */ /* * define the extents of the sky object * note that the floor and sky are basically just parallel surfaces */ skyMin.x = -30.0; skyMin.y = -30.0; skyMin.z = 3.0F; skyMax.x = 30.0; skyMax.y = 30.0; skyMax.z = 3.0F; addCloudLayer(myTransparentRoot,worldRadius, 3.0F, subdivisions, 0.0F, 0.0F); addCloudLayer(myTransparentRoot,worldRadius, 5.0F, subdivisions, 0.1F, 0.1F); skyMin.z = skyMax.z = 2.0; addPostsToFloor2(myOpaqueRoot, numPosts, worldRadius, skyMin.z - floorMin.z); addOutsideBoundaries(myOpaqueRoot, worldRadius, 24.0F, subdivisions); addTopCap(myOpaqueRoot, worldRadius, 24.0F, 48.0F, subdivisions);; addCamera(rmRootNode(), imgWidth, imgHeight); /* * set handler to reset aspect ratio when the window is resized. */ rmauxSetResizeFunc(p, rmRootNode(), rmauxDefaultResizeFunc); addLighting(rmRootNode()); if (doFog == 1) addFog(rmRootNode()); rmauxFlyUI(rmRootNode(),rmRootNode(),p, orientScale, translateScale); rmNodeAddChild(rmRootNode(), myRoot); /* * modify the rmRootNode() so that it is processed ONLY for * the 3D opaque rendering pass. */ rmNodeSetTraversalMaskDims(rmRootNode(), RM_RENDERPASS_3D); /* rmNodeSetTraversalMaskOpacity(rmRootNode(), RM_RENDERPASS_OPAQUE); */ /* * or, we coule accomplish the same thing by modifying the * RMpipe to have only a single rendering pass: 3D, opaque */ { RMcolor4D bg={0.1,0.1,0.15,1.0}; /* * assign a background color to take effect at "myOpaqueRoot" */ rmNodeSetSceneBackgroundColor(myOpaqueRoot, &bg); } { RMvertex3D bmin, bmax; bmin.x = bmin.y = -10.; bmin.z = 0.0; /* temp */ bmax.x = bmax.y = 10.0; bmax.z = 1.0; /* temp */ rmNodeSetBoundingBox(rmRootNode(), &bmin, &bmax); rmNodeComputeCenterFromBoundingBox(rmRootNode()); } if (doStats != 0) rmStatsComputeDemography(rmRootNode()); if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE) rmFrame(p, rmRootNode()); rmFrame(p, rmRootNode()); } #ifdef RM_WIN int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND w; void *fptr; int status; RMpipe *pipe = NULL; RMenum stereoFormat = RM_MONO_CHANNEL; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_WGL; if (parseArgs(__argc, __argv) < 0) exit(1); #else /* assume RM_X */ int main(int ac, char *av[]) { Window w; RMenum stereoFormat = RM_MONO_CHANNEL; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_GLX; RMpipe *pipe=NULL; if (parseArgs(ac,av) < 0) exit(1); #endif rmInit(); pipe = rmPipeNew(targetPlatform); rmPipeSetProcessingMode(pipe, processingMode); rmPipeSetProcessingMode(pipe, RM_PIPE_MULTISTAGE_PARALLEL); rmPipeSetFrameRate(pipe, frameRate); #ifdef RM_WIN fptr = (void *)(rmauxWndProc); w = rmauxCreateW32Window(pipe, NULL, /* no parent window */ 20,20,imgWidth,imgHeight,"Win32 tfly", hInstance,fptr); if (w == 0) exit(-1); #else w = rmauxCreateXWindow(pipe, (Window)NULL, 0,0,imgWidth,imgHeight, "X11 tfly","X11 tfly", RM_TRUE); #endif rmPipeSetWindow(pipe,w,imgWidth,imgHeight); rmauxSetInitFunc(myInitFunc); rmPipeMakeCurrent(pipe); rmauxSetRenderFunc(myrenderfunc); /* * set key handler function so this prog will exit on "q" key. */ rmauxSetKeyFunc(pipe, rmauxDefaultKeyFunc); #ifdef RM_WIN rmauxEventLoop(pipe,rmRootNode(),&msg); #else rmauxEventLoop(pipe,rmRootNode(),NULL); #endif rmPipeDelete(pipe); rmFinish(); #ifdef RM_WIN return(msg.wParam); #else return(1); #endif }