[BACK]Return to pfclosest.C CVS log [TXT][DIR] Up to [Development] / performer / src / lib / libpfdb / libpfclosest

File: [Development] / performer / src / lib / libpfdb / libpfclosest / pfclosest.C (download)

Revision 1.1, Tue Nov 21 21:39:33 2000 UTC (16 years, 11 months ago) by flynnt
Branch: MAIN
CVS Tags: HEAD

Initial check-in based on OpenGL Performer 2.4 tree.
-flynnt

/*
 * Copyright 1996, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * This source code ("Source Code") was originally derived from a
 * code base owned by Silicon Graphics, Inc. ("SGI")
 * 
 * LICENSE: SGI grants the user ("Licensee") permission to reproduce,
 * distribute, and create derivative works from this Source Code,
 * provided that: (1) the user reproduces this entire notice within
 * both source and binary format redistributions and any accompanying
 * materials such as documentation in printed or electronic format;
 * (2) the Source Code is not to be used, or ported or modified for
 * use, except in conjunction with OpenGL Performer; and (3) the
 * names of Silicon Graphics, Inc.  and SGI may not be used in any
 * advertising or publicity relating to the Source Code without the
 * prior written permission of SGI.  No further license or permission
 * may be inferred or deemed or construed to exist with regard to the
 * Source Code or the code base of which it forms a part. All rights
 * not expressly granted are reserved.
 * 
 * This Source Code is provided to Licensee AS IS, without any
 * warranty of any kind, either express, implied, or statutory,
 * including, but not limited to, any warranty that the Source Code
 * will conform to specifications, any implied warranties of
 * merchantability, fitness for a particular purpose, and freedom
 * from infringement, and any warranty that the documentation will
 * conform to the program, or any warranty that the Source Code will
 * be error free.
 * 
 * IN NO EVENT WILL SGI BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT
 * LIMITED TO DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES,
 * ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THE
 * SOURCE CODE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT OR
 * OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM,
 * OR AROSE OUT OF USE OR RESULTS FROM USE OF, OR LACK OF ABILITY TO
 * USE, THE SOURCE CODE.
 * 
 * Contact information:  Silicon Graphics, Inc., 
 * 1600 Amphitheatre Pkwy, Mountain View, CA  94043, 
 * or:  http://www.sgi.com
 */

/*
 * pfclosest.c
 *
 * Pseudo-loader that loads a scene graph from
 * from a file of any format and adds a callback
 * to highlight the closest point
 * (testing the pfutil function pfuGetClosestPoint()).
 *
 * By default, the closest point in the scene is highlighed during each frame.
 * If the environment variable PFCLOSEST_TEXTURE_I is set to an integer i,
 * the search for closest point is limited to the triangles textured
 * by the i'th texture encountered when traversing the scene graph.
 *
 * For example, to highlight the closest point in the scene file
 * esprit.flt, say:
 	perfly esprit.flt.closest
 * To highlight the closest point on the hubcaps, say:
	setenv PFCLOSEST_TEXTURE_I 0
 	perfly esprit.flt.closest
 * To highlight the closest point on the license plate, say:
	setenv PFCLOSEST_TEXTURE_I 1
 	perfly esprit.flt.closest
 *
 * Use the following to see a list of possible values
 * of PFCLOSEST_TEXTURE_I with corresponding texture names, say:
  	setenv PFCLOSEST_TEXTURE_I -1
	setenv PFNFYLEVEL 5
	perfly esprit.flt.closest
 *
 * ==========================================================================
 *
 * To instead highlight a point that is a frontward bound of the
 * intersection of the bounding sphere with the view frustum
 * (to be used in conjunction with perfly's "Cull View FOV" and the z key;
 * you'll want to turn off culling so you can see the ball)
 * say:
        setenv PFCLOSEST_FRUSTUM_INTERSECT_SPHERE
 * To approximate the view frustum with a cone, instead say:
        setenv PFCLOSEST_CONE_INTERSECT_SPHERE

 * Or to find the point on the object nearest to the eye plane:
	setenv PFCLOSEST_FRUSTUM_INTERSECT
 * (this mode also looks at PFCLOSEST_TEXTURE_I, as above)
 * 
 * WARNING: Do not use this mode unless you know the culling polytope
 * is a frustum (as it always is in perfly).
 *
 * (XXX should turn culling off explicitly for the ball)
 *
 * XXX notes:
 *	This traversal is kind of ridiculous since it clips every triangle
 *	in the entire tree and then throws away the result!
 *	Certainly this routine could benefit from the culler
 *	    (make it a post-cull function)
 *	and vice-versa (this function gives a more aggressive cull result).
 *
 * ==========================================================================
 *
 * With either of the above methods, the update can be made to
 * happen every n frames (so that once the closest point is highlighted,
 * the scene may be rotated and examined for a period of time before it is
 * updated again).  For example, to update every 120 frames,
  	setenv PFCLOSEST_UPDATE_FREQUENCY 120
 */

#include <Performer/pr/pfTexture.h>
#include <Performer/pr/pfGeoSet.h>
#include <Performer/pf/pfGeode.h>
#include <Performer/pf/pfDCS.h>
#include <Performer/pf/pfTraverser.h>
#include <Performer/pfutil.h>
#include <Performer/pfdu.h>
/*
#include <Performer/pf/pfGroup.h>
#include <Performer/pr/pfTexture.h>
*/
#include <stdlib.h>
#include <assert.h>
#ifdef __linux__
#include <limits.h>
#endif

static int _tex_is_in_array(pfTexture *tex, pfTexture **texs, int n)
{
    int i;
    for (i = 0; i < n; ++i)
	if (texs[i] == tex)
	    return 1;
    return 0;
}

struct findTextureTraverser: public pfuTraverser {
    int n;		// input-- find the n'th texture in the graph
    int i;		// local-- number of textures seen so far
    pfTexture **texs;	// local-- textures seen so far
    pfTexture *tex;	// output-- the desired texture

    findTextureTraverser(int n)
	: n(n < 0 ? 1000 : n), i(0), tex(NULL)
    {
	if (n < 0)
	{
	    /*
	     * We won't find it, but traverse everything anyway
	     * so that the user can see what textures are available
	     * if in debug mode...
	     */
	    this->n = 1000;
	}

	pfuInitTraverser(this);
	preFunc = _find_texture;
	texs = new pfTexture* [this->n];
	if (texs == NULL)
	    pfNotify(PFNFY_WARN, PFNFY_RESOURCE,
		     "pfdLoadFile_substclip: can't allocate %d pfTexture*'s",
		     this->n);
    }
    int bad() { return texs == NULL; }	// whether constructor failed
    ~findTextureTraverser()
    {
	delete[] texs; // okay if NULL
    }

    static int _find_texture(pfuTraverser *_trav)
    {
	struct findTextureTraverser *trav = (findTextureTraverser *)_trav;

	if (trav->node->isOfType(pfGeode::getClassType()))
	{
	    pfGeode *geode = (pfGeode *)trav->node;
	    int nGSets = geode->getNumGSets();
	    for (int i = 0; i < nGSets; ++i)
	    {
		pfGeoSet *gset = geode->getGSet(i);
		pfGeoState *gstate = gset->getGState();
		if (gstate != NULL
		 && gstate->getMode(PFSTATE_ENTEXTURE))
		{
		    pfTexture *tex = (pfTexture *)
			gstate->getAttr(PFSTATE_TEXTURE);
		    if (tex != NULL && !_tex_is_in_array(tex, trav->texs, trav->i))
		    {
			if (trav->i == trav->n)
			{
			    trav->tex = tex;
			    return PFTRAV_TERM;
			}
			else
			{
			    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
				     "Texture #%d: %s(0x%p)\n", trav->i,
				     tex->getName(),
				     (void *)tex);
			    trav->texs[trav->i++] = tex;
			}
		    }
		}
	    }
	}
	return PFTRAV_CONT;
    }
};

/*
 * Find the n'th texture in the scene graph
 * and return the corresponding geostate and containing geode.
 * XXX there's now a pfu utility that does this... need to convert to using it
 * XXX (currently, this one is better, I think)
 */
static pfTexture *
pfuFindTexture(pfNode *node, int n)
{
    findTextureTraverser trav(n);
    if (trav.bad())
	return NULL;

    pfuTraverse(node, &trav);
    return trav.tex;
}


/*
 * Assume the channel cull polytope is in the shape of a standard frustum
 * (as it is in perfly- it will be the view frustum, possibly
 * shrunk by perfly's "Cull View FOV" controls).
 * Calculate that frustum.
 * (It's rather silly that we have to do this,
 * but we don't want to pollute perfly any more than is necessary)...
 * XXX actually, it's not necessary, if we change the API
 * XXX to pfuFindNearestWhatever so that it takes a list of planes
 * XXX rather than a pfFrustum or pfPolytope, since the implementation
 * XXX only uses the planes (assuming it knows which one is far)
 * XXX and not any other information.
 * XXX then we wouldn't need the stuff below, either
 */
/* XXX assume knowledge of frustum internals */
#define PFFRUST_LEFT	0
#define PFFRUST_RIGHT	1
#define PFFRUST_NEAR	2
#define PFFRUST_FAR	3
#define PFFRUST_BOTTOM	4    
#define PFFRUST_TOP	5
static void
calcViewFrustumFromCullPtope(const pfPolytope *ptope, pfFrustum *frust)
{
    assert(ptope->getNumFacets() == 6);
    pfPlane facets[6];
    int i;
    for (i = 0; i < 6; ++i)
	ptope->getFacet(i, &facets[i]);

    //
    // These calculations should be valid whether it's perspective
    // or orthogonal (though the orthogonal case has not been tested)...
    //
    float near = -facets[PFFRUST_NEAR].offset;
    float far = facets[PFFRUST_FAR].offset;

#define GET_COORD_AT_NEAR(which, ax) \
		((facets[which].offset - near*facets[which].normal[PF_Y]) \
	        / facets[which].normal[ax])

    float right  = GET_COORD_AT_NEAR(PFFRUST_RIGHT, PF_X);
    float left   = GET_COORD_AT_NEAR(PFFRUST_LEFT, PF_X);
    float top    = GET_COORD_AT_NEAR(PFFRUST_TOP, PF_Z);
    float bottom = GET_COORD_AT_NEAR(PFFRUST_BOTTOM, PF_Z);

    if (facets[PFFRUST_LEFT].normal[PF_Y] < -.01) /* sine of 1/2 degree */
    {
	frust->setNearFar(near, far); /* must come before makePersp */
	frust->makePersp(left, right, bottom, top);
    }
    else
    {
	/* XXX not tested yet */
	pfNotify(PFNFY_DEBUG, PFNFY_USAGE,
		 "closest loader: orthogonal frusta have not been tested");
	frust->setNearFar(near, far);
	frust->makeOrtho(left, right, bottom, top);
    }
}

// Possible modes...
#define FIND_CONE_INTERSECT_SPHERE 0
#define FIND_FRUSTUM_INTERSECT_SPHERE 1
#define FIND_FRUSTUM_INTERSECT_TRIANGLES 2
#define FIND_CLOSEST_POINT_TO_EYE 3

struct Data {
    int frequency;	/* update every this-many frames */
    int mode;
    pfTexture *tex; /* only used when doing pfuGetClosestPoint */

    /* scratch area so we don't have to reallocate each frame
       (polytopes don't want to exist on the stack) */
    pfPolytope *ptope;
    pfFrustum *frust;
};

/*
 * post-app callback to adjust the size and position of the little red sphere
 */
static int
highlightClosestPoint(pfTraverser *trav, void *_data)
{
    struct Data *data = (struct Data *)_data;
    int framecount = pfGetFrameCount();
    if (framecount % data->frequency != 0)
	return PFTRAV_CONT;
    pfTexture *tex = data->tex;
    pfGroup *parent = (pfGroup *)trav->getNode();
    pfNode *child = parent->getChild(0);
    pfDCS *dcs = (pfDCS *)parent->getChild(1);
    int travmode = PFUTRAV_SW_CUR | PFUTRAV_SEQ_CUR
		 | PFUTRAV_LOD_RANGE0 | PFUTRAV_LAYER_BOTH;
	/* XXX would really like PFUTRAV_LOD_CUR but it doesn't exist */

    /*
     * There is a single sphere under the dcs.
     * Scale it so that it is 1/100 the size of the
     * bounding sphere of child,
     * and translate it so that it is centered at the
     * point in child closest to the eye
     * (if the environment variable PFCLOSEST_TEXTURE_I is set to an integer i,
     * limit the search for closest point to the triangles textured
     * by the i'th texture encountered).
     */

    pfSphere bsph;
    child->getBound(&bsph);
    parent->setBound(&bsph, PFBOUND_STATIC); // so red ball doesn't affect it

    pfVec3 eye(0,0,0);
    pfMatrix viewmat, travmat;
    trav->getChan()->getViewMat(viewmat);
    trav->getMat(travmat);
    pfMatrix invtravmat;
    invtravmat.invertOrtho(travmat);
    pfMatrix eyespace_to_objspace_mat = viewmat;
    eyespace_to_objspace_mat.postMult(invtravmat);

#ifdef DEBUG
    pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "Channel matrix:");
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    viewmat[0][0],viewmat[0][1],viewmat[0][2],viewmat[0][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    viewmat[1][0],viewmat[1][1],viewmat[1][2],viewmat[1][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    viewmat[2][0],viewmat[2][1],viewmat[2][2],viewmat[2][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    viewmat[3][0],viewmat[3][1],viewmat[3][2],viewmat[3][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "Traverser matrix:");
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    travmat[0][0],travmat[0][1],travmat[0][2],travmat[0][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    travmat[1][0],travmat[1][1],travmat[1][2],travmat[1][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    travmat[2][0],travmat[2][1],travmat[2][2],travmat[2][3]);
    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "\t%g %g %g %g",
	    travmat[3][0],travmat[3][1],travmat[3][2],travmat[3][3]);
#endif
    eye.xformPt(eye, eyespace_to_objspace_mat);

    switch (data->mode)
    {
	case FIND_CLOSEST_POINT_TO_EYE:
	{
	    float x,y,z,s,t,r;
	    if (pfuGetClosestPoint(child, eye[0], eye[1], eye[2], tex,
				   travmode,
				   &x, &y, &z, &s, &t, &r))
	    {
		/* duplicate code below */
		dcs->setScale(bsph.radius*.01,bsph.radius*.01,bsph.radius*.01);
		dcs->setTrans(x, y, z);
		pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "highlightClosestPoint:");
		pfNotify(PFNFY_DEBUG, PFNFY_MORE,"        v=(%g %g %g)", x,y,z);
		if (tex != NULL)
		    pfNotify(PFNFY_DEBUG, PFNFY_MORE,
			     "        t=(%g %g %g)", s,t,r);
	    }
	    else
	    {
		pfNotify(PFNFY_NOTICE, PFNFY_PRINT,
			 "highlightClosestPoint: no closest point?\n");
		/* just leave it like it was */
	    }
	    break;
	}
	case FIND_CONE_INTERSECT_SPHERE:
	case FIND_FRUSTUM_INTERSECT_SPHERE:
	case FIND_FRUSTUM_INTERSECT_TRIANGLES:
	{
	    /* essentially local variables; they're kept around
	       so we don't have to malloc each frame */
	    pfPolytope *ptope = data->ptope;
	    pfFrustum *frust = data->frust;
	    trav->getChan()->getCullPtope(ptope, PF_EYESPACE);
	    calcViewFrustumFromCullPtope(ptope, frust);

	    //
	    // For a confidence test of recommendNear(),
	    // we should do this in both eye space
	    // and object space, and verify that the answers
	    // are the same
	    // XXX do this
	    // XXX (the problem is that the current implmentation
	    // XXX of recommendNear is discontinuous,
	    // XXX so near the discontinuity the answers
	    // XXX returned might be different
	    //

	    if (data->mode == FIND_FRUSTUM_INTERSECT_TRIANGLES)
	    {
		// Transform frustum into world space...
		frust->orthoXform(frust, eyespace_to_objspace_mat);
		    // XXX this isn't really world space; it would be better
		    // XXX to really do it in world space since
		    // XXX then nonuniform transformations would
		    // XXX be handled correctly

		float x,y,z,s,t,r;
		if (pfuGetNearestVisiblePointToEyePlane(child, frust, tex,
				    travmode,
				    &x, &y, &z, &s, &t, &r))
		{
		    /* duplicate code above */
		    dcs->setScale(bsph.radius*.01,bsph.radius*.01,bsph.radius*.01);
		    dcs->setTrans(x, y, z);
		    pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "highlightClosestPoint:");
		    pfNotify(PFNFY_DEBUG, PFNFY_MORE,"        v=(%g %g %g)", x,y,z);
		    if (tex != NULL)
			pfNotify(PFNFY_DEBUG, PFNFY_MORE,
				 "        t=(%g %g %g)", s,t,r);

		}
	    }
	    else /* data->mode == FIND_FRUSTUM_INTERSECT_SPHERE or FIND_CONE_INTERSECT_SPHERE */
	    {
		//
		// Transform the bounding sphere from object space into eye space
		//
		pfMatrix objspace_to_eyespace_mat;
		objspace_to_eyespace_mat.invertOrtho(eyespace_to_objspace_mat);
		pfSphere bsphInEyeSpace;
		bsphInEyeSpace.orthoXform(&bsph, objspace_to_eyespace_mat);

		pfVec3 frontwardBound;
		frust->recommendNear(&bsphInEyeSpace, frontwardBound,
				     data->mode == FIND_CONE_INTERSECT_SPHERE);
		//
		// Transform the point we got back into object space
		//
		frontwardBound.xformPt(frontwardBound, eyespace_to_objspace_mat);

		dcs->setScale(bsph.radius*.01, bsph.radius*.01, bsph.radius*.01);
		dcs->setTrans(frontwardBound[0],
			      frontwardBound[1],
			      frontwardBound[2]);
		pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "highlightClosestPoint(calculated in eye space):");
		pfNotify(PFNFY_DEBUG, PFNFY_MORE, "        v=(%g %g %g)",
			  frontwardBound[0], frontwardBound[1], frontwardBound[2]);
	    }
#if 0 // calcViewFrustumFromPolytope doesn't work unless in eye space
	    {
		// Now do it in object space
		frust->orthoXform(frust, eyespace_to_objspace_mat);
		pfVec3 altFrontwardBound;
		frust->recommendNear(&bsph, altFrontwardBound);
		pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "highlightClosestPoint(calculated in object space):");
		pfNotify(PFNFY_DEBUG, PFNFY_MORE, "        v=(%g %g %g)",
		  altFrontwardBound[0], altFrontwardBound[1], altFrontwardBound[2]);
	    }
#endif
	    break;
	}
    }

    return PFTRAV_CONT;
}

extern "C" pfNode *
pfdLoadFile_closest(char *fileName)
{
    char *lastdot = strrchr(fileName, '.');
    if (lastdot == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE,
		"pfdLoadFile_closest: bad file name %s", fileName);
	return NULL;
    }
    char realName[PATH_MAX];
    strncpy(realName, fileName, lastdot-fileName);
    realName[lastdot-fileName] = '\0';

    pfNode *child = pfdLoadFile(realName);
    if (child == NULL)
	return NULL; /* error message printed already */

    pfGroup *parent = new pfGroup();
    parent->addChild(child);

    pfDCS *dcs = new pfDCS();

    pfGeode *geode = new pfGeode();
    int subdiv = 2;
    pfGeoSet *sphere = pfdNewSphere(subdiv*subdiv * 6*2, pfGetSharedArena());
    pfGeoState *gstate = new pfGeoState();
    gstate->setMode(PFSTATE_ENTEXTURE, PF_OFF);
    gstate->setMode(PFSTATE_TRANSPARENCY, PF_ON);
    sphere->setGState(gstate);
    pfVec4 *transparentred = new pfVec4(1., .25, .25, .5);
    sphere->setAttr(PFGS_COLOR4, PFGS_OVERALL, transparentred, NULL);
    geode->addGSet(sphere);

    dcs->addChild(geode);
    parent->addChild(dcs);

    struct Data *data = (struct Data *)pfMalloc(sizeof(*data),
						pfGetSharedArena());
    assert(data != NULL);
    char *e = getenv("PFCLOSEST_FREQUENCY");
    data->frequency = (e ? *e ? atoi(e) : 1 : 1);
    data->frust = new pfFrustum();
    data->ptope = new pfPolytope();
    assert(data->frust != NULL && data->ptope != NULL);

    if (getenv("PFCLOSEST_FRUSTUM_INTERSECT_SPHERE"))
	data->mode = FIND_FRUSTUM_INTERSECT_SPHERE;
    else if (getenv("PFCLOSEST_FRUSTUM_INTERSECT"))
	data->mode = FIND_FRUSTUM_INTERSECT_TRIANGLES;
    else if (getenv("PFCLOSEST_CONE_INTERSECT_SPHERE"))
	data->mode = FIND_CONE_INTERSECT_SPHERE;
    else /* original default mode that we know and love */
	data->mode = FIND_CLOSEST_POINT_TO_EYE;

    {
	pfTexture *tex;
	e = getenv("PFCLOSEST_TEXTURE_I");
	int PFCLOSEST_TEXTURE_I = (e ? *e ? atoi(e) : 0 : -1);
	if (PFCLOSEST_TEXTURE_I >= 0)
	{
	    tex = pfuFindTexture(child, PFCLOSEST_TEXTURE_I);
	    if (tex == NULL)
		pfNotify(PFNFY_WARN, PFNFY_PRINT,
			 "highlightClosestPoint: no texture with index %d\n",
			 PFCLOSEST_TEXTURE_I);
	}
	else
	    tex = NULL;
	data->tex = tex;
    }

    parent->setTravFuncs(PFTRAV_APP, NULL, highlightClosestPoint);
    parent->setTravData(PFTRAV_APP, data);

    return parent;
}