[BACK]Return to pfuLightPoints.c CVS log [TXT][DIR] Up to [Development] / performer / src / sample / C / asdfly

File: [Development] / performer / src / sample / C / asdfly / pfuLightPoints.c (download)

Revision 1.1, Tue Nov 21 21:39:43 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 1993, 1994, 1995, 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
 */

/*
 *	Terrain tracking light points.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include <malloc.h>
#include <assert.h>

#include "perfly.h"

#include "pfuLightPointsTypes.h"
#include "pfuLightPointsPrototypes.h"

static int  	pfuBillboardDrawCallback (pfTraverser *trav, void *data);
static int  	pfuBillboardPostDrawCallback (pfTraverser *trav, void *data);
static int 	pfuGStateCallback (pfGeoState *gstate, void *userData);

static int 	fluxed_gset_create_func (pfFluxMemory *fmem);
static void 	fluxed_gset_init_func (void *_data, void *_funcData);

typedef struct
{
	pfGeoState	*gstate;
	float		*c_attr;
        pfBox		*box;
} pfuFluxedGSetData;


#undef DEBUG_SHADOW
#undef DEBUG_FLUX

/*============================================================================*/
static void
buildGState(pfGeoState *gs)
/*============================================================================*/
{
    int width, height, depth;
    pfLPointState  *lps;

#if 0
    pfGStateMode(gs, PFSTATE_DECAL,
	PFDECAL_LAYER_DISPLACE | PFDECAL_LAYER_7 | PFDECAL_LAYER_OFFSET);
#endif

    if (! ViewState->lightPoints.useRealLightPoints)
    {
        pfGStateMode(gs, PFSTATE_ANTIALIAS, PFAA_ON);
	pfGStateMode(gs, PFSTATE_CULLFACE, PFCF_OFF);
	pfGStateMode(gs, PFSTATE_ENLIGHTING, PF_OFF);
	pfGStateMode(gs, PFSTATE_ENTEXTURE, PF_OFF);
    }
    else
    {
	lps = pfNewLPState(pfGetSharedArena());
	pfGStateMode(gs, PFSTATE_ENLPOINTSTATE, 1);
	pfGStateAttr(gs, PFSTATE_LPOINTSTATE, lps);

	/*
	 * Light point projected diameter is computed on CPU. Real world
	 * size is 0.07 database units and projected size is clamped be
	 * between 0.25 and 4 pixels.
	 */
	pfLPStateMode(lps, PFLPS_SIZE_MODE, PFLPS_SIZE_MODE_ON);
	pfLPStateVal(lps, PFLPS_SIZE_MIN_PIXEL, 0.75f);
	pfLPStateVal(lps, PFLPS_SIZE_ACTUAL, 0.1f);
	pfLPStateVal(lps, PFLPS_SIZE_MAX_PIXEL, 10.0f);
	pfLPStateMode(lps, PFLPS_RANGE_MODE, PFLPS_RANGE_MODE_TRUE);


	/*
	 * Light points become transparent when their projected diameter is
	 * < 2 pixels. The transparency falloff rate is linear with
	 * projected size with a scale factor of 0.6. The transparency
	 * multiplier, NOT the light point transparency, is clamped to 0.1.
	 */
	pfLPStateVal(lps, PFLPS_TRANSP_PIXEL_SIZE, 2.0f);
	pfLPStateVal(lps, PFLPS_TRANSP_EXPONENT, 1.0f);
	pfLPStateVal(lps, PFLPS_TRANSP_SCALE, 0.6f);
	pfLPStateVal(lps, PFLPS_TRANSP_CLAMP, 0.1f);

	/*
	 * Light points will be fogged as if they were 4 times
	 * nearer to the eye than actual to achieve punch-through.
	 */
	pfLPStateVal(lps, PFLPS_FOG_SCALE, 0.25f);
	pfLPStateMode(lps, PFLPS_DIR_MODE, PFLPS_DIR_MODE_OFF);
#if 0
	/*
	 * Turn on calligraphic light points
	 */
	pfLPStateMode(lps, PFLPS_DRAW_MODE, PFLPS_DRAW_MODE_CALLIGRAPHIC);
	pfLPStateMode(lps, PFLPS_QUALITY_MODE, PFLPS_QUALITY_MODE_HIGH);
#endif

	/*
	 * Disable pfFog effects since light points are fogged by
	 * the pfLPointState.
	 */
	pfGStateMode ( gs, PFSTATE_ENFOG, PF_OFF ) ;
	/*
	 * Disable lighting effects since light points are completely
	 * emissive.
	 */
        pfGStateMode ( gs, PFSTATE_ENHIGHLIGHTING, PF_OFF ) ;
	pfGStateMode ( gs, PFSTATE_CULLFACE, PFCF_OFF ) ;
	pfGStateVal  ( gs, PFSTATE_ALPHAREF, 0.0 ) ;
	pfGStateMode ( gs, PFSTATE_ALPHAFUNC, PFAF_GREATER ) ;

	pfGStateMode(gs, PFSTATE_ENLIGHTING, PF_OFF);
	pfGStateMode(gs, PFSTATE_ENTEXTURE, PF_OFF);
	pfGStateAttr(gs, PFSTATE_TEXENV, NULL);

    }
}

/*============================================================================*/
pfNode *
pfNewLightPointAlignNode(
			int			nofLightPoints,
			float			*vertexList,
			float			*colorList,
			float			*normalList,
			int			is_down_z,
			pfASD			*asd_hook)
/*============================================================================*/
{
    pfFlux				*results;
    pfEngine				*eng;
    pfGeoState				*gs;
    pfGeoSet				*gset, **gset_array;
    pfGeode				*geode;
    static pfVec3			normal;
    static ushort			ncnt[1];
    float				*vlist, *nlist;
    float				*clist;
    int					i, j;
    int					is_pos;
    float				*down;
    pfFlux				*attr;
    pfLOD				*lod;
    pfSphere				sphere;
    pfBox				box, *box_p;
    pfBox				Vbox;
    int					src_index_1, src_index_2;
    int					query_id;
    unsigned long			is;


    /*
     *  =====================
     *	Final tree structure:
     *  =====================
     *
     *      <pfLOD>
     *	       |
     *	    <Geode>
     *	       |
     *      <Geoset>
     *
     */

    gs = pfNewGState(pfGetSharedArena());
    buildGState(gs);

    /*
     *	=====================================================================
     *	Copy points to chared arena.
     *	=====================================================================
     */

    if (vertexList)
    {
	attr = pfNewFlux(nofLightPoints * 3 * sizeof(float),
			 4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
	pfFluxSyncGroup(attr, pfGetASDSyncGroup(asd_hook));

	vlist = pfMalloc(nofLightPoints * 3 * sizeof(float),
			 pfGetSharedArena());

	memcpy(vlist, vertexList, nofLightPoints * 3 * sizeof(float));

	pfFluxInitData(attr, vertexList);

	if (is_down_z)
	{
	    /*
	     *	 Fake displacement from ground.
	     */
	    for (i = 0; i < nofLightPoints; i++)
		vlist[i * 3 + 2] += 2.0;
	}
    }
    else
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT, "Problem: Null vertex list.\n");
	return(NULL);
    }

    if (colorList)
    {
	clist = pfMalloc(nofLightPoints * 4 * sizeof(float),
			 pfGetSharedArena());
	memcpy(clist, colorList, nofLightPoints * 4 * sizeof(float));
    }
    else
	clist = NULL;

    if (normalList)
    {
	nlist = pfMalloc(nofLightPoints * 3 * sizeof(float),
			 pfGetSharedArena());
	memcpy(nlist, normalList, nofLightPoints * 3 * sizeof(float));
    }
    else
	nlist = NULL;

    down = pfMalloc(nofLightPoints * 3 * sizeof(float), pfGetSharedArena());

    for (i = 0; i < nofLightPoints; i++)
    {
	if (is_down_z)
	{
	    down[3 * i + 0] = 0.0;
	    down[3 * i + 1] = 0.0;
	    down[3 * i + 2] = -1.0;
	}
	else
	{
	    down[3 * i + 0] = - vertexList[3*i];
	    down[3 * i + 1] = - vertexList[3*i + 1];
	    down[3 * i + 2] = - vertexList[3*i + 2];
	}
    }

    /*
     *	======================================================================
     *	Run a check to verify that this set of light points 
     *  falls within the given asd... 
     *	======================================================================
     */

    is = pfASDContainsQueryArray (asd_hook, vertexList, down, nofLightPoints);

    if (! (is & PFIS_ALL_IN))
    {
	if (is & PFIS_MAYBE)
	    printf ("Light points are MAYBE on pfASD\n");
	else
	if (is == PFIS_FALSE)
	    printf ("Light Point set is NOT on this pfASD node.\n");
    }

    /*
     *	=====================================================================
     *	Make a standard GeoSet with light points.
     *	=====================================================================
     */

    gset = pfNewGSet(pfGetSharedArena());
    pfGSetPrimType(gset, PFGS_POINTS);
    pfGSetPntSize(gset, 4.0);
    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, (void *)attr, NULL);

    if (clist)
	pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, (void *)clist, NULL);
    else
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT, "Warning: No color for strip of light points.\n");
	pfGSetAttr(gset, PFGS_COLOR4, PFGS_OFF, NULL, NULL);
    }

    if (nlist)
	pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, (void *)nlist, NULL);
    else
	pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OFF, NULL, NULL);

    pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_OFF, NULL, NULL);

    pfGSetNumPrims(gset, nofLightPoints);
    pfGSetGState(gset, gs);
    pfGSetBBox(gset, NULL, PFBOUND_STATIC);

    geode = pfNewGeode();
    pfAddGSet(geode, gset);

    /*
     *	=====================================================================
     *	Setup alignment operation for this geoset.
     *	=====================================================================
     */

    results = pfNewFlux(nofLightPoints * sizeof(pfVec3),
			4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
    pfFluxMode(results, PFFLUX_PUSH, PF_ON);

    query_id = pfASDAddQueryArray(asd_hook, vertexList, down, nofLightPoints,
				  PR_QUERY_POSITION, results);

    pfASDGetQueryArrayPositionSpan(asd_hook, query_id, &box);

    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
	     "asdfly: Light set bbox = (%f,%f) x (%f,%f) x (%f,%f)\n",
	     box.min[0], box.max[0],
	     box.min[1], box.max[1],
	     box.min[2], box.max[2]);


    if (is_down_z)
    {
	/*
	 *	 Update only the Z part.
	 */
#if 1
	eng = pfNewEngine(PFENG_SUM, pfGetSharedArena());
	pfEngineIterations(eng, nofLightPoints, 1);
	pfEngineSrc(eng, PFENG_SUM_SRC(0), vlist, NULL, 0, PF_Z, 3);
	pfEngineSrc(eng, PFENG_SUM_SRC(1), results, NULL, 0, PF_Z, 3);
	pfEngineDst(eng, attr, NULL, PF_Z, 3);

#else

	for (i = 0; i < nofLightPoints; i++)
	{
	    vlist[i * 3 + 0] = 0.0;
	    vlist[i * 3 + 1] = 0.0;
	}

	eng = pfNewEngine(PFENG_SUM, pfGetSharedArena());
	pfEngineIterations(eng, nofLightPoints, 3);
	pfEngineSrc(eng, PFENG_SUM_SRC(0), vlist, NULL, 0, 0, 3);
	pfEngineSrc(eng, PFENG_SUM_SRC(1), results, NULL, 0, 0, 3);
	pfEngineDst(eng, attr, NULL, 0, 3);
#endif
    }
    else
    {
	/*
	 * Update All X,Y,Z.
	 */
	eng = pfNewEngine(PFENG_SUM, pfGetSharedArena());	/* XXX _SET */
	pfEngineIterations(eng, nofLightPoints, 3);
	pfEngineSrc(eng, PFENG_SUM_SRC(0), results, NULL, 0, 0, 3);
	pfEngineDst(eng, attr, NULL, 0, 3);
    }

    /*
     *  Set static bounding boxes to reflect maximum position span of query
     *  points.
     */
    box_p = & box;
    pfSphereAroundBoxes(&sphere, (const pfBox **)&box_p, 1);

    lod = pfNewLOD();
    pfAddChild(lod, geode);

    pfLODRange(lod, 0, 0.0);
    pfLODRange(lod, 1, 40000.0);
    pfLODCenter(lod, sphere.center);

    pfNodeBSphere(lod, &sphere, PFBOUND_STATIC);
    pfNodeBSphere(geode, &sphere, PFBOUND_STATIC);
    pfGSetBBox(gset, &box, PFBOUND_STATIC);

    /*
     *  Evaluation Range
     */
    pfEngineEvaluationRange(eng, sphere.center, 0.0, 40000.0);
    pfEngineMode(eng, PFENG_RANGE_CHECK, PF_ON);

    return((pfNode *)lod);
}

/*============================================================================*/
pfNode *
pfNewObjectAlignNode(
			char			*filename,
			float			x,
			float			y,
			float			z,
			float			*down,
			int			is_normal,
			int			is_azimuth,
			float			*azimuth,
			pfASD			*asd_hook)
/*============================================================================*/
{
    pfFlux				*results;
    pfEngine				*eng;
    pfGeoState				*gs;
    pfGeoSet				*gset;
    pfGeode				*geode;
    static pfVec3			normal;
    static ushort			ncnt[1];
    float				*vlist, *nlist;
    float				*clist;
    int					i;
    pfFlux				*flux_matrix;
    pfFCS				*fcs;
    float				*s_position, *s_down;
    float				*s_azimuth, *s_normal;
    pfLOD				*lod;
    pfSphere				sphere;
    pfSphere				min_sphere, max_sphere, big_sphere;
    pfSphere				*sphere_list[10];
    pfBox				Vbox, *box_p;
    int					query_id;
    int					src_index_1, src_index_2, src_index_3;
    pfMatrix				M;

    /*
     *  =====================
     *	Final tree structure:
     *  =====================
     *
     *	    <pfLOD>
     *	       |
     *	     <FCS>
     *	       |
     *      <pfNode>
     *      /      \
     *       Object
     */

    /*
     *	==================================================================
     *	Copy the position information into shared arena memory. We may not
     *	need them all (dependeing on is_azimuth/is_normal).
     *	==================================================================
     */

    s_position = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    s_down = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    s_normal = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    s_azimuth = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());

    s_position[0] = x;
    s_position[1] = y;
    s_position[2] = z;


    if (down)
    {
	s_down[0] = down[0];
	s_down[1] = down[1];
	s_down[2] = down[2];
	s_normal[0] = - down[0];
	s_normal[1] = - down[1];
	s_normal[2] = - down[2];
    }
    else
    {
	s_down[0] = 0.0;
	s_down[1] = 0.0;
	s_down[2] = -1.0;
	s_normal[0] = 0.0;
	s_normal[1] = 0.0;
	s_normal[2] = 1.0;
    }

    if (azimuth)
    {
	s_azimuth[0] = azimuth[0];
	s_azimuth[1] = azimuth[1];
	s_azimuth[2] = azimuth[2];
    }
    else
    {
	s_azimuth[0] = 1.0;
	s_azimuth[1] = 0.0;
	s_azimuth[2] = 0.0;
    }

    /*
     *	==================================================================
     *	Make a FCS. Put the object under it.
     *	==================================================================
     */

    flux_matrix = pfNewFlux(sizeof(pfMatrix),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
    pfFluxSyncGroup(flux_matrix, pfGetASDSyncGroup(asd_hook));

    pfMakeTransMat (M, x, y, 300.0);
    pfFluxInitData (flux_matrix, M);

    fcs = pfNewFCS(flux_matrix);

    pfAddChild(fcs, pfdLoadFile(filename));

    pfGetNodeBSphere(fcs, &sphere);

    /*
     *	=====================================================================
     *	Distinguish four different possible modes:
     *
     *		1. Align object to terrain normal.
     *		   Use azimuth vector as the nose direction vector (projected
     *		   onto wing plane).
     *
     *		2. No normal but rotate object around it's Z for azimuth.
     *
     *		3. No normal, no azimuth and down direction is -Z.
     *		   We only need to translate the object.
     *
     *		4. no normal, no azimuth but down direction != (-Z).
     *		   Use normal = (- down).
     *	=====================================================================
     */
    if (is_normal)
    {
	results = pfNewFlux(2 * sizeof(pfVec3),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
	pfFluxMode(results, PFFLUX_PUSH, PF_ON);

	query_id = pfASDAddQueryArray(asd_hook, s_position, s_down, 1,
				      PR_QUERY_POSITION | PR_QUERY_NORMAL,
				      results);

	eng = pfNewEngine(PFENG_ALIGN, pfGetSharedArena());
	pfEngineSrc(eng, PFENG_ALIGN_POSITION, results, NULL, 0, 0, 6);
	pfEngineSrc(eng, PFENG_ALIGN_NORMAL, results, NULL, 0, 3, 6);
	pfEngineSrc(eng, PFENG_ALIGN_AZIMUTH, s_azimuth, NULL, 0, 0, 3);
	pfEngineDst(eng, flux_matrix, NULL, 0, 0);
    }
    else
    {
	results = pfNewFlux(sizeof(pfVec3),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
	pfFluxMode(results, PFFLUX_PUSH, PF_ON);

	query_id = pfASDAddQueryArray(asd_hook, s_position, s_down, 1,
				      PR_QUERY_POSITION, results);

	if (is_azimuth)
	{
	    eng = pfNewEngine(PFENG_ALIGN, pfGetSharedArena());
	    pfEngineSrc(eng, PFENG_ALIGN_POSITION, results, NULL, 0, 0, 3);
	    pfEngineSrc(eng, PFENG_ALIGN_NORMAL, s_normal, NULL, 0, 0, 3);
	    pfEngineSrc(eng, PFENG_ALIGN_AZIMUTH, s_azimuth, NULL, 0, 0, 3);
	    pfEngineDst(eng, flux_matrix, NULL, 0, 0);
	}
	else
	{
	    if ((down[0] == 0.0) && (down[1] == 0.0))
	    {
		eng = pfNewEngine(PFENG_ALIGN, pfGetSharedArena());
		pfEngineSrc(eng, PFENG_ALIGN_POSITION, results, NULL, 0, 0, 3);
		pfEngineDst(eng, flux_matrix, NULL, 0, 0);
	    }
	    else
	    {
		s_azimuth[0] = 1.0;
		s_azimuth[1] = 0.0;
		s_azimuth[2] = 0.0;

		eng = pfNewEngine(PFENG_ALIGN, pfGetSharedArena());
		pfEngineSrc(eng, PFENG_ALIGN_POSITION, results, NULL, 0, 0, 3);
		pfEngineSrc(eng, PFENG_ALIGN_NORMAL, s_normal, NULL, 0, 0, 3);
		pfEngineSrc(eng, PFENG_ALIGN_AZIMUTH, s_azimuth, NULL, 0, 0, 3);
		pfEngineDst(eng, flux_matrix, NULL, 0, 0);
	    }
	}
    }

    pfASDGetQueryArrayPositionSpan(asd_hook, query_id, &Vbox);

    /*
     *	==================================================================
     *	Calculate the maximum bbox of the morphing object.
     *	==================================================================
     */
    for (i = 0; i < 3; i++)
    {
	Vbox.min[i] = Vbox.min[i] - sphere.radius;
	Vbox.max[i] = Vbox.max[i] + sphere.radius;
    }

    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
	     "Object <%s> bbox = (%f,%f) x (%f,%f) x (%f,%f)\n",
	     filename,
	     Vbox.min[0], Vbox.max[0],
	     Vbox.min[1], Vbox.max[1],
	     Vbox.min[2], Vbox.max[2]);

    box_p = & Vbox;
    pfSphereAroundBoxes(&big_sphere, (const pfBox **)&box_p, 1);

    lod = pfNewLOD();
    pfAddChild(lod, fcs);

    pfLODRange(lod, 0, 0.0);
    pfLODRange(lod, 1, 10000.0 + big_sphere.radius);
    pfLODCenter(lod, big_sphere.center);

    pfNodeBSphere(lod, &big_sphere, PFBOUND_STATIC);
    pfNodeBSphere(fcs, &big_sphere, PFBOUND_STATIC);

    /*
     *  Evaluation Range
     */
    pfEngineEvaluationRange(eng, sphere.center, 0.0, 10000.0);
    pfEngineMode(eng, PFENG_RANGE_CHECK, PF_ON);

    return((pfNode *)lod);
}

/*============================================================================*/
pfNode *
pfNewBillboardAlignNode(
			char			*filename,
			float			x,
			float			y,
			float			z,
			float			*down,
			float			width,
			float			height,
			pfASD			*asd_hook)
/*============================================================================*/
{
    pfGeoState				*gstate;
    pfGeoSet				*gset;
    int					i;
    pfBillboard				*bill;
    float				*s_position, *s_down, *n_down;
    pfLOD				*lod;
    pfSphere				sphere;
    pfSphere				min_sphere, max_sphere, big_sphere;
    pfSphere				*sphere_list[10];
    pfBox				Vbox, *box_p;
    int					query_id;
    int					src_index_1, src_index_2, src_index_3;
    float				TexCoord[4][2];
    float				VertexCoord[4][3];
    float				*vlist, *tlist, *clist;
    float				*len;
    pfTexture       			*tex;
    pfTexEnv        			*tev;
    pfFlux				*flux_position;
    float				span;


    TexCoord[0][0] = 0.0;
    TexCoord[0][1] = 0.0;

    TexCoord[1][0] = 1.0;
    TexCoord[1][1] = 0.0;

    TexCoord[2][0] = 1.0;
    TexCoord[2][1] = 1.0;

    TexCoord[3][0] = 0.0;
    TexCoord[3][1] = 1.0;

    VertexCoord[0][0] = - width * 0.5;
    VertexCoord[0][1] = 0.0;
    VertexCoord[0][2] = 0.0;

    VertexCoord[1][0] = width * 0.5;
    VertexCoord[1][1] = 0.0;
    VertexCoord[1][2] = 0.0;

    VertexCoord[2][0] = width * 0.5;
    VertexCoord[2][1] = 0.0;
    VertexCoord[2][2] = height;

    VertexCoord[3][0] = -width * 0.5;
    VertexCoord[3][1] = 0.0;
    VertexCoord[3][2] = height;

    /*
     *	=========================================
     *	Generate attributes for geoset.
     *	=========================================
     */


    vlist = pfMalloc (4 * 3 * sizeof (float), pfGetSharedArena());
    clist = pfMalloc (4 * sizeof (float), pfGetSharedArena());
    tlist = pfMalloc (4 * 2 * sizeof (float), pfGetSharedArena());
    len =   pfMalloc (sizeof (int), pfGetSharedArena());

    memcpy (vlist, VertexCoord, 3 * 4 * sizeof (float));
    memcpy (tlist, TexCoord, 2 * 4 * sizeof (float));

    clist[0] = 1.0;
    clist[1] = 1.0;
    clist[2] = 1.0;
    clist[3] = 1.0;

    len[0] = 4;

    /*
     *	=========================================
     *	Load texture for billboard
     *	=========================================
     */
    printf ("Load Billboard texture file %s\n", filename);

    tex = pfNewTex (pfGetSharedArena());
    if (! pfLoadTexFile (tex, filename))
	fprintf (stderr, "Texture %s: Not loaded (Error).\n", filename);;

    pfTexRepeat(tex, PFTEX_WRAP, PFTEX_CLAMP);

    tev = pfNewTEnv (pfGetSharedArena());

    gstate = pfNewGState (pfGetSharedArena());
    pfMakeBasicGState (gstate);

    pfGStateMode (gstate, PFSTATE_CULLFACE, PFCF_OFF);
    pfGStateMode (gstate, PFSTATE_ENLIGHTING, PF_OFF);

    pfGStateMode (gstate, PFSTATE_ENTEXTURE, 1);
    pfGStateAttr (gstate, PFSTATE_TEXTURE, tex);
    pfGStateAttr (gstate, PFSTATE_TEXENV, tev);

    pfGStateMode (gstate, PFSTATE_TRANSPARENCY, PFTR_ON);
    pfGStateMode (gstate, PFSTATE_ALPHAFUNC, PFAF_GEQUAL);
    pfGStateVal  (gstate, PFSTATE_ALPHAREF, 0.4);

    /*
     *  =====================
     *	Final tree structure:
     *  =====================
     *
     *	    <pfLOD>
     *	       |
     *	   <Billboard>
     *	       |
     *     <pfGeoSet>
     *
     */

    /*
     *	==================================================================
     *	Copy the position information into shared arena memory. We may not
     *	need them all (dependeing on is_azimuth/is_normal).
     *	==================================================================
     */

    s_position = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    s_down = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    n_down = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());

    s_position[0] = x;
    s_position[1] = y;
    s_position[2] = z;


    if (down)
    {
	s_down[0] = down[0];
	s_down[1] = down[1];
	s_down[2] = down[2];
    }
    else
    {
	s_down[0] = 0.0;
	s_down[1] = 0.0;
	s_down[2] = -1.0;
    }

    n_down[0] = - s_down[0];
    n_down[1] = - s_down[1];
    n_down[2] = - s_down[2];

    /*
     *	==================================================================
     *	Make a Billboard node. Put the object under it.
     *	==================================================================
     */

    flux_position = pfNewFlux(sizeof(pfVec3),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());

    pfFluxSyncGroup(flux_position, pfGetASDSyncGroup(asd_hook));

    bill = pfNewBboard();
    pfBboardPosFlux (bill, flux_position);

    pfBboardAxis(bill, n_down);
    pfBboardMode(bill, PFBB_ROT, PFBB_AXIAL_ROT);


    /*
     *	==================================================================
     *	Make Geoset for billboard geometry
     *	==================================================================
     */
    gset = pfNewGSet(pfGetSharedArena());

    pfGSetGState (gset, gstate);

    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, vlist, NULL);
    pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_PER_VERTEX, tlist, NULL);
    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, clist, NULL);
    pfGSetAttr (gset, PFGS_NORMAL3, PFGS_OFF, NULL, NULL);

    pfGSetPrimType(gset, PFGS_QUADS);
    pfGSetNumPrims(gset, 1);

    pfAddGSet(bill, gset);

    /*
     *	==================================================================
     *	Setup pfASD query.
     *	==================================================================
     */
    pfFluxMode(flux_position, PFFLUX_PUSH, PF_ON);

    query_id = pfASDAddQueryArray(asd_hook, s_position, s_down, 1,
				      PR_QUERY_POSITION, flux_position);

    pfASDGetQueryArrayPositionSpan(asd_hook, query_id, &Vbox);

    /*
     *	==================================================================
     *	Calculate the maximum bbox of the morphing object.
     *	==================================================================
     */
    span = sqrt (height * height + width * width * 0.25);
    for (i = 0; i < 3; i++)
    {
	Vbox.min[i] = Vbox.min[i] - span;
	Vbox.max[i] = Vbox.max[i] + span;
    }

    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
	     "Object <%s> bbox = (%f,%f) x (%f,%f) x (%f,%f)\n",
	     filename,
	     Vbox.min[0], Vbox.max[0],
	     Vbox.min[1], Vbox.max[1],
	     Vbox.min[2], Vbox.max[2]);

    box_p = & Vbox;
    pfSphereAroundBoxes(&big_sphere, (const pfBox **)&box_p, 1);

    lod = pfNewLOD();
    pfAddChild(lod, bill);

    pfLODRange(lod, 0, 0.0);
    pfLODRange(lod, 1, 40000.0 + big_sphere.radius);
    pfLODCenter(lod, big_sphere.center);

    pfNodeBSphere(lod, &big_sphere, PFBOUND_STATIC);
    pfNodeBSphere(bill, &big_sphere, PFBOUND_STATIC);

#ifdef DEBUG_SHADOW
    pfNodeTravFuncs (bill, PFTRAV_DRAW, pfuBillboardDrawCallback, 
					pfuBillboardPostDrawCallback);
    pfNodeTravData (bill, PFTRAV_DRAW, (void *) ((int) y));

    pfGStateFuncs (gstate, pfuGStateCallback, NULL, (void *) ((int) y));
#endif

    return((pfNode *)lod);
}
/*============================================================================*/
pfNode *
pfuNewGSetShadowNode (pfGeoSet *_gset, 
			float		*_base, 
			float		*_down, 
			float		*_azimuth, 
			float		*_projection, 
			char		*_texture_filename, 
			unsigned long	_mask,
			unsigned long	_transform_mask,
			float		*_c,
			pfASD		*asd_hook)
/*============================================================================*/
{
    pfGeode		*geode;
    pfGeoSet		*gset;
    pfGeoState		*gstate;
    pfTexture      	*tex;
    pfTexEnv        	*tev;
    unsigned long	query_mask;
    pfuAppCallbackData	*userData;
    int			query_id;
    pfBox		box;
    pfEngine 		*engine;
    pfuShadowFluxData	shadow_data;
    pfSwitch 		*sw;
    pfVec3		center;
    int			i, do_wrap;
    int			nof_geosets;
    int			dummy;
    int			size;
    float		fdummy;
    int			_nof_triangles;
    ushort		*dummy_ushort;
    float		*_v, *_t;
    pfFlux      	*fluxed_gset;
    pfuFluxedGSetData	*fluxed_gset_data;
    float		*c_attr;

    _nof_triangles = pfGetGSetNumPrims (_gset);

    /*
     *	===========================================================
     *	Build output Geoset
     *	===========================================================
     */
    query_mask = 0;

    if (_mask & PR_QUERY_TRI_COORD)
	query_mask |= PR_QUERY_TRI_COORD;

    if (_mask & PR_QUERY_TRI_TEXTURE)
	query_mask |= PR_QUERY_TRI_TEXTURE;

    if (_mask & PR_QUERY_TRI_COLOR)
	query_mask |= PR_QUERY_TRI_COLOR;

    if (_mask & PR_QUERY_TRI_NORMAL)
	query_mask |= PR_QUERY_TRI_NORMAL;


    fluxed_gset = pfNewFluxInitFunc (
                                fluxed_gset_create_func,
                                4 + PFFLUX_DEFAULT_NUM_BUFFERS,
                                pfGetSharedArena());

    pfFluxSyncGroup((pfFlux *) fluxed_gset, pfGetASDSyncGroup(asd_hook));
    pfFluxMode((pfFlux *) fluxed_gset, PFFLUX_PUSH, PF_ON);


    /*
     *	===========================================================
     *	connect to asd.
     *	===========================================================
     */

    pfGetGSetAttrLists (_gset, PFGS_COORD3, (void **) &_v, &dummy_ushort);

    pfdAlignVerticesToASD (asd_hook, 
			    (pfVec3 *) _v, 
			    _nof_triangles * 3, 
			    _base, 
			    _down, 
			    (_transform_mask & PR_ALIGN_AXIS_BILLBOARD) ? 
			    _projection : _azimuth,
			    _transform_mask, 
			    (pfVec3 *) _v);

    pfdProjectVerticesOnASD (asd_hook, 
			    (pfVec3 *) _v, 
			    _nof_triangles * 3, 
			    _projection, 
			    _down, 
			    (pfVec3 *) _v);

    query_id = pfASDAddQueryGeoSet (asd_hook, 
			    _gset, 
			    _down, 
			    query_mask, 
			    fluxed_gset);

    pfASDGetQueryArrayPositionSpan(asd_hook, query_id, &box);

    /*
     *	===========================================================
     *	Build state.
     *	===========================================================
     */
    gstate = pfNewGState (pfGetSharedArena());
    pfMakeBasicGState (gstate);
    pfGStateMode (gstate, PFSTATE_CULLFACE, PFCF_OFF);
    pfGStateMode (gstate, PFSTATE_ENLIGHTING, PF_OFF);
    pfGStateMode (gstate, PFSTATE_TRANSPARENCY, PFTR_ON | PFTR_NO_OCCLUDE);
    pfGStateMode(gstate, PFSTATE_DECAL,
	PFDECAL_LAYER_DISPLACE | PFDECAL_LAYER_7 | PFDECAL_LAYER_OFFSET);

    /*
     *	===========================================================
     *	If needed - add texture info.
     *	===========================================================
     */
    if (_mask & PR_QUERY_TRI_TEXTURE)
    {
	tex = pfNewTex (pfGetSharedArena());
	pfLoadTexFile (tex, _texture_filename);

	do_wrap = 0;
	pfGetGSetAttrLists (_gset, PFGS_TEXCOORD2, 
				(void **) &_t, &dummy_ushort);
	for (i = 0 ; i < 2 * 3 * _nof_triangles ; i ++)
	    if ((_t[i] < -0.001) || (_t[i] > 1.001))
		do_wrap = 1;

	if (do_wrap)
	    pfTexRepeat(tex, PFTEX_WRAP, PFTEX_WRAP);
	else
	    pfTexRepeat(tex, PFTEX_WRAP, PFTEX_CLAMP);

	tev = pfNewTEnv (pfGetSharedArena());

	pfGStateMode (gstate, PFSTATE_ENTEXTURE, 1);
	pfGStateAttr (gstate, PFSTATE_TEXTURE, tex);
	pfGStateAttr (gstate, PFSTATE_TEXENV, tev);
    }
    else
	pfGStateMode (gstate, PFSTATE_ENTEXTURE, 0);


    /*
     *	===========================================================
     *	If needed - add overall color
     *	===========================================================
     */

    c_attr = NULL;
    if (! (_mask & PR_QUERY_TRI_COLOR))
    {
        c_attr = pfMalloc(4 * sizeof(float), pfGetSharedArena());

        if (_c)
        {
            /*
             *  If we have any color specified, use it as overall color.
             */
            c_attr[0] = _c[0];
            c_attr[1] = _c[1];
            c_attr[2] = _c[2];
            c_attr[3] = _c[3];
        }
        else
        {
            c_attr[0] = 0.1;
            c_attr[1] = 0.1;
            c_attr[2] = 0.1;
            c_attr[3] = 0.6;
        }
    }

    /*
     *	====================================================================
     *	Init other attributes of the geoset (not taken care of by the init 
     *  function or the pfASD query.
     *	====================================================================
     */

    fluxed_gset_data = (pfuFluxedGSetData *) 
		    pfMalloc(sizeof(pfuFluxedGSetData), pfGetSharedArena());

    fluxed_gset_data -> c_attr = c_attr;
    fluxed_gset_data -> gstate = gstate;
    fluxed_gset_data -> box = & box;

    pfFluxCallDataFunc (fluxed_gset, fluxed_gset_init_func, 
					(void *) fluxed_gset_data);

    /* 
     *  ====================================================================
     *	Set user data on flux, so that future flux-generated buffers will 
     *  initialize correctly.
     *  ====================================================================
     */
    pfUserData ((pfObject *) fluxed_gset, (void *) fluxed_gset_data);

    geode = pfNewGeode();

    gset = (pfGeoSet*) pfGetFluxCurData (fluxed_gset);
    pfAddGSet(geode, gset);

    return ((pfNode *) geode);
}

/*============================================================================*/
static int  	pfuBillboardDrawCallback (pfTraverser *trav, void *data)
/*============================================================================*/
{
    pfGeoSet			*gset;
    pfBillboard			*bill;
    pfTexture			*tex, *active;
    pfGeoState			*gstate;
    int				mode;

    fprintf (stderr, "================================== DRAW  %d\n", 
								(int) data);

    bill = (pfBillboard *) pfGetTravNode (trav);

    fprintf (stderr, "DRAW: billboard = %x\n", (int) bill);

    gset = pfGetGSet (bill, 0);

    fprintf (stderr, "DRAW: gset = %x\n", (int) gset);

    gstate = pfGetGSetGState (gset);

    fprintf (stderr, "DRAW: gstate = %x\n", (int) gstate);

    tex = pfGetGStateAttr(gstate, PFSTATE_TEXTURE);

    if (tex == NULL)
	fprintf (stderr, "DRAW: No texture\n");
    else
	fprintf (stderr, "DRAW: texture name = <%s>\n", pfGetTexName (tex));

    mode = pfGetGStateMode (gstate, PFSTATE_ENTEXTURE);

    fprintf (stderr, "DRAW: texture mode == %d\n", mode);

    active = pfGetCurTex();

    fprintf (stderr, "DRAW: texture = %x, active = %x\n", 
			(int) tex, (int) active);
    fprintf (stderr, "DRAW: active texture name = <%s>\n", 
					pfGetTexName (active));

    return (PFTRAV_CONT);
}

/*============================================================================*/
static int  	pfuBillboardPostDrawCallback (pfTraverser *trav, void *data)
/*============================================================================*/
{
    pfGeoSet			*gset;
    pfBillboard			*bill;
    pfTexture			*tex, *active;
    pfGeoState			*gstate;
    int				mode;

    fprintf (stderr, "============================ POST DRAW == %d\n", 
								(int) data);

    bill = (pfBillboard *) pfGetTravNode (trav);

    fprintf (stderr, "DRAW: billboard = %x\n", (int) bill);

    gset = pfGetGSet (bill, 0);

    fprintf (stderr, "DRAW: gset = %x\n", (int) gset);

    gstate = pfGetGSetGState (gset);

    fprintf (stderr, "DRAW: gstate = %x\n", (int) gstate);

    tex = pfGetGStateAttr(gstate, PFSTATE_TEXTURE);

    if (tex == NULL)
	fprintf (stderr, "DRAW: No texture\n");
    else
	fprintf (stderr, "DRAW: texture name = <%s>\n", pfGetTexName (tex));

    mode = pfGetGStateMode (gstate, PFSTATE_ENTEXTURE);

    fprintf (stderr, "DRAW: texture mode == %d\n", mode);

    active = pfGetCurTex();

    fprintf (stderr, "DRAW: texture = %x, active = %x\n", 
			(int) tex, (int) active);
    fprintf (stderr, "DRAW: active texture name = <%s>\n", 
					pfGetTexName (active));

    return (PFTRAV_CONT);
}

/*============================================================================*/
static int pfuGStateCallback (pfGeoState *gstate, void *userData)
/*============================================================================*/
{
    pfTexture   *tex, *active;
    int		mode;
    pfGeoState	*scene_gstate;
    pfGeoState	*cur_gstate;

    fprintf (stderr, "================= GSTATE %d\n", (int) userData);

    active = pfGetCurTex();
    fprintf (stderr, "GSTATE: active texture name = <%s>\n", 
					pfGetTexName (active));
    fprintf (stderr, "============\n");

/*==================================================================*/

    tex = pfGetGStateAttr(gstate, PFSTATE_TEXTURE);

    if (tex == NULL)
	fprintf (stderr, "GSTATE: No texture\n");
    else
	fprintf (stderr, "GSTATE: texture name = <%s>\n", pfGetTexName (tex));

    mode = pfGetGStateMode (gstate, PFSTATE_ENTEXTURE);

    fprintf (stderr, "GSTATE: texture mode == %d\n", mode);

    pfPrint (gstate, 0, 0, 0);

/*==================================================================*/

    fprintf (stderr, "SCENE GSTATE:\n");

    fprintf (stderr, "============\n");

    scene_gstate = pfGetSceneGState(ViewState->scene);

    tex = pfGetGStateAttr(scene_gstate, PFSTATE_TEXTURE);

    if (tex == NULL)
	fprintf (stderr, "SCENE GSTATE: No texture\n");
    else
	fprintf (stderr, "SCENE GSTATE: texture name = <%s>\n", 
						pfGetTexName (tex));

    mode = pfGetGStateMode (scene_gstate, PFSTATE_ENTEXTURE);
    fprintf (stderr, "SCENE GSTATE: texture mode == %d\n", mode);

    pfPrint (scene_gstate, 0, 0, 0);

/*==================================================================*/

    fprintf (stderr, "CUR   GSTATE:\n");

    fprintf (stderr, "============\n");

    cur_gstate = pfGetCurGState();

    tex = pfGetGStateAttr(cur_gstate, PFSTATE_TEXTURE);

    if (tex == NULL)
	fprintf (stderr, "CUR   GSTATE: No texture\n");
    else
	fprintf (stderr, "CUR   GSTATE: texture name = <%s>\n", 
						pfGetTexName (tex));

    mode = pfGetGStateMode (cur_gstate, PFSTATE_ENTEXTURE);
    fprintf (stderr, "CUR   GSTATE: texture mode == %d\n", mode);

    pfPrint (cur_gstate, 0, 0, 0);


    fprintf (stderr, "=================\n");

    return (PFTRAV_CONT);
}


/*============================================================================*/
pfNode *
pfNewAnimationAlignNode(
			char			*filename,
			float			x,
			float			y,
			float			z,
			float			*down,
			float			trip_radius,
			int			*o_query_id,
			pfFlux			**o_azimuth_flux,
			pfASD			*asd_hook)
/*============================================================================*/
{
    pfFlux				*results;
    pfEngine				*eng;
    pfGeoState				*gs;
    pfGeoSet				*gset;
    pfGeode				*geode;
    static pfVec3			normal;
    static ushort			ncnt[1];
    float				*vlist, *nlist;
    float				*clist;
    int					i;
    pfFlux				*flux_matrix;
    pfFCS				*fcs;
    float				*s_position, *s_down;
    pfLOD				*lod;
    pfSphere				sphere;
    pfSphere				min_sphere, max_sphere, big_sphere;
    pfSphere				*sphere_list[10];
    pfBox				Vbox, *box_p;
    int					query_id;
    int					src_index_1, src_index_2, src_index_3;
    pfFlux				*azimuth_flux;
    float				s_azimuth[3];
    pfMatrix				M;

    /*
     *  =====================
     *	Final tree structure:
     *  =====================
     *
     *	    <pfLOD>
     *	       |
     *	     <FCS>
     *	       |
     *      <pfNode>
     *      /      \
     *       Object
     */

    /*
     *	==================================================================
     *	Copy the position information into shared arena memory. We may not
     *	need them all (dependeing on is_azimuth/is_normal).
     *	==================================================================
     */

    s_position = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());
    s_down = (float *)pfMalloc(3 * sizeof(float), pfGetSharedArena());

    s_position[0] = x;
    s_position[1] = y;
    s_position[2] = z;


    if (down)
    {
	s_down[0] = down[0];
	s_down[1] = down[1];
	s_down[2] = down[2];
    }
    else
    {
	s_down[0] = 0.0;
	s_down[1] = 0.0;
	s_down[2] = -1.0;
    }

    /*
     *	==================================================================
     *	Make a FCS. Put the object under it.
     *	==================================================================
     */

    flux_matrix = pfNewFlux(sizeof(pfMatrix),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
    pfFluxSyncGroup(flux_matrix, pfGetASDSyncGroup(asd_hook));

    pfMakeTransMat (M, x, y, 300.0);
    pfFluxInitData (flux_matrix, M);

    fcs = pfNewFCS(flux_matrix);

    pfAddChild(fcs, pfdLoadFile(filename));

    pfGetNodeBSphere(fcs, &sphere);

    /*	
     *	=====================================================================
     *	Make flux for APP control over azimuth.
     *	=====================================================================
     */

    azimuth_flux = pfNewFlux(3 * sizeof(float),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());

    s_azimuth[0] = 0.0;
    s_azimuth[1] = 1.0;
    s_azimuth[2] = 0.0;

    pfFluxInitData(azimuth_flux, s_azimuth);

    *o_azimuth_flux = azimuth_flux;

    /*
     *	=====================================================================
     *	Align object to terrain normal.
     *	Use azimuth vector as the nose direction vector (projected
     *	onto wing plane).
     *	=====================================================================
     */
    results = pfNewFlux(2 * 3 * sizeof(float),
			    4 + PFFLUX_DEFAULT_NUM_BUFFERS, pfGetSharedArena());
    pfFluxMode(results, PFFLUX_PUSH, PF_ON);

    query_id = pfASDAddQueryArray(asd_hook, s_position, s_down, 1,
				  PR_QUERY_POSITION | PR_QUERY_NORMAL,
				  results);

    eng = pfNewEngine(PFENG_ALIGN, pfGetSharedArena());
    pfEngineSrc(eng, PFENG_ALIGN_POSITION, results, NULL, 0, 0, 0);
    pfEngineSrc(eng, PFENG_ALIGN_NORMAL, results, NULL, 0, 3, 0);
    pfEngineSrc(eng, PFENG_ALIGN_AZIMUTH, azimuth_flux, NULL, 0, 0, 0);
    pfEngineDst(eng, flux_matrix, NULL, 0, 0);
    /*
     *  Evaluation Range
     */
    pfEngineEvaluationRange(eng, sphere.center, 0.0, 10000.0);
    pfEngineMode(eng, PFENG_RANGE_CHECK, PF_ON);

    pfASDGetQueryArrayPositionSpan(asd_hook, query_id, &Vbox);

    /*
     *	==================================================================
     *	Calculate the maximum bbox of the morphing object.
     *	==================================================================
     */
    for (i = 0; i < 3; i++)
    {
	Vbox.min[i] = Vbox.min[i] - sphere.radius - trip_radius;
	Vbox.max[i] = Vbox.max[i] + sphere.radius + trip_radius;
    }

    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
	     "Animation <%s> bbox = (%f,%f) x (%f,%f) x (%f,%f)\n",
	     filename,
	     Vbox.min[0], Vbox.max[0],
	     Vbox.min[1], Vbox.max[1],
	     Vbox.min[2], Vbox.max[2]);

    box_p = & Vbox;
    pfSphereAroundBoxes(&big_sphere, (const pfBox **)&box_p, 1);

    lod = pfNewLOD();
    pfAddChild(lod, fcs);

    pfLODRange(lod, 0, 0.0);
    pfLODRange(lod, 1, 10000.0 + big_sphere.radius);
    pfLODCenter(lod, big_sphere.center);

    pfNodeBSphere(lod, &big_sphere, PFBOUND_STATIC);
    pfNodeBSphere(fcs, &big_sphere, PFBOUND_STATIC);


    *o_query_id = query_id;

    return((pfNode *)lod);
}

/*===========================================================================*/
static int fluxed_gset_create_func (pfFluxMemory *fmem)
/*===========================================================================*/
{
    float       *v_attr, *c_attr;
    pfGeoState  *gstate;
    pfGeoSet    *gset;
    pfFlux	*flux;
    void	*flux_data;

    if (fmem == NULL)
        return pfFluxedGSetInit(fmem);

    pfFluxedGSetInit(fmem);

    gset = (pfGeoSet*)pfGetData(fmem);

    /*
     *  ===========================================================
     *  Gset attributes.
     *  ===========================================================
     */
    pfGSetPrimType(gset, PFGS_TRIS);
    pfGSetNumPrims(gset, 0);

    /*
     *  =======================================================================
     *	If we have user-data on the flux, use it to initialize the flux geoset.
     * 	This should only happen for newly generated (on-the-fly) flux buffers.
     *  =======================================================================
     */
    flux = pfGetFluxMemoryFlux(fmem);

    flux_data = pfGetUserData ((pfObject *) flux);
    if (flux_data)
	fluxed_gset_init_func ((void *) gset, flux_data);

    return 0;
}

/*===========================================================================*/
static void fluxed_gset_init_func (void *_data, void *_funcData)
/*===========================================================================*/
{
    pfuFluxedGSetData 	*ShadowData;
    pfGeoSet		*gset;

    ShadowData = (pfuFluxedGSetData *) _funcData;
    gset = (pfGeoSet *) _data;

    pfGSetGState(gset, ShadowData -> gstate);

    if (ShadowData -> c_attr)
	pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, ShadowData -> c_attr, NULL);

    pfGSetBBox(gset, ShadowData -> box, PFBOUND_STATIC);
}