[BACK]Return to spherefront2.C CVS log [TXT][DIR] Up to [Development] / performer / src / lib / libpfutil

File: [Development] / performer / src / lib / libpfutil / spherefront2.C (download)

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

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

/*
 * Copyright 2000, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * UNPUBLISHED -- Rights reserved under the copyright laws of the United
 * States.   Use of a copyright notice is precautionary only and does not
 * imply publication or disclosure.
 *
 * U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to restrictions
 * as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or
 * in similar or successor clauses in the FAR, or the DOD or NASA FAR
 * Supplement.  Contractor/manufacturer is Silicon Graphics, Inc.,
 * 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that (i) the above copyright notices and this
 * permission notice appear in all copies of the software and related
 * documentation, and (ii) the name of Silicon Graphics may not be
 * used in any advertising or publicity relating to the software
 * without the specific, prior written permission of Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SILICON GRAPHICS BE LIABLE FOR ANY SPECIAL,
 * INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY
 * THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE
 * OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.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>

#define FOR(i,n) for ((i) = 0; (i) < (n); ++(i))


//
// Conversion between various coordinate spaces...
//	x,y,z <-> lon,lat <-> s,t
// XXX need scale and bias for general conversion between s,t and lon,lat!
// 
static inline void xyz_to_lonlat(double x, double y, double z, double *Lon, double *Lat)
{
    *Lon = atan2(x,-y); // sic
    *Lat = asin(PF_CLAMP(z,-1,1));
}
static inline void lonlat_to_xyz(double lon, double lat, double *X, double *Y, double *Z)
{
    double coslat = cos(lat);
    *X = sin(lon) * coslat;
    *Y = -cos(lon) * coslat;
    *Z = sin(lat);
}
static inline void st_to_lonlat(double s, double t, double *Lon, double *Lat)
{
    *Lon = 2*M_PI * (s-.5);
    *Lat = M_PI * (t-.5);
}
static inline void lonlat_to_st(double lon, double lat, double *S, double *T)
{
    *S = lon * (1/(2*M_PI)) + .5;
    *T = lat * (1/M_PI) + .5;
}
static inline void xyz_to_st(double x, double y, double z, double *S, double *T)
{
    double lon,lat;
    xyz_to_lonlat(x,y,z,&lon,&lat);
    lonlat_to_st(lon,lat,S,T);
}
static inline void st_to_xyz(double s, double t, double *X, double *Y, double *Z)
{
    double lon,lat;
    st_to_lonlat(s,t,&lon,&lat);
    lonlat_to_xyz(lon,lat,X,Y,Z);
}


//
// Structure definitions...
//

// possible edge status values...
#define INELIGIBLE_TO_BE_SPLIT 0
#define ELIGIBLE_TO_BE_SPLIT 1
#define HAS_BEEN_SPLIT 2
struct Edge {
    int level;
    int status;	// one of the above constants
    int from, to;	// indices into vertices
    int children[2];	// pieces at next level (self if not split)
};
struct Tri {
    int level;
    int orientation; // 1 for CCW, -1 for CW
    int sides[3]; // indices into edges
    int sidedirection[3]; // -1 or 1, always 1 for a splittable edge
    int children[4]; // pieces at next level
};

struct Mesh {
    void *arena;
    int maxntris, maxnedges, maxnverts;
    int nverts, nedges, ntris;
    double (*vc)[3];
    double (*tc)[2];
    struct Edge *edges;
    struct Tri *tris;
};

#define INIT_EDGE(_edges, _edgei, _level, _status, _from, _to) \
	((_edges)[_edgei].level = (_level), \
	 (_edges)[_edgei].status = (_status), \
	 (_edges)[_edgei].from = (_from), \
	 (_edges)[_edgei].to = (_to), \
	 (_edges)[_edgei].children[0] = (_edgei), /* important */ \
	 (_edges)[_edgei].children[1] = (_edgei))  /* important */

static struct Mesh *
allocMesh(int maxntris, int maxnedges, int maxnverts, void *arena)
{
    struct Mesh *mesh = (struct Mesh *)pfMalloc(sizeof(*mesh), arena);
    assert(mesh != NULL);

    mesh->arena = arena;
    mesh->vc = (double (*)[3])pfMalloc(maxnverts * sizeof(*mesh->vc), arena);
    mesh->tc = (double (*)[2])pfMalloc(maxnverts * sizeof(*mesh->tc), arena);
    mesh->edges = (struct Edge *)pfMalloc(maxnedges * sizeof(*mesh->edges), arena);
    mesh->tris = (struct Tri *)pfMalloc(maxntris * sizeof(*mesh->tris), arena);
    assert(mesh->vc && mesh->tc && mesh->edges && mesh->tris);
    mesh->maxnverts = maxnverts;
    mesh->maxnedges = maxnedges;
    mesh->maxntris = maxntris;
    mesh->nverts = 0;
    mesh->nedges = 0;
    mesh->ntris = 0;
    return mesh;
}

#ifdef MAIN
static void
freeMesh(struct Mesh *mesh)
{
    if (mesh != NULL)
    {
	if (mesh->vc) pfDelete(mesh->vc);
	if (mesh->tc) pfDelete(mesh->tc);
	if (mesh->edges) pfDelete(mesh->edges);
	if (mesh->tris) pfDelete(mesh->tris);

	pfDelete(mesh);
    }
}
#endif // MAIN

static inline void expandVerts(struct Mesh *mesh, int n, double (**vc)[3], double (**tc)[2])
{
    if (n > mesh->maxnverts)
    {
	mesh->maxnverts = n*3/2;
	mesh->vc = (double (*)[3])pfRealloc(mesh->vc, mesh->maxnverts * sizeof(*mesh->vc));
	mesh->tc = (double (*)[2])pfRealloc(mesh->tc, mesh->maxnverts * sizeof(*mesh->tc));
	assert(mesh->vc != NULL && mesh->tc != NULL);
	fprintf(stderr, "Expanding to %d verts\n", mesh->maxnverts);
	*vc = mesh->vc;
	*tc = mesh->tc;
    }
}
static inline void expandEdges(struct Mesh *mesh, int n, struct Edge **edges)
{
    if (n > mesh->maxnedges)
    {
	mesh->maxnedges = n*3/2;
	mesh->edges = (struct Edge *)pfRealloc(mesh->edges, mesh->maxnedges * sizeof(*mesh->edges));
	assert(mesh->edges != NULL);
	fprintf(stderr, "Expanding to %d edges\n", mesh->maxnedges);
	*edges = mesh->edges;
    }
}
static inline void expandTris(struct Mesh *mesh, int n, struct Tri **tris)
{
    if (n > mesh->maxntris)
    {
	mesh->maxntris = n*3/2;
	mesh->tris = (struct Tri *)pfRealloc(mesh->tris, mesh->maxntris * sizeof(*mesh->tris));
	assert(mesh->tris != NULL);
	fprintf(stderr, "Expanding to %d tris\n", mesh->maxntris);
	*tris = mesh->tris;
    }
}

//
// Initialization function...
//
#define NORTHPOLE 0
#define GREENWICH 1
#define EASTPOLE 2
#define IDL 3
#define WESTPOLE 4
#define SOUTHPOLE 5
double octa_wrap_verts[8][3] = {
    /* NORTHPOLE */ {0,0,1},
    /* GREENWICH */ {0,-1,0},
    /* EASTPOLE */  {1,0,0},
    /* IDL */       {0,1,0},
    /* WESTPOLE */  {-1,0,0},
    /* SOUTHPOLE */ {0,0,-1},
};
// Adjacent tris must have opposite orientations (so icosa doesn't work)
// fourth coord is the orientation
static int octa_wrap_faces[8][4] = {
    {GREENWICH, NORTHPOLE, WESTPOLE, 1},
    {GREENWICH, NORTHPOLE, EASTPOLE, -1},
    {GREENWICH, SOUTHPOLE, WESTPOLE, -1},
    {GREENWICH, SOUTHPOLE, EASTPOLE, 1},
    {IDL, NORTHPOLE, WESTPOLE, -1},
    {IDL, NORTHPOLE, EASTPOLE, 1},
    {IDL, SOUTHPOLE, WESTPOLE, 1},
    {IDL, SOUTHPOLE, EASTPOLE, -1},
};

static void
meshOcta(struct Mesh *mesh)
{
    int nverts = mesh->nverts = 6;
    int ntris = mesh->ntris = 8;
    int nedges = mesh->nedges = 12;
    double (*vc)[3] = mesh->vc;
    double (*tc)[2] = mesh->tc;
    struct Edge *edges = mesh->edges;
    struct Tri *tris = mesh->tris;
    int verti, trii;

    expandTris(mesh, 8, &tris);
    expandEdges(mesh, 12, &edges);
    expandVerts(mesh, 6, &vc, &tc);

    FOR (verti, nverts)
    {
	PFCOPY_VEC3(vc[verti], octa_wrap_verts[verti]);
	xyz_to_st(vc[verti][0], vc[verti][1], vc[verti][2],
		  &tc[verti][0], &tc[verti][1]);
    }
    nedges = 0;
    FOR (trii, ntris)
    {
	tris[trii].level = 0;
	tris[trii].orientation = octa_wrap_faces[trii][3];
	PFSET_VEC4(tris[trii].children, -1,-1,-1,-1);

	int sidei;
	FOR (sidei, 3)
	{
	    //
	    // Attempt to find the side in the list.
	    //
	    int edgei;
	    FOR (edgei, nedges)
	    {
		if (edges[edgei].from == octa_wrap_faces[trii][sidei]
		 && edges[edgei].to == octa_wrap_faces[trii][(sidei+1)%3])
		    break;
	    }
	    if (edgei == nedges) // didn't find it
	    {
		INIT_EDGE(edges, nedges, 0, ELIGIBLE_TO_BE_SPLIT,
			  octa_wrap_faces[trii][sidei],
			  octa_wrap_faces[trii][(sidei+1)%3]);
		nedges++;
	    }
	    tris[trii].sides[sidei] = edgei;
	    tris[trii].sidedirection[sidei] = 1;
	}
    }
    assert(nedges == 12);
}

static inline double lerp(double a, double b, double t)
{
    return a + t*(b-a);
}
/*
 * Linear interpolation modulo 1; always take the closest answer
 */
static inline double lerpwrap(double a, double b, double t)
{
    if (PF_ABS(a-b) > .5)
    {
	if (a > b)
	    if (1-a < b-0)
		a -= 1;
	    else
		b += 1;
	else /* b > a */
	    if (1-b < a-0)
		b -= 1;
	    else
		a += 1;
    }
    return lerp(a,b,t);
}
static inline void lerp_st(double result[2], const double from[2],
					        const double to[2], double t)
{
    result[PF_S] = lerpwrap(from[PF_T]==0||from[PF_T]==1 ? to[PF_S]
							 : from[PF_S],
			    to[PF_T]==0||to[PF_T]==1 ? from[PF_S]
						     : to[PF_S], t);
    result[PF_T] = lerp(from[PF_T], to[PF_T], t);
}

//
// Note, I put some thought into which
// should be floats and which doubles...
// don't go changing them indiscriminately
//
static void
meshSubdivide(struct Mesh *mesh,
	      double viewDir[3],
	      double viewRight[3],
	      double viewUp[3],
	      double viewOffset,
	      float lowtol, float hightol)
{
    int nverts = mesh->nverts;
    int ntris = mesh->ntris;
    int nedges = mesh->nedges;
    double (*vc)[3] = mesh->vc;
    double (*tc)[2] = mesh->tc;
    struct Edge *edges = mesh->edges;
    struct Tri *tris = mesh->tris;

    int edgei = 0;
    int trii = 0;
    int leveli;
    for (leveli = 0; edgei < nedges; leveli++) /* sic */
    {
	//
	// For each edge at this level,
	// if it's eligible for splitting
	// and the error at the edge center is > lowtol,
	// split the edge into two pieces at leveli+1.
	//
	for (;edgei < nedges && edges[edgei].level == leveli; edgei++)
	{
	    struct Edge *edge = &edges[edgei];
	    if (edge->status == ELIGIBLE_TO_BE_SPLIT)
	    {
		// XXX if we ever reuse the information,
		// XXX should store this info so it doesn't need to
		// XXX be recalculated
		// XXX also, can interpolate in latlon space
		// XXX and not waste the conversion time between
		// XXX lon,lat and s,t
		double initial[3], final[3]; // morph points
		double finaltc[2];
		PFCOMBINE_VEC3(initial, .5, vc[edge->from], .5, vc[edge->to]);
		lerp_st(finaltc, tc[edge->from], tc[edge->to], .5);

		st_to_xyz(finaltc[0],finaltc[1], &final[0],&final[1],&final[2]);

		float initialdivide = PFDOT_VEC3(initial, viewDir)-viewOffset;
		float finaldivide = PFDOT_VEC3(final, viewDir)-viewOffset;

		double projecteddelta[2];
		projecteddelta[0] = PFDOT_VEC3(final, viewRight)/finaldivide
				  - PFDOT_VEC3(initial, viewRight)/initialdivide;
		projecteddelta[1] = PFDOT_VEC3(final, viewUp)/finaldivide
				  - PFDOT_VEC3(initial, viewUp)/initialdivide;

		float errorsqrd = PFDOT_VEC2(projecteddelta, projecteddelta);

		//
		// Based on the error,
		// decide whether to split and,
		// if so, how much to morph.
		//
		if (errorsqrd < lowtol*lowtol)
		{
		    /* fprintf(stderr, "Edge %d (level %d) doesn't need splitting: error = %g (%g %g)\n", edgei, leveli, sqrt(errorsqrd), projecteddelta[0], projecteddelta[1]); */
		    edge->status = INELIGIBLE_TO_BE_SPLIT; // necessary
		    continue;	// edge doesn't need splitting
		}
		/* fprintf(stderr, "Edge %d (level %d) need splitting: error = %g (%g %g)\n", edgei, leveli, sqrt(errorsqrd), projecteddelta[0], projecteddelta[1]); */

		expandVerts(mesh, nverts+1, &vc, &tc);
		assert(nverts+1 <= mesh->maxnverts);
		double t;
		if (errorsqrd > hightol*hightol)
		{
		    t = 1.;
		    PFCOPY_VEC3(vc[nverts], final);
		    PFCOPY_VEC2(tc[nverts], finaltc);
		}
		else
		{
		    float error = pfSqrt(errorsqrd);
		    t = (error-lowtol)/(hightol-lowtol);
		    PFCOMBINE_VEC3(vc[nverts], (1-t), initial, t, final);
		    lerp_st(tc[nverts], initial, final, t);
		}
		int newverti = nverts++;

		expandEdges(mesh, nedges+2, &edges);
		assert(nedges+2 <= mesh->maxnedges);
		edge = &edges[edgei];

		edge->status = HAS_BEEN_SPLIT;
		edge->children[0] = nedges+0;
		edge->children[1] = nedges+1;
			  
		INIT_EDGE(edges, nedges+0, leveli+1,
			  t==1. ? ELIGIBLE_TO_BE_SPLIT : INELIGIBLE_TO_BE_SPLIT,
			  edge->from, newverti);
		INIT_EDGE(edges, nedges+1, leveli+1,
			  t==1. ? ELIGIBLE_TO_BE_SPLIT : INELIGIBLE_TO_BE_SPLIT,
			  newverti, edge->to);

		nedges += 2;
	    }
	}

	//
	// For each triangle at this level,
	// if any of its edges got split,
	// split the triangle into the appropriate number of sub-triangles.
	//
	for (; trii < ntris && tris[trii].level == leveli; trii++)
	{
	    struct Tri *tri = &tris[trii];

#if 0 // XXX get rid of this when it works
	    {
		int nsplit = (edges[tri->sides[0]].status == HAS_BEEN_SPLIT)
			   + (edges[tri->sides[1]].status == HAS_BEEN_SPLIT)
			   + (edges[tri->sides[2]].status == HAS_BEEN_SPLIT);
		if (nsplit > 0)
		    fprintf(stderr, "Splitting tri %d into %d parts\n", trii, nsplit+1);
	    }
#endif

	    int something_split = 0;
	    int sidei;
	    FOR (sidei, 3)
	    {
		if (edges[tri->sides[sidei]].status == HAS_BEEN_SPLIT)
		{
		    expandEdges(mesh, nedges+1, &edges);
		    expandTris(mesh, ntris+1, &tris);
		    assert(nedges+1 <= mesh->maxnedges);
		    assert(ntris+1 <= mesh->maxntris);
		    tri = &tris[trii];
		    int childside0 = edges[tri->sides[sidei]].children[0];
		    int childside1 = nedges;
		    int childside2 = edges[tri->sides[(sidei+2)%3]].children[1];

		    tris[ntris].level = leveli+1;
		    PFSET_VEC3(tris[ntris].sides,
			       childside0, childside1, childside2);
		    PFSET_VEC3(tris[ntris].sidedirection, 1,1,1);
		    PFSET_VEC4(tris[ntris].children, -1,-1,-1,-1);
		    tris[ntris].orientation = tri->orientation;
		    INIT_EDGE(edges, childside1, leveli+1,
			  edges[childside0].status == ELIGIBLE_TO_BE_SPLIT
		       && edges[childside2].status == ELIGIBLE_TO_BE_SPLIT ?
			      ELIGIBLE_TO_BE_SPLIT : INELIGIBLE_TO_BE_SPLIT,
			      edges[childside0].to, edges[childside2].from);

		    tri->children[sidei] = ntris;

		    nedges++;
		    ntris++;
		    something_split = 1;
		}
	    }
	    if (something_split)
	    {
		//
		// Add a reversed triangle in the middle...
		//
		expandTris(mesh, ntris+1, &tris);
		assert(ntris+1 <= mesh->maxntris);
		tri = &tris[trii];
		tris[ntris].level = leveli+1;
		tris[ntris].orientation = -tri->orientation;
		FOR (sidei, 3)
		    if (tri->children[sidei] != -1)
		    {
			tris[ntris].sides[2-sidei] =
				tris[tri->children[sidei]].sides[1];
			tris[ntris].sidedirection[2-sidei] = 1;
		    }
		    else
		    {
			tris[ntris].sides[2-sidei] =
			    edges[tri->sides[(sidei+2)%3]].children[1];
			    // if not split, child 1 is itself
			tris[ntris].sidedirection[2-sidei] = -1;
		    }
		PFSET_VEC4(tris[ntris].children, -1,-1,-1,-1);
		tri->children[3] = ntris;
if (trii == 15)
{
printf("THERE at tri=15\n");
sginap(1);
}
		ntris++;
	    }
	}
    }
    mesh->nverts = nverts;
    mesh->ntris = ntris;
    mesh->nedges = nedges;
}


static void
meshPrint(struct Mesh *mesh)
{
    int i;
    printf("%d verts:\n", mesh->nverts);
    FOR (i, mesh->nverts)
    {
	printf("\t%3i: (%.9g %.9g %.9g)  (%.9g %.9g)\n",
		i,
		mesh->vc[i][0],
		mesh->vc[i][1],
		mesh->vc[i][2],
		mesh->tc[i][0],
		mesh->tc[i][1]);

    }
    printf("%d edges\n", mesh->nedges);
    FOR (i, mesh->nedges)
    {
	printf("\t%3i: from=%d to=%d level=%d status=%d children=%d,%d\n",
		i,
		mesh->edges[i].from,
		mesh->edges[i].to,
		mesh->edges[i].level,
		mesh->edges[i].status,
		mesh->edges[i].children[0],
		mesh->edges[i].children[1]);
    }
    printf("%d tris\n", mesh->ntris);
    FOR (i, mesh->ntris)
    {
	printf("\t%3i: level=%d orient=%d sides=%d,%d,%d sidedirs=%d,%d,%d children=%d,%d,%d,%d\n",
		i,
		mesh->tris[i].level,
		mesh->tris[i].orientation,
		mesh->tris[i].sides[0],
		mesh->tris[i].sides[1],
		mesh->tris[i].sides[2],
		mesh->tris[i].sidedirection[0],
		mesh->tris[i].sidedirection[1],
		mesh->tris[i].sidedirection[2],
		mesh->tris[i].children[0],
		mesh->tris[i].children[1],
		mesh->tris[i].children[2],
		mesh->tris[i].children[3]);
    }
}

#define FROMV(edge,dir) ((dir) == -1 ? (edge).from : (edge).to)
#define TOV(edge,dir) ((dir) == -1 ? (edge).to : (edge).from)

#ifdef MAIN // little test program
//
// Print in .im format
//
static void
meshPrint_im(struct Mesh *mesh)
{
    int ntris = mesh->ntris;
    double (*vc)[3] = mesh->vc;
    double (*tc)[2] = mesh->tc;
    struct Edge *edges = mesh->edges;
    struct Tri *tris = mesh->tris;

    printf("%s",
	   "new root Root\n"
	   "end_root\n"
	   "new geode Geode\n"
	   "end\n"
	   "attach Root Geode\n"
	   "load_geode Geode\n"
	  );

    int trii;
    FOR (trii, ntris)
    {
	struct Tri *tri = &tris[trii];
	if (tri->children[3] != -1)
	    continue; // if there are any children, [3] will be one
	printf("  poly 3\n");
	if (tri->orientation == 1)
	{
	    printf("    vc %.9g %.9g %.9g  %.9g %.9g %.9g  %.9g %.9g %.9g\n",
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][2],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][2],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][2]);
	    printf("    nc %.9g %.9g %.9g  %.9g %.9g %.9g  %.9g %.9g %.9g\n",
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][2],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][2],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][2]);
	    printf("    tc %.9g %.9g  %.9g %.9g  %.9g %.9g\n",
		tc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		tc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1],
		tc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		tc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		tc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		tc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1]);
	}
	else
	{
	    printf("    vc %.9g %.9g %.9g  %.9g %.9g %.9g  %.9g %.9g %.9g\n",
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][2],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][2],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][2]);
	    printf("    nc %.9g %.9g %.9g  %.9g %.9g %.9g  %.9g %.9g %.9g\n",
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1],
		vc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][2],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		vc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][2],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1],
		vc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][2]);
	    printf("    tc %.9g %.9g  %.9g %.9g  %.9g %.9g\n",
		tc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][0],
		tc[FROMV(edges[tri->sides[2]], tri->sidedirection[2])][1],
		tc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][0],
		tc[FROMV(edges[tri->sides[1]], tri->sidedirection[1])][1],
		tc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][0],
		tc[FROMV(edges[tri->sides[0]], tri->sidedirection[0])][1]);
	}
	printf("  end\n");
    }
}


main(int argc, char **argv)
{
    double viewDir[3] = {0,1,0};
    double viewRight[3] = {1,0,0};
    double viewUp[3] = {0,0,1};
    double viewOffset = -2;	/* dot prod of viewDir with eye point */
    float tol = 1;

    if (argc > 1)
	tol = atof(argv[1]);

    float hightol = tol;
    float lowtol = tol*.5;

    if (argc > 2)
	lowtol = atof(argv[1]);

    struct Mesh *mesh = allocMesh(1,1,1, NULL);
    meshOcta(mesh);
    meshSubdivide(mesh, viewDir, viewOffset, lowtol, hightol);
    fprintf(stderr, "%d tris, %d edges, %d vertices\n",
		mesh->ntris, mesh->nedges, mesh->nverts);
    if (getenv("_PFSPHEREFRONT_DEBUG"))
	meshPrint(mesh);
    else
	meshPrint_im(mesh);
    freeMesh(mesh);
    return 0;
}
#endif


struct sphereData
{
    int frequency; /* update every this many frames */

    float hightol, lowtol;
    pfVec2 lonlat[2], st[2]; // tie points for texture mapping

    struct Mesh *mesh;
			
    int maxntris;
    pfVec3 (*vc)[3];
    pfVec2 (*tc)[3];
    pfGeoSet *geoset;
};


/*
 * post-app callback to adjust the sphere for the current position
 */
static int
updateSphereFunc(pfTraverser *trav, void *_data)
{
    struct sphereData *data = (struct sphereData *)_data;

    int framecount = pfGetFrameCount();
    if (framecount % data->frequency != 0
     && framecount != 1) /* wacky on first frame */
	return PFTRAV_CONT;

    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);
    eye.xformPt(eye, eyespace_to_objspace_mat);

    pfVec3 viewDir(0,1,0);
    pfVec3 viewRight(1,0,0);
    pfVec3 viewUp(0,0,1);
    viewDir.xformVec(viewDir, eyespace_to_objspace_mat);
    viewUp.xformVec(viewUp, eyespace_to_objspace_mat);
    viewRight.xformVec(viewRight, eyespace_to_objspace_mat);
    viewDir.normalize();
    viewRight.normalize();
    viewUp.normalize();

    double doubleViewDir[3], doubleViewRight[3], doubleViewUp[3];;
    PFCOPY_VEC3(doubleViewDir, viewDir);
    PFCOPY_VEC3(doubleViewUp, viewUp);
    PFCOPY_VEC3(doubleViewRight, viewRight);

    struct Mesh *mesh = data->mesh;
    meshOcta(mesh);
    meshSubdivide(mesh,
		  doubleViewDir,
		  doubleViewRight,
		  doubleViewUp,
		  PFDOT_VEC3(doubleViewDir, eye),
		  data->lowtol, data->hightol);

    static int debug = -1;
    if (debug == -1)
    {
	char *e = getenv("_PFSPHEREFRONT_DEBUG");
	debug = (e ? *e  ? atoi(e) : 1 : 0);
    }
    if (debug)
	meshPrint(mesh);

    //
    // Expand attribute arrays if necessary...
    //
    if (mesh->ntris > data->maxntris)
    {
	// By braille...
	pfRef(data->vc);
	pfRef(data->tc);
	/*
	assert(pfGetRef(data->vc) == 1);
	assert(pfGetRef(data->tc) == 1);
	*/
	data->geoset->setAttr(PFGS_COORD3, PFGS_PER_VERTEX, NULL, NULL);
	data->geoset->setAttr(PFGS_NORMAL3, PFGS_PER_VERTEX, NULL, NULL);
	data->geoset->setAttr(PFGS_TEXCOORD2, PFGS_PER_VERTEX, NULL, NULL);
	assert(pfGetRef(data->vc) == 1);
	assert(pfGetRef(data->tc) == 1);
	pfUnrefDelete(data->vc);
	pfUnrefDelete(data->tc);

	data->vc = (pfVec3 (*)[3])pfMalloc(mesh->ntris * sizeof(*data->vc),
					   mesh->arena);
	data->tc = (pfVec2 (*)[3])pfMalloc(mesh->ntris * sizeof(*data->tc),
					   mesh->arena);
	data->maxntris = mesh->ntris;
    }

    //
    // Copy stuff from the mesh structure into the attribute arrays...
    //
    int meshntris = mesh->ntris; // can have holes;
    int datantris = 0;

    struct Tri *meshtris = mesh->tris;
    struct Edge *meshedges = mesh->edges;
    double (*meshvc)[3] = mesh->vc;
    double (*meshtc)[2] = mesh->tc;
    pfVec3 (*datavc)[3] = data->vc;
    pfVec2 (*datatc)[3] = data->tc;

    datantris = 0;
    int meshtrii;
    FOR (meshtrii, meshntris)
    {
	struct Tri *tri = &meshtris[meshtrii];
	if (tri->children[3] != -1)
	    continue; // if there are any children, [3] will be one
	int sidei;
	FOR (sidei, 3)
	{
	    int verti =
		FROMV(meshedges[tri->sides[tri->orientation == 1 ? sidei : 2-sidei]], tri->sidedirection[tri->orientation == 1 ? sidei : 2-sidei]);
	    PFCOPY_VEC3(datavc[datantris][sidei], meshvc[verti]);
	    PFCOPY_VEC2(datatc[datantris][sidei], meshtc[verti]);
	}
	/*
	{
	printf("tri %d(%d) = %g %g %g  %g %g %g  %g %g %g\n",
		datantris, meshtrii,
		datavc[datantris][0][0],
		datavc[datantris][0][1],
		datavc[datantris][0][2],
		datavc[datantris][1][0],
		datavc[datantris][1][1],
		datavc[datantris][1][2],
		datavc[datantris][2][0],
		datavc[datantris][2][1],
		datavc[datantris][2][2]);
	}
	*/
	datantris++;
    }
    assert(datantris <= data->maxntris);

    /*
     * XXX is the following frame-accurate? I think not.
     */

    data->geoset->setAttr(PFGS_COORD3, PFGS_PER_VERTEX, datavc, NULL);
    data->geoset->setAttr(PFGS_NORMAL3, PFGS_PER_VERTEX, datavc, NULL); /*sic*/
    data->geoset->setAttr(PFGS_TEXCOORD2, PFGS_PER_VERTEX, datatc, NULL);
    data->geoset->setPrimType(PFGS_TRIS);
    data->geoset->setNumPrims(datantris);

    return PFTRAV_CONT;
}

/*
 * Every frame, an octahedron is recursively subdivided from scratch.
 * For each edge, the error (in pixels) is calculated at its center.
 *	If error <= lowtol, the edge is not split.
 *	If lowtol < error < hightol, the edge is split by a new vertex
 *		placed somewhere between the edge center and the desired
 *		position, proportional to where error is along
 *		the range from lowtol to hightol.  The new edges
 *		are *not* eligible for further subdivision.
 *	If error >= hightol, the edge is split by a new vertex
 *		placed at the desired position.  The two new 
 *		edges are eligible for further subdivision.
 *
 * Update geometry every "update_frequency" frames
 * (so that the scene may be rotated and examined for a period of time
 * before it is updated again).
 *
 * The texture is assumed to be such that
 * lon/lat is a straight scale&bias function of s,t;
 * the inputs lonlat and st specify the
 * lower left and upper right of some rectangle,
 * to be used as tie points.  lonlat is in degrees.
 *
 * If either or both of these are not specified (i.e. NULL),
 * the following defaults will be used:
 *	lonlat[0] = -180,-90	st[0] = 0,0
 *	lonlat[1] = 180,90	st[1] = 1,1
 */

extern /*"C"*/ pfNode *
pfuNewSphereFront(float hightol,
		  float lowtol,
		  pfTexture *texture,
		  pfVec2 lonlat[2], pfVec2 st[2],
		  int update_frequency)
{
    struct sphereData *data = (struct sphereData*)pfMalloc(
			       sizeof(struct sphereData),
			       pfGetSharedArena());
    assert(data != NULL);
    data->frequency = update_frequency;
    data->lonlat[0] = (lonlat ? lonlat[0]*(M_PI/180) : pfVec2(-M_PI,-M_PI/2));
    data->lonlat[1] = (lonlat ? lonlat[1]*(M_PI/180) : pfVec2(M_PI,M_PI/2));
    data->st[0] = (st ? st[0] : pfVec2(0,0));
    data->st[1] = (st ? st[1] : pfVec2(1,1));

    data->mesh = allocMesh(1,1,1,pfGetSharedArena());
    data->hightol = hightol;
    data->lowtol = lowtol;
    data->maxntris = 1;
    data->vc = (pfVec3 (*)[3])pfMalloc(data->maxntris * sizeof(*data->vc));
    data->tc = (pfVec2 (*)[3])pfMalloc(data->maxntris * sizeof(*data->tc));

    pfGeoSet *geoset = new pfGeoSet();
    assert(geoset != NULL);
    geoset->setAttr(PFGS_COORD3, PFGS_PER_VERTEX, data->vc, NULL);
    geoset->setAttr(PFGS_NORMAL3, PFGS_PER_VERTEX, data->vc, NULL);
    geoset->setAttr(PFGS_TEXCOORD2, PFGS_PER_VERTEX, data->tc, NULL);
    geoset->setPrimType(PFGS_TRIS);
    geoset->setNumPrims(0);


    pfGeoState *geostate = new pfGeoState();
    assert(geostate != NULL);
    geostate->setMode(PFSTATE_ENLIGHTING, PF_ON);
    if (texture != NULL)
    {
	geostate->setAttr(PFSTATE_TEXTURE, texture);
	geostate->setMode(PFSTATE_ENTEXTURE, PF_ON);
    }
    geoset->setGState(geostate);
    data->geoset = geoset;

    pfGeode *geode = new pfGeode();
    assert(geode != NULL);

    pfSphere bsph;
    bsph.center.set(0,0,0);
    bsph.radius = 1;
    geode->setBound(&bsph, PFBOUND_STATIC);
    geode->setTravFuncs(PFTRAV_APP, NULL, updateSphereFunc);
    geode->setTravData(PFTRAV_APP, data);
    geode->addGSet(geoset);

    /*
     * setBound doesn't take when it's a geode, so...
     */
    pfGroup *protector = new pfGroup();
    protector->addChild(geode);
    protector->setBound(&bsph, PFBOUND_STATIC);

    return protector;
}