/* * Copyright (C) 1997-2005, R3vis Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; 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/lgpl.html. * * Original Contributor: * Wes Bethel, R3vis Corporation, Marin County, California * Additional Contributor(s): * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: rmpick.c,v 1.10 2005/06/06 02:04:29 wes Exp $ * Version: $Name: OpenRM-1-6-0-RC5 $ * $Revision: 1.10 $ * $Log: rmpick.c,v $ * Revision 1.10 2005/06/06 02:04:29 wes * Lots of small additions to clean up compiler warnings. * * Revision 1.9 2005/02/19 16:22:50 wes * Distro sync and consolidation. * * Revision 1.8 2005/01/23 17:00:22 wes * Copyright updated to 2005. * * Revision 1.7 2004/09/28 00:46:56 wes * Use calloc to clear out the pick buffer before doing the pick. * * Revision 1.6 2004/03/30 14:13:31 wes * Fixed declarations and man page docs for several routines. * * Revision 1.5 2004/01/16 16:46:35 wes * Updated copyright line for 2004. * * Revision 1.4 2003/12/12 00:33:47 wes * Removed a bunch of dead code, commented out diagnostic messages. * * Revision 1.3 2003/11/16 16:19:40 wes * Removed "serial table" from picking operations. rmserial.c can probably * be removed from the source tree, and rmpick.c needs to have a bunch * of dead code surrounded by if 0's removed. * * Revision 1.2 2003/02/02 02:07:15 wes * Updated copyright to 2003. * * Revision 1.1.1.1 2003/01/28 02:15:23 wes * Manual rebuild of rm150 repository. * * Revision 1.13 2003/01/16 22:21:17 wes * Updated all source files to reflect new organization of header files: * all header files formerly located in include/rmaux, include/rmi, include/rmv * are now located in include/rm. * * Revision 1.12 2002/09/23 13:42:36 wes * * Established minimum size for feedback buffer; one user reported problems * when picking with a very small number of objects. Establishing a * minimum feedback buffer size appears to have fixed the problem. There * may still be a linger problem. * * Revision 1.11 2002/06/17 01:01:39 wes * Replaced fixed-size pick table with one that is completely dynamic - * no more realloc error messages. Replaced wonky #if DEBUG statements * with those that are consistent with the rest of the distribution. * * Revision 1.10 2002/04/30 19:33:05 wes * Updated copyright dates. * * Revision 1.9 2001/06/03 20:50:04 wes * Removed dead code. * * Revision 1.8 2001/05/26 14:37:49 wes * Added RMnode parameter to serialization code used in picking - this * will permit picking of scene graph subtrees that are disconnected * from rmRootNode(). * * Revision 1.7 2001/03/31 17:12:39 wes * v1.4.0-alpha-2 checkin. * * Revision 1.6 2000/12/03 22:35:38 wes * Mods for thread safety. * * Revision 1.5 2000/08/23 23:27:10 wes * Added RM_TRUE as the default matrix stack initialization mode * to all pick routines. This is a placeholder. * * Revision 1.4 2000/05/14 23:38:38 wes * New param to private_rmSubTreeFrame() for OpenGL matrix stack * initialization during rendering/picking frame operations. * * Revision 1.3 2000/04/20 16:29:47 wes * Documentation additions/enhancements, some code rearragement. * * Revision 1.2 2000/02/29 23:43:53 wes * Compile warning cleanups. * * Revision 1.1.1.1 2000/02/28 21:29:40 wes * OpenRM 1.2 Checkin * * Revision 1.1.1.1 2000/02/28 17:18:48 wes * Initial entry - pre-RM120 release, source base for OpenRM 1.2. * */ /* * this file contains all the routines in RM that support picking. */ #include #include "rmprivat.h" #include /* PRIVATE declarations */ static int private_processHits (int nhits, GLuint *pick_buffer, RMpick *srmpick); static int private_processHitsList (int nhits, GLuint *pick_buffer, RMpick *list); void private_rmSetupPickMatrix (void); void private_rmNodePrimPickName (RMnode *r, int primindex); void private_rmNodeOnlyPickName (const RMnode *r); static RMpick *private_rmPickListNew (int n); /* pick coordinates */ static int xpick_location = 0, ypick_location = 0; static GLdouble pick_width = 5.0, pick_height = 5.0; /* tmp 11/9/03 until we move the following routine to somewhere more permanent */ RMnode * private_rmNodeFromIndex(int indx) { extern RMcompMgrHdr *global_RMnodePool; int pageNum, offset; RMnode *t; pageNum = indx / NUM_ITEMS_PER_PAGE; offset = indx % NUM_ITEMS_PER_PAGE; /* put sanity check here */ t = (RMnode *)(global_RMnodePool->objectPool[pageNum]) + offset; return t; } /* * ---------------------------------------------------- * @Name rmFramePick @pstart RMpick * rmFramePick (RMpipe *renderPipe, RMnode *subTree, int xpick, int ypick) @pend @astart RMpipe *renderPipe - the pipe upon which rendering and picking will occur. RMnode *subTree- the subtree to be drawn, and picked. int xpick, ypick - integer values indicating an (x,y) window location. @aend @dstart rmFramePick() performs object picking. A single RMpick object is returned representing the object closest to the viewer at the pixel location (xpick, ypick) in the display window. To obtain a list of all objects that appear at (xpick, ypick), not just the frontmost, use rmFramePickList(). If no objects were picked, NULL is returned. Some scene parameters will generate pick hits. These include 2D and 3D cameras. When the pick occurs at some (x,y) location that is not covered by any objects, a pick hit will be returned. In that case, the returned RMpick object's RMnode attribute will point to the RMnode that has a camera that generated the pick hit. With the RMpick object returned by rmPickFrame or rmPickFrameList, use the routine rmPickedNode to obtain a handle to the RMnode that was picked; rmPickedPrimitive to obtain a handle to the RMprimitive that was picked; rmPickedNodeName() to obtain the (character string) name of the node that was picked; and rmPickedPrimitiveZval to obtain the NDC z-coordinate of the primitive that was picked. June 2002: the RMpick object returned by rmFramePick should be deleted with rmPickDelete when it is no longer needed. (In versions of RM earlier than 1.4.2, a pointer to static memory was returned, and apps were prohibited from free'ing that memory. This has changed as of v1.4.2). See the RM demo programs for example usage, particularly "trans2d.c", which performs picking in 2D, and "pickTest.c", which performs picking in 3D. The RM demo program "pickListTest.c" exercises rmFramePickList() in 3D. @dend * ---------------------------------------------------- */ RMpick * rmFramePick (RMpipe *renderPipe, RMnode *subTree, int xpick, int ypick) { int i, hits = 0; RMenum render3DOpaqueEnable = RM_TRUE; RMenum render3DTransparentEnable = RM_TRUE; RMenum render2DEnable = RM_TRUE; RMpipe *usePipe; RMpick *pickReturn=NULL; int pickBufferSize; int totalNodes, totalPrims; unsigned int *pick_buffer; int private_rmTrueFilterfunc(RMnode *r); xpick_location = xpick; ypick_location = ypick; /* 11/9/03 wes - removed serial table from picking process */ { extern RMcompMgrHdr *global_RMnodePool, *global_RMprimitivePool; totalNodes = global_RMnodePool->numAlloc; totalPrims = global_RMprimitivePool->numAlloc; } #if (DEBUG_LEVEL & DEBUG_TRACE) printf(" rmFramePick: #nodes = %d, #prims = %d \n", totalNodes, totalPrims); #endif pickBufferSize = totalNodes + totalPrims; pickBufferSize = RM_MAX(32, pickBufferSize+1); pick_buffer = (unsigned int *)calloc(sizeof(unsigned int)*pickBufferSize, sizeof(unsigned int)); /* set the render mode to pick objects when the scene is rendered. */ glSelectBuffer(pickBufferSize, pick_buffer); glRenderMode(GL_SELECT); glInitNames(); glPushName(-1); /* render stuff */ usePipe = renderPipe; private_rmSubTreeFrame(usePipe, subTree, GL_SELECT, private_rmNodeOnlyPickName, private_rmNodePrimPickName, private_rmTrueFilterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DEnable, RM_TRUE); /* always initialize matrix stack here? */ /* get the hits back from OpenGL */ hits = glRenderMode(GL_RENDER); glMatrixMode(GL_MODELVIEW); #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " %d hits from pick operation. \n", hits); #endif /* process hits */ if (hits > 0) { pickReturn = private_rmPickListNew(1); i = private_processHits(hits, pick_buffer, pickReturn); pickReturn->node = private_rmNodeFromIndex(pickReturn->index); } free((void *)pick_buffer); return(pickReturn); } /* * ---------------------------------------------------- * @Name rmFramePickList @pstart int rmFramePickList (RMpipe *renderPipe, RMnode *subTree, int xpick, int ypick, RMpick **listReturn) @pend @astart RMpipe *renderPipe - the pipe upon which rendering and picking will occur. RMnode *subTree- the subtree to be drawn, and picked. int xpick, ypick - integer values indicating an (x,y) pixel coordinate within a window (input). RMpick **listReturn - a handle to an RMpick pointer (modified, return). @aend @dstart Performs a pick operation, returning a list of all objects encountered at the (x,y) pixel location in the render window through the parameter listReturn. The number of objects in that list is returned on the stack, or zero is returned if there were no objects picked. The list of objects that is returned to the caller is sorted in ascending order of the z-coordinate of the picked object. Therefore, listReturn[0] contains information about the object closest to the viewer, and listReturn[nhits-1] contains info about the object farthest from the viewer. The RMpick objects returned through listReturn should be freed when no longer needed by using rmPickListDelete(). @dend * ---------------------------------------------------- */ int rmFramePickList (RMpipe *renderPipe, RMnode *subTree, int xpick, int ypick, RMpick **list_return) { int hits = 0; RMpick *list; RMenum render3DOpaqueEnable = RM_TRUE; RMenum render3DTransparentEnable = RM_TRUE; RMenum render2DEnable = RM_TRUE; unsigned int *pick_buffer; int totalNodes, totalPrims, pickBufSize; xpick_location = xpick; ypick_location = ypick; { extern RMcompMgrHdr *global_RMnodePool, *global_RMprimitivePool; totalNodes = global_RMnodePool->numAlloc; totalPrims = global_RMprimitivePool->numAlloc; } pickBufSize = totalNodes + totalPrims; pickBufSize = RM_MAX(32, pickBufSize); #if (DEBUG_LEVEL & DEBUG_TRACE) printf(" rmFramePickList totalNodes = %d, totalPrims = %d \n", totalNodes, totalPrims); #endif pick_buffer = (unsigned int *)malloc((sizeof(unsigned int)*pickBufSize)); /* set the render mode to pick objects when the scene is rendered */ glSelectBuffer(pickBufSize, pick_buffer); glRenderMode(GL_SELECT); glInitNames(); glPushName(-1); /* render stuff */ private_rmSubTreeFrame(renderPipe, subTree, GL_SELECT, private_rmNodeOnlyPickName, private_rmNodePrimPickName, private_rmTrueFilterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DEnable, RM_TRUE); /* always initialize matrix stack? */ /* get the hits back from OpenGL */ hits = glRenderMode(GL_RENDER); /* pop off projection matrix */ glPopMatrix(); glMatrixMode(GL_MODELVIEW); #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " %d hits from pick operation. \n", hits); #endif /* process hits */ if (hits > 0) { int i; list = private_rmPickListNew(hits); private_processHitsList(hits, pick_buffer, list); #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " there were %d hits. \n", hits); #endif for (i = 0; i < hits; i++) { list[i].node = private_rmNodeFromIndex(list[i].index); #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " %s %g \n", rmNodeGetName(list[i].node), list[i].zval); #endif } } else list = NULL; free((void *)pick_buffer); *list_return = list; return(hits); } /* * ---------------------------------------------------- * @Name rmPickDelete @pstart RMenum rmPickDelete (RMpick *toDelete) @pend @astart RMpick *toDelete - a handle to a single or flat array of RMpick objects. @aend @dstart Use this routine to free resources associated with an RMpick object or flat array of RMpick objects. When rmFramePick() and rmFramePickList() finish, each returns an RMpick * object (rmFramePick) or flat array of RMpick objects (rmFramePickList). When applications are finished with these RMpick objects, they should be deleted using rmPickDelete(). @dend * ---------------------------------------------------- */ RMenum rmPickDelete (RMpick *list) { if (RM_ASSERT(list, "rmPickDelete() error: the input RMpick list is NULL") == RM_WHACKED) return(RM_WHACKED); free((void *)list); return(RM_CHILL); } /* * ---------------------------------------------------- * @Name rmPickedNode @pstart RMnode * rmPickedNode (const RMpick *toQuery) @pend @astart const RMpick *toQuery - a handle to an RMpick object (input). @aend @dstart Returns to the caller the RMnode handle contained within an RMpick object, or NULL upon failure. The RMpick object returned from rmFramePick (or objects returned from rmFramePickList) can be queried to determine the node, primitive, node name or z-coordinate of the picked object. @dend * ---------------------------------------------------- */ RMnode * rmPickedNode (const RMpick *p) { if (p == NULL) return(NULL); return(p->node); } /* * ---------------------------------------------------- * @Name rmPickedPrimitive @pstart int rmPickedPrimitive (const RMpick *toQuery) @pend @astart const RMpick *toQuery - a handle to an RMpick object (input). @aend @dstart The RMpick object returned from rmFramePick (or objects returned from rmFramePickList) can be queried to determine the node, primitive, node name or z-coordinate of the picked object. This routine returns the index of the primitive that was picked. Combining the primitive index with the RMnode handle of the picked object allows applications to determine which primitive was picked. Returns a non-negative integer upon success, or -1 upon failure. @dend * ---------------------------------------------------- */ int rmPickedPrimitive (const RMpick *p) { if (p == NULL) return(-1); else return(p->prim_index); } /* * ---------------------------------------------------- * @Name rmPickedNodeName @pstart char * rmPickedNodeName (const RMpick *toQuery) @pend @astart const RMpick *toQuery - a handle to an RMpick object (input). @aend @dstart Returns to the caller the node name of the RMnode handle contained within an RMpick object, or NULL upon failure. The RMpick object returned from rmFramePick (or objects returned from rmFramePickList) can be queried to determine the node, primitive, node name or z-coordinate of the picked object. This routine is functionally equivalent to first obtaining the RMnode handle using rmPickedNode() followed by rmNodeGetName(). @dend * ---------------------------------------------------- */ char * rmPickedNodeName (const RMpick *p) { if (p == NULL) return(NULL); return(rmNodeGetName(rmPickedNode(p))); } /* * ---------------------------------------------------- * @Name rmPickedPrimitiveZval @pstart float rmPickedPrimitiveZval (const RMpick *toQuery) @pend @astart const RMpick *toQuery - a handle to an RMpick object (input). @aend @dstart Returns to the caller the NDC z-coordinate of the RMprimitive at the (x,y) screen location where picking occured. A value of zero is returned upon error, when no picking occurred (RMpick NULL). The RMpick object returned from rmFramePick (or objects returned from rmFramePickList) can be queried to determine the node, primitive, node name or z-coordinate of the picked object. @dend * ---------------------------------------------------- */ float rmPickedPrimitiveZval (const RMpick *p) { if (RM_ASSERT(p, "rmPrimPickedZval() error: the input RMpick object is NULL") == RM_WHACKED) return(0.0); return(p->zval); } /* PRIVATE * * this routine takes a pile of pick hits and returns the one with * the smallest z-coordinate. nhits and pick_buffer are provided by * the GL_FEEDBACK mechanism, srmpick is the destination RMpick object * that is filled in by this routine. */ static int private_processHits (int nhits, GLuint *pick_buffer, RMpick *srmpick) { int i, j; int use_it; float zval; float zcur = RM_MAXFLOAT; GLuint *ptr; GLuint names; GLuint zscale = (~0u); GLuint ival = 0; ptr = pick_buffer; for (i = 0; i < nhits; i++) { use_it = 0; names = *ptr; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " number of names for this hit = %d \n", names); #endif ptr++; zval = *ptr; zval = zval / (float)zscale; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, "\tz1 = %g;", zval); fprintf(stderr, "\tz1 = %u;", *ptr); #endif ptr++; if (zval <= zcur) /* if they're the same, use the one that was drawn last */ { use_it = 1; zcur = zval; } zval = *ptr; zval = zval / (float)zscale; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " z2 = %g \n", zval); fprintf(stderr, " z2 = %u \n", *ptr); #endif ptr++; /* i'm going to assume that we'll use the first name and ignore the rest, and that if we got here, that "names" >= 1*/ #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, "\tthe names are: \n"); #endif if (use_it) ival = *ptr; for (j = 0; j < (int)(names); j++) { #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr," %d ", *ptr); #endif ptr++; } #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr," \n"); #endif } /* from ival, we need to extract the object index and the prim index */ { int opcode = RMPASSTHRU_GET_OPCODE(ival); if (opcode != RM_PASSTHRU_OPCODE_IDENTIFIER) rmError(" expected an identifier opcode in a pick operation. \n"); } srmpick->index = RMPASSTHRU_GET_OBJECT_ID(ival); srmpick->prim_index = RMPASSTHRU_GET_PRIM_ID(ival); srmpick->zval = zcur; return(1); } /* PRIVATE */ static int sortPickFunc( const void *p1, const void *p2) { RMpick *a, *b; float z1, z2; a = (RMpick *)p1; b = (RMpick *)p2; z1 = rmPickedPrimitiveZval(a); z2 = rmPickedPrimitiveZval(b); if (z1 < z2) return -1; else if (z1 > z2) return 1; else return 0; } /* PRIVATE * * analagous to processHits, private_processHitsList will return in RMpick *list * _all_ objects that were picked regardless of z-value. nhits and pick_buffer * are supplied by the OpenGL feedback mechanism, and list[] must be * "nhits" in size. */ static int private_processHitsList (int nhits, GLuint *pick_buffer, RMpick *list) { int i, j; int ival, use_it; float zval; GLuint *ptr; GLuint names; GLuint zscale = (~0u); ptr = pick_buffer; for (i = 0; i < nhits; i++) { use_it = 0; names = *ptr; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " number of names for this hit = %d \n", names); #endif ptr++; zval = *ptr; zval = zval / (float)zscale; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, "\tz1 = %g;", zval); fprintf(stderr, "\tz1 = %u;", *ptr); #endif ptr++; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " z2 = %g \n", zval); fprintf(stderr, " z2 = %u \n", *ptr); #endif ptr++; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, "\tthe names are: \n"); #endif ival = *ptr; { int opcode = RMPASSTHRU_GET_OPCODE(ival); if (opcode != RM_PASSTHRU_OPCODE_IDENTIFIER) rmError(" expected an identifier opcode in a pick operation. \n"); } list[i].index = RMPASSTHRU_GET_OBJECT_ID(ival); list[i].prim_index = RMPASSTHRU_GET_PRIM_ID(ival); list[i].zval = zval; for (j = 0; j < (int)(names); j++) { #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " %d ", *ptr); #endif ptr++; } #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr, " \n"); #endif } /* OK. we now have the hits list. sort it in increasing Z */ qsort(list, nhits, sizeof(RMpick), sortPickFunc); return(1); } /* PRIVATE */ void private_rmComputePickMatrix (RMstate *s, RMmatrix *pickReturn) { float sx, sy, tx, ty; RMmatrix m; rmMatrixIdentity(&m); sx = s->vp[2] / pick_width; sy = s->vp[3] / pick_height; tx = (s->vp[2] + (2.0F * (s->vp[0] - (float)xpick_location))) / pick_width; ty = (s->vp[3] + (2.0F * (s->vp[1] - (s->h - (float)ypick_location)))) / pick_height; m.m[0][0] = sx; m.m[1][1] = sy; m.m[3][0] = tx; m.m[3][1] = ty; *pickReturn = m; } /* PRIVATE * * private routine to set up the "pick matrix". see the man page * for gluPickMatrix. the basic idea is that the projection matrix * is tweaked so that only those objects that fall within a very * small region are "rendered." rendering, in the case of picking, * means that a GL_FEEDBACK token is generated, rather than pixels * being painted. */ void private_rmSetupPickMatrix (void) { rmError(" private_rmSetupPickMatrix is deprecated!"); #if 0 int w, h; GLint viewport[4]; RMpipe *pipe = private_rmPipeGetCurrent(); glGetIntegerv(GL_VIEWPORT, viewport); rmPipeGetWindowSize(pipe, &w, &h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix((GLdouble)xpick_location, (GLdouble)ypick_location, pick_width, pick_height, viewport); /* while we're here before any rendering happens, set up the serialized list of objects */ glMatrixMode(GL_MODELVIEW); #endif } /* PRIVATE * * the GL_FEEDBACK mechanism allows for applications to place * application-specific information in the "feedback token." * RM builds a serialized representation of the scene graph, then * as each node & primitive is rendered, a "bread crumb" is * encoded in the feedback token. the routine below will encode * the node & prim info into a token (glLoadName). * * the current_lod parm is deprecated and needs to be removed. */ void private_rmNodePrimPickName (RMnode *r, int primindex) { int indx; unsigned int token = 0; indx = r->compListIndx; RMPASSTHRU_ENCODE_OPCODE(token, RM_PASSTHRU_OPCODE_IDENTIFIER); RMPASSTHRU_ENCODE_OBJECT_ID(token, indx); RMPASSTHRU_ENCODE_PRIM_ID(token, primindex); glLoadName((GLuint)token); } /* PRIVATE * * private_rmNodeOnlyPickName encodes ONLY the node name into the feedback token */ void private_rmNodeOnlyPickName (const RMnode *r) { int indx; unsigned int token = 0; indx = r->compListIndx; RMPASSTHRU_ENCODE_OPCODE(token, RM_PASSTHRU_OPCODE_IDENTIFIER); RMPASSTHRU_ENCODE_OBJECT_ID(token, indx); glLoadName((GLuint)token); } /* PRIVATE * * this routine, while public in appearance, is used at the present time * only within RM itself to allocate space for RMpick objects. the routine * rmFramePickList uses this routine. applications will probably never * need to use this routine, as rmFramePickList() calls this routine on * behalf of the application. * * January 2000, there is presently no scenario in which applications using * RM will ever need to call private_rmPickListNew(). Because of this * condition, this function is static to this file. * There is no reason why it couldn't be moved to rmpublic.h if needed. */ static RMpick * private_rmPickListNew (int n) { RMpick *t = (RMpick *)malloc(sizeof(RMpick) * n); memset((void *)t, 0, sizeof(RMpick) * n); return(t); } /* EOF */