/* * Copyright (C) 2001-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: spotlight.c,v 1.7 2003/04/13 18:13:23 wes Exp $ * $Revision: 1.7 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: spotlight.c,v $ * Revision 1.7 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.6 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.5 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.4 2002/06/17 00:42:44 wes * Added -s command line flag to generate per-frame rendering statistics. * * Revision 1.3 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.2 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.1 2001/05/26 14:23:16 wes * Initial entry. * */ /* * usage: spotlight [-w imgWidthPixels] [-h imgHeightPixels] [-s (turn on/display frame rate statistics)] * * This program creates a scene consisting of 5 walls and a spotlight. * You can interactively move the spotlight around to see the effect * of spot lighting in a scene. The walls are reasonably dense quadmeshes * since OpenGL uses vertex lighting - a single quad would produce * crummy results. */ #include <stdio.h> #ifndef RM_WIN #include <unistd.h> #endif #include <rm/rm.h> #include <rm/rmaux.h> #include "procmode.h" /* default window size, this can be overridden by command line args */ #define DEFAULT_IMAGE_WIDTH 640 #define DEFAULT_IMAGE_HEIGHT 525 /* * you can safely modify the following four items to suit your tastes. */ #define SUBDIVISIONS 100 /* defines resolution of quadmeshes */ #define DEFAULT_SPOT_CUTOFF 45.0F /* half-angle width of spotlight beam */ static RMcolor4D spotColor = {0.9, 0.5, 0.5, 1.0}; /* color of spotlight */ static float spotExponent = 4.0F; /* values range 0..128, 0 produces uniform distribution, higher values concentrate light in center of spot */ /* * these define the position and orientation of the spotlight when * created. you can change the default position if you want. the * size of the scene is a cube bounded by 0,0,0 and 10,10,10. if * you change the spotDirection, be sure to specify a unit vector. */ static RMvertex3D spotPosition = {5.0F, 5.0F, 5.0F}; static RMvertex3D spotDirection = {0.0F, -1.0F, 0.0F}; static RMpipe *myPipe=NULL; static RMnode *myRoot=NULL; static RMnode *dummyNode=NULL; static RMnode *spotIcon=NULL; static RMcamera3D *myCamera=NULL; static int doStats=0; void buildXZQuadmesh(RMnode *addTo, RMvertex3D *vmin, RMvertex3D *vmax, int subdivisions, int ySign) { /* * build a quadmesh parallel to the X-Z plane. */ RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D refNormal={0.0, 1.0, 0.0}; RMprimitive *p = rmPrimitiveNew(RM_QUADMESH); int i,j, cnt=0; RMvertex3D w; double dx, dz; refNormal.y *= ySign; rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions); dx = (vmax->x - vmin->x)/(subdivisions-1); dz = (vmax->z - vmin->z)/(subdivisions-1); w.z = vmin->z; w.y = vmin->y; for (j=0;j<subdivisions;j++) { w.x = vmin->x; for (i=0;i<subdivisions;i++) { v[cnt] = w; n[cnt] = refNormal; cnt++; w.x += dx; } w.z += dz; } rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL); rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL); rmNodeAddPrimitive(addTo, p); rmVertex3DDelete(v); rmVertex3DDelete(n); } void buildXYQuadmesh(RMnode *addTo, RMvertex3D *vmin, RMvertex3D *vmax, int subdivisions, int zSign) { /* * build a quadmesh parallel to the X-Y plane. */ RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D refNormal={0.0, 0.0, 1.0}; RMprimitive *p = rmPrimitiveNew(RM_QUADMESH); int i,j, cnt=0; RMvertex3D w; double dx, dy; refNormal.z *= zSign; rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions); dx = (vmax->x - vmin->x)/(subdivisions-1); dy = (vmax->y - vmin->y)/(subdivisions-1); w.z = vmin->z; w.y = vmin->y; for (j=0;j<subdivisions;j++) { w.x = vmin->x; for (i=0;i<subdivisions;i++) { v[cnt] = w; n[cnt] = refNormal; cnt++; w.x += dx; } w.y += dy; } rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL); rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL); rmNodeAddPrimitive(addTo, p); rmVertex3DDelete(v); rmVertex3DDelete(n); } void buildYZQuadmesh(RMnode *addTo, RMvertex3D *vmin, RMvertex3D *vmax, int subdivisions, int xSign) { /* * build a quadmesh parallel to the Y-Z plane. */ RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions); RMvertex3D refNormal={1.0, 0.0, 0.0}; RMprimitive *p = rmPrimitiveNew(RM_QUADMESH); int i,j, cnt=0; RMvertex3D w; double dy, dz; refNormal.x *= xSign; rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions); dy = (vmax->y - vmin->y)/(subdivisions-1); dz = (vmax->z - vmin->z)/(subdivisions-1); w.x = vmin->x; w.y = vmin->y; for (j=0;j<subdivisions;j++) { w.z = vmin->z; for (i=0;i<subdivisions;i++) { v[cnt] = w; n[cnt] = refNormal; cnt++; w.z += dz; } w.y += dy; } rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL); rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL); rmNodeAddPrimitive(addTo, p); rmVertex3DDelete(v); rmVertex3DDelete(n); } void buildCamera(RMnode *addTo, int imgWidth, int imgHeight) { RMcamera3D *c = rmCamera3DNew(); RMvertex3D eye={5.0, 5.0, 22.0}; RMvertex3D at={5.0, 5.0, 0.0}; RMvertex3D up={0.0, 1.0, 0.0}; rmCamera3DSetEye(c, &eye); rmCamera3DSetAt(c, &at); rmCamera3DSetHither(c, 5.0F); rmCamera3DSetYon(c, 40.0F); rmCamera3DSetFOV(c, 45.0F); rmCamera3DSetUpVector(c, &up); rmCamera3DSetAspectRatio(c, (float)imgWidth/(float)imgHeight); rmCamera3DSetProjection(c, RM_PROJECTION_PERSPECTIVE); rmNodeSetSceneCamera3D(addTo, c); myCamera = c; } void setViewport(RMnode *addTo) { float vp[4] = {0.0F, 0.0F, 1.0F, 1.0F}; rmNodeSetSceneViewport(addTo, vp); } void setFBClear(RMnode *addTo) { RMcolor4D c={0.2, 0.2, 0.3, 1.0}; rmNodeSetSceneBackgroundColor(addTo, &c); } void setArenaLighting(RMnode *addTo) { RMlight *l0; RMcolor4D diffuse = {0.3, 0.3, 0.3, 1.0}; RMcolor4D defAmbient = {0.3, 0.3, 0.3, 1.0}; RMvertex3D pos = {5.0, 7.5, 5.0}; /* point light */ RMlightModel *lm; l0 = rmLightNew(); rmLightSetType(l0, RM_LIGHT_POINT); rmLightSetColor(l0, NULL, &diffuse, &diffuse); rmLightSetXYZ(l0, &pos); rmNodeSetSceneLight(addTo, RM_LIGHT0, l0); rmLightDelete(l0); lm = rmLightModelNew(); rmLightModelSetAmbient(lm, &defAmbient); rmLightModelSetTwoSided(lm, RM_FALSE); rmLightModelSetLocalViewer(lm, RM_TRUE); rmNodeSetSceneLightModel(addTo, lm); rmLightModelDelete(lm); } void setSpotLight(RMnode *addTo, RMnode *iconNode, RMvertex3D *pos, RMvertex3D *dir) { /* * build a spotlight RMlight, and stick it in the addTo node. * create a cone to represent the position, orientation and * spread angle of the spotlight, and stick it in the iconNode. */ RMlight *l0; RMnode *n=rmNodeNew("spotLight", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); RMprimitive *p = rmPrimitiveNew(RM_CONES); RMvertex3D v[2]; float scale[1]; RMnode *t; if (rmNodeGetNumChildren(iconNode) > 0) { t = rmNodeGetIthChild(iconNode, 0); rmNodeRemoveAllChildren(iconNode); rmNodeDelete(t); } rmNodeAddChild(iconNode, n); v[1] = *pos; v[0].x = pos->x + dir->x; v[0].y = pos->y + dir->y; v[0].z = pos->z + dir->z; scale[0] = (float)(sin(RM_DEGREES_TO_RADIANS(DEFAULT_SPOT_CUTOFF ))); rmPrimitiveSetVertex3D(p, 2, v, RM_COPY_DATA, NULL); rmPrimitiveSetRadii(p, 1, scale, RM_COPY_DATA, NULL); rmNodeSetDiffuseColor(n, &spotColor); rmNodeSetSpecularColor(n, &spotColor); rmNodeSetAmbientColor(n, &spotColor); rmNodeAddPrimitive(n, p); l0 = rmLightNew(); rmLightSetType(l0, RM_LIGHT_SPOT); rmLightSetColor(l0, NULL, &spotColor, &spotColor); rmLightSetXYZ(l0, pos); rmLightSetSpotDirection(l0, dir); rmLightSetSpotCutoff(l0, DEFAULT_SPOT_CUTOFF); rmLightSetSpotExponent(l0, spotExponent); rmNodeSetSceneLight(addTo, RM_LIGHT1, l0); rmLightDelete(l0); } void myInitFunc(RMpipe *p, RMnode *n) { int imgWidth, imgHeight; RMnode *objRoot = rmNodeNew("objRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); RMvertex3D origin={0.0F, 0.0F, 0.0F}; RMvertex3D xzMax={10.0F, 0.0F, 10.0F}; RMvertex3D yzMax1={0.0F, 10.0F, 10.0F}; RMvertex3D origin2={10.0F, 0.0F, 0.0F}; RMvertex3D yzMax2={10.0F, 10.0F, 10.0F}; RMvertex3D origin3={0.0F, 10.0F, 0.0F}; RMvertex3D xzMax2={10.0F, 10.0F, 10.0F}; RMvertex3D xyMax={10.0F, 10.0F, 0.0F}; /* build walls */ buildXZQuadmesh(objRoot,&origin, &xzMax, SUBDIVISIONS, 1); buildXZQuadmesh(objRoot,&origin3, &xzMax2, SUBDIVISIONS, -1); buildYZQuadmesh(objRoot, &origin, &yzMax1, SUBDIVISIONS, 1); buildYZQuadmesh(objRoot, &origin2, &yzMax2, SUBDIVISIONS, -1); buildXYQuadmesh(objRoot, &origin, &xyMax, SUBDIVISIONS, 1); rmNodeComputeBoundingBox(objRoot); rmNodeComputeCenterFromBoundingBox(objRoot); myRoot = rmNodeNew("myRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); rmPipeGetWindowSize(p, &imgWidth, &imgHeight); /* create the 3D viewer for the scene */ buildCamera(myRoot, imgWidth, imgHeight); /* framebuffer clear & viewport specs */ setFBClear(myRoot); setViewport(rmRootNode()); rmNodeAddChild(rmRootNode(), myRoot); rmNodeAddChild(myRoot, objRoot); rmNodeUnionAllBoxes(rmRootNode()); rmNodeComputeCenterFromBoundingBox(rmRootNode()); /* create non-spot light source & lighting environment */ setArenaLighting(myRoot); spotIcon = rmNodeNew("spotIcon", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); /* create the spotlight. */ setSpotLight(myRoot, spotIcon, &spotPosition, &spotDirection); rmNodeAddChild(myRoot, spotIcon); /* the dummyNode is used to harvest rotation matrices from the rmaux routines that do trackball-style rotations. we'll use the rotation matrix to change the orientation of the spotlight. */ dummyNode = rmNodeNew("dummyNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE); /* attach the trackball rotation to the dummy node. */ rmauxSetGeomTransform(dummyNode, myPipe); /* you can move the viewpoint around */ rmauxSetCamera3DTransform(myRoot, p); /* * set handler to reset aspect ratio when the window is resized. */ rmauxSetResizeFunc(p, myRoot, rmauxDefaultResizeFunc); if (doStats != 0) rmStatsComputeDemography(rmRootNode()); /* cough up one frame - needed for multistage rendering so that the user isn't presented an empty window. */ if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE) rmFrame(p, rmRootNode()); rmFrame(p, rmRootNode()); } void appDrawFunc(RMpipe *p, RMnode *n) { RMvertex3D newDir; RMmatrix m; /* move the spotlight around according to the current rotation matrix computed by rmaux. we transform the original spotlight direction by the rotation matrix of the dummyNode. */ newDir = spotDirection; if ((rmNodeGetRotateMatrix(dummyNode,&m)) != RM_WHACKED) rmPointMatrixTransform(&newDir, &m, &newDir); setSpotLight(myRoot, spotIcon, &spotPosition, &newDir); if (doStats != 0) rmStatsStartTime(); /* redraw the scene */ rmFrame(p, n); if (doStats != 0) { rmStatsEndTime(); rmStatsPrint(); } } void usage(char *s) { printf("usage: %s [-w imgWidthPixels ] [-h imgHeightPixels] [-s (turn on frame rate statistics)]\n",s); } void parseArgs(int ac, char *av[], int *imgWidth, int *imgHeight) { int i; i = 1; while (i < ac) { 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],"-s") == 0) doStats = 1; else { usage(av[0]); exit(-1); } i++; } } #ifdef RM_WIN int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND hWnd; void *fptr; int status; int imgWidth, imgHeight; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_WGL; imgWidth = DEFAULT_IMAGE_WIDTH; imgHeight = DEFAULT_IMAGE_HEIGHT; parseArgs(__argc, __argv, &imgWidth, &imgHeight); #else /* assume RM_X */ int main(int argc, char *argv[]) { void *msg; /* needed for rmauxEventLoop win32/unix API consistency */ int status; int imgWidth, imgHeight; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_GLX; imgWidth = DEFAULT_IMAGE_WIDTH; imgHeight = DEFAULT_IMAGE_HEIGHT; parseArgs(argc, argv, &imgWidth, &imgHeight); #endif /* * first stage of RM initialization. */ rmInit(); /* * create the rendering pipe. this step is required in both * Win32 and X. */ myPipe = rmPipeNew(targetPlatform); processingMode = RM_PIPE_MULTISTAGE; rmPipeSetProcessingMode(myPipe, processingMode); #ifdef RM_WIN { /* * Win32: when a window is created, we have to tell windows the * name of the "WndProc," the procedure that gets called by * windows with events (the event loop) (contrast to the X model * where the name of the event loop is not part of the window). * Since we're using RMaux, we know about the event handling * procedure named "rmauxWndProc" and we provide that here. */ fptr = (void *)(rmauxWndProc); hWnd = rmauxCreateW32Window(myPipe, NULL, /* no parent window */ 20,20,imgWidth,imgHeight,"RM for Windows", hInstance,fptr); if (hWnd == 0) exit(-1); /* * assign the new window handle to the rendering pipe. */ rmPipeSetWindow(myPipe,hWnd, imgWidth, imgHeight); } #endif #ifdef RM_X { Window w; w = rmauxCreateXWindow(myPipe, (Window)NULL, /* parent window */ 0,0,imgWidth,imgHeight, "RM for X-Windows","icon-title",RM_TRUE); /* * assign the window to the rendering pipe. */ rmPipeSetWindow(myPipe,w,imgWidth,imgHeight); } #endif /* * X-ism: once the window is created and assigned to the * rendering pipe, rmPipeMakeCurrent makes the OpenGL rendering context * current for the pipe+window combination. * * this step is required for X. in these demo programs, it is not * strictly required by Win32, as the newly created context is made * current as part of the OpenGL initialization sequence. */ rmPipeMakeCurrent(myPipe); rmauxSetInitFunc(myInitFunc); rmauxSetRenderFunc(appDrawFunc); /* * set key handler function so this prog will exit on "q" key. */ rmauxSetKeyFunc(myPipe, rmauxDefaultKeyFunc); rmauxEventLoop(myPipe,rmRootNode(), &msg); rmPipeDelete(myPipe); rmFinish(); return(1); }