Module: rtrackv.cpp

class RTrackVRML
.h

constructorRTrackVRML()

: RTrack(), nodes(0), flags(0)

destructor~RTrackVRML()

Flat functions
 

OptimizeModelstatic void OptimizeModel(DGeode *geode)

Make sure the geode paints fast for VRML tracks


class RTrackVRML
.h

AddNodebool AddNode(const RTV_Node *newNode)
BuildCullervoid BuildCuller()

Create culling structure

DestroyCullervoid DestroyCuller()
PaintNodevoid PaintNode(int n)

Paint sector #n

Paintvoid Paint()

Given the current graphics matrix, paint the track


Flat functions
 

SetGLColorstatic void SetGLColor(QColor *color)

Local function to convert rgba to float

PaintRopestatic void PaintRope(DVector3 *v1,DVector3 *v2,QColor *col)

Paint rope with poles


class RTrackVRML
.h

PaintHiddenvoid PaintHidden()

Paint hidden stuff

Loadbool Load()

Loads a track in the TrackFlat format (very simple)
Calls the application back if a function is supplied
using SetLoadCallback() (this function may take some time).

Savebool Save()

Flat functions
 

Unprojectstatic void Unproject(DVector3 *vWin,DVector3 *vObj)

Unprojects a window coordinate (vWin) into a 3D coordinate (vObj)
vWin->z defines the depth into the display
Is not that fast, since it retrieves matrix state information,
and it inverts the model*proj matrix.
For some easy picking, it will do though.

Projectstatic void Project(DVector3 *vObj,DVector3 *vWin)

!! Text must be updated
Projects a window coordinate (vWin) into a 3D coordinate (vObj)
vWin->z defines the depth into the display
Is not that fast, since it retrieves matrix state information,
and it inverts the model*proj matrix.
For some easy picking, it will do though.

RaySphereIntersectstatic bool RaySphereIntersect(const DVector3 *origin,const DVector3 *dir, const DVector3 *center,dfloat radius,DVector3 *intersect)

Determines whether a ray intersects the sphere
From 'Real-Time Rendering', page 299.
Calculates intersection point in 'intersect', if wished. Otherwise,
pass 0 for 'intersect' (is quite a bit faster).

RayTriangleIntersectstatic int RayTriangleIntersect(const dfloat orig[3],const dfloat dir[3], const dfloat vert0[3],const dfloat vert1[3],const dfloat vert2[3],

dfloat *t,dfloat *u,dfloat *v)
From Real-Time Rendering, page 305
Returns 0 if not hit is found
If a hit is found, t contains the distance along the ray (dir)
and u/v contain u/v coordinates into the triangle (like texture
coordinates).

FindGeodeTrianglestatic bool FindGeodeTriangle(DVector3 *origin,DVector3 *dir,DGeode *geode, RSurfaceInfo *si,DVector3 *inter)

The ray is traced and tested if it hits any triangle in 'geode'.
Returns index of triangle.
If a triangle is hit, the info is saved as cache info in 'si'.

CacheCheckTrianglestatic bool CacheCheckTriangle(const DVector3 *origin,const DVector3 *dir, const DGeode *geode,const RSurfaceInfo *si,DVector3 *inter)

Check if the cached triangle still hits
If so, it returs TRUE and the intersection in 'inter'


class RTrackVRML
.h

GetSurfaceInfovoid GetSurfaceInfo(DVector3 *pos,DVector3 *dir, RSurfaceInfo *si)

Determines what the surface looks like under 'pos'
'dir' is the direction of the ray to trace (often like (0,-1,0))
Currently returns valid (si->): x,y,z


Flat functions
 

vOrigin.DbgPrintvOrigin.DbgPrint("origin");

vDir.DbgPrint("dir");
#endif
// Store cache info that wasn't already stored
// by FindGeodeTriangle()
si->lastNode=i;

#ifdef OBS
// Don't seek further
break;
#endif
}
}
}
dbg("--\n");
}



/*
 * RTrackVRML - VRML/SCGT-type tracks
 * 12-11-00: Created!
 * 10-03-01: Optimized a bit in the ray intersection parts.
 * NOTES:
 * - Currently uses .wrl files, although this will probably change
 * into .dof files for speed later on.
 * (c) Dolphinity/Ruud van Gaal
 */

#include <racer/racer.h>
#include <qlib/debug.h>
#pragma hdrstop
#include <racer/trackv.h>
#include <d3/culler.h>
#include <qlib/app.h>
#include <math.h>
DEBUG_ENABLE

// File to contain geode names
#define FILE_GEOMETRY  "geometry.ini"

// Macro to avoid calling BuildCuller() at every track trace
#define QUICK_BUILD_CULLER   if(!(flags&CULLER_READY))BuildCuller()

#undef  DBG_CLASS
#define DBG_CLASS "RTrackVRML"

RTrackVRML::RTrackVRML()
  : RTrack(), nodes(0), flags(0)
{
  DBG_C("ctor")

  int i;
  for(i=0;i<MAX_NODE;i++)
  { node[i]=0;
  }
  culler=new DCullerSphereList();
}

RTrackVRML::~RTrackVRML()
{
  int i;
  for(i=0;i<MAX_NODE;i++)
  { if(node[i])
    { if(node[i]->model)delete node[i]->model;
      delete node[i];
    }
  }
  delete culler;
}

/*****************
* BASIC BUILDING *
*****************/
static void OptimizeModel(DGeode *geode)
// Make sure the geode paints fast for VRML tracks
{
  int i;
  
  // Make sure geode has no normals (no lighting used)
  geode->DestroyNormals();
  // All materials are largely equal, just a decal-ed texture.
  // Avoid state changes in the geode's materials
  for(i=0;i<geode->materials;i++)
  {
    geode->material[i]->Enable(
      /*DMaterial::NO_TEXTURE_ENABLING|*/
      DMaterial::NO_MATERIAL_PROPERTIES|
      DMaterial::NO_BLEND_PROPERTIES);
  }
  // SCGT VRML nodes are never drawn at a different position
  // This may change in the future, since I'd really like
  // reusing of grandstands, little houses etc.
  // So this optimization may disappear in the future (or be
  // done only if the position is (0,0,0))
  geode->Set2D();
  // All geodes are part of one big scene
  geode->EnableCSG();
}
bool RTrackVRML::AddNode(const RTV_Node *newNode)
{
  int i;
  RTV_Node *nn;
  
  // Out of space?
  if(nodes==MAX_NODE)
  {
    qwarn("RTrackVRML:AddNodes(); out of nodes");
    return FALSE;
  }
  //node[nodes]=(RTV_Node*)qcalloc(sizeof(RTV_Node));
  node[nodes]=new RTV_Node();
  
  // Copy parameters
  nn=node[nodes];
  nn->type=newNode->type;
  nn->fileName=newNode->fileName;
  nn->model=newNode->model;

  // Prepare model for fast batch drawing
  OptimizeModel(newNode->model);
  
  // Node added
  nodes++;
  
  // Track was modified
  DestroyCuller();
  return TRUE;
}

/**********
* Culling *
**********/
void RTrackVRML::BuildCuller()
// Create culling structure
{
  int i;
  
  if(flags&CULLER_READY)return;
qdbg("RTrackVRML:Paint(); create culler\n");
  
  for(i=0;i<nodes;i++)
  {
    culler->AddGeode(node[i]->model);
//qdbg("add geode %p\n",node[i]->model);
  }
  flags|=CULLER_READY;
}
void RTrackVRML::DestroyCuller()
{
  culler->Destroy();
  flags&=~CULLER_READY;
}

/***********
* PAINTING *
***********/
void RTrackVRML::PaintNode(int n)
// Paint sector #n
{
  DBG_C("PaintNode")
  DBG_ARG_I(n)

  RTV_Node *pNode;
  
  pNode=node[n];
//qdbg("RTrackVRML:PaintNode(%d)=%p\n",n,pNode);
  pNode->model->EnableCSG();
  pNode->model->Paint();
}

void RTrackVRML::Paint()
// Given the current graphics matrix, paint the track
{
  DBG_C("Paint")

  int i;
  QUICK_BUILD_CULLER;
  
  // Update culler (if viewpoint has changed)
  culler->CalcFrustumEquations();
  
  // Set OpenGL state
  glEnable(GL_CULL_FACE);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glShadeModel(GL_FLAT);
//glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  // Leave painting to culler
  culler->Paint();
  
#ifdef OBS_DONE_BY_CULLER
  for(i=0;i<nodes;i++)
  {
    PaintNode(i);
  }
#endif
}
static void SetGLColor(QColor *color)
// Local function to convert rgba to float
{
  GLfloat cr,cg,cb,ca;
  cr=(GLfloat)color->GetR()/255;
  cg=(GLfloat)color->GetG()/255;
  cb=(GLfloat)color->GetB()/255;
  ca=(GLfloat)color->GetA()/255;
  glColor4f(cr,cg,cb,ca);
}
static void PaintRope(DVector3 *v1,DVector3 *v2,QColor *col)
// Paint rope with poles
{
  SetGLColor(col);
  glBegin(GL_LINES);
    // Both 
    glVertex3f(v1->x,v1->y,v1->z);
    glVertex3f(v1->x,v1->y+1,v1->z);
    glVertex3f(v2->x,v2->y,v2->z);
    glVertex3f(v2->x,v2->y+1,v2->z);
    // Connection
    glVertex3f(v1->x,v1->y+1,v1->z);
    glVertex3f(v2->x,v2->y+1,v2->z);
  glEnd();
}
void RTrackVRML::PaintHidden()
// Paint hidden stuff
{
  DBG_C("PaintHidden")

  int i;
  
  glDisable(GL_LIGHTING);
  glDisable(GL_CULL_FACE);
  glDisable(GL_TEXTURE_2D);

  // Paint timelines
  for(i=0;i<GetTimeLines();i++)
  {
    RTimeLine *t=GetTimeLine(i);
    QColor col(255,255,0);
    PaintRope(&t->from,&t->to,&col);
  }
  for(i=0;i<GetGridPosCount();i++)
  {
    RCarPos *p=GetGridPos(i);
    QColor col(0,255,0);
    PaintRope(&p->from,&p->to,&col);
  }
  for(i=0;i<GetPitPosCount();i++)
  {
    RCarPos *p=GetPitPos(i);
    QColor col(255,0,255);
    PaintRope(&p->from,&p->to,&col);
  }
}

/**********
* LOADING *
**********/
bool RTrackVRML::Load()
// Loads a track in the TrackFlat format (very simple)
// Calls the application back if a function is supplied
// using SetLoadCallback() (this function may take some time).
{
  QInfo *infoTrk;
  QInfoIterator *ii;
  qstring s,fileName;
  RTV_Node node;
  DGeode *model;
  char buf[256];
  int  current,total;
  
  // First load generic data
  if(!RTrack::Load())return FALSE;

  // Select OpenGL context to create textures in
  if(!QCV)
  { qerr("RTrackVRML: can't load track without a canvas");
    return FALSE;
  }
  
  sprintf(buf,"%s/%s",(cstring)trackDir,FILE_GEOMETRY);
  infoTrk=new QInfo(buf);
  
  // Count #objects to come (for the callback function)
  ii=new QInfoIterator(infoTrk,"objects");
  for(total=0;ii->GetNext(s);total++);
  delete ii;
  
  // Actually load the objects
  current=0;
  ii=new QInfoIterator(infoTrk,"objects");
  while(ii->GetNext(s))
  {
//qdbg("object '%s'\n",(cstring)s);
    node.type=0;
    
    sprintf(buf,"%s.file",(cstring)s);
    infoTrk->GetString(buf,fileName);
    // Use last part of the info path as the name
    node.fileName=QFileExtension(s)+1;
    // Create textures in QCV (!)
    QCV->Select();
    model=new DGeode(fileName);
    { 
      sprintf(buf,"%s/%s",(cstring)trackDir,(cstring)fileName);
      QFile f(RFindFile(fileName,trackDir));
//qdbg("  importing '%s'\n",RFindFile(fileName,trackDir));
      model->SetMapPath(RFindFile(".",trackDir));
//qdbg("  map path='%s'\n",RFindFile(".",trackDir));
      if(!model->ImportDOF(&f))
      {
        qwarn("Can't import DOF '%s'",(cstring)fileName);
        continue;
      }
    }
    // Transfer model to node (don't free geode)
    node.model=model;
    AddNode(&node);
    
    // Notify application of progress
    current++;
    if(cbLoad)
      if(!cbLoad(current,total,node.fileName))break;
  }
  delete ii;
  delete infoTrk;
  return TRUE;
}

/*********
* Saving *
*********/
bool RTrackVRML::Save()
{
  int i;
  QInfo *infoTrk;
  char buf[256],buf2[256];
  
  // First save generic data
  if(!RTrack::Save())return FALSE;
  
  sprintf(buf,"%s/%s",(cstring)trackDir,FILE_GEOMETRY);
  infoTrk=new QInfo(buf);
  
  // Write geode names
  for(i=0;i<nodes;i++)
  {
//qdbg("Save node '%s'\n",(cstring)node[i]->fileName);
    sprintf(buf,"objects.%s.file",(cstring)node[i]->fileName);
    sprintf(buf2,"%s.dof",(cstring)node[i]->fileName);
    infoTrk->SetString(buf,buf2);
  }
  delete infoTrk;
  return TRUE;
}

/********************
* Surface detection *
********************/

/********************
* Manual projection *
********************/
static void Unproject(DVector3 *vWin,DVector3 *vObj)
// Unprojects a window coordinate (vWin) into a 3D coordinate (vObj)
// vWin->z defines the depth into the display
// Is not that fast, since it retrieves matrix state information,
// and it inverts the model*proj matrix.
// For some easy picking, it will do though.
{
  double mModel[16],mPrj[16];
  int    viewport[4];
  GLdouble wx,wy,wz;
  GLdouble ox,oy,oz;
  int i;
  
//qdbg("Unprj: glCtx: %p\n",glXGetCurrentContext());
  // Retrieve matrices and viewport settings
  glGetDoublev(GL_MODELVIEW_MATRIX,mModel);
  glGetDoublev(GL_PROJECTION_MATRIX,mPrj);
  glGetIntegerv(GL_VIEWPORT,viewport);

#ifdef OBS
  for(i=0;i<16;i++)
  {
    qdbg("mModel[%d]=%g\n",i,mModel[i]);
  }  
  for(i=0;i<16;i++)
  {
    qdbg("mPrj[%d]=%g\n",i,mPrj[i]);
  }
#endif
  
  // Reverse projection
  wx=vWin->x;
  wy=vWin->y;
  wz=vWin->z;
  if(!gluUnProject(wx,wy,wz,mModel,mPrj,viewport,&ox,&oy,&oz))
  {
    qwarn("Unproject failed");
    vObj->SetToZero();
    return;
  }
  
  vObj->x=(dfloat)ox;
  vObj->y=(dfloat)oy;
  vObj->z=(dfloat)oz;
  
  if(!gluProject(ox,oy,oz,mModel,mPrj,viewport,&wx,&wy,&wz))
  {
    qwarn("Project failed");
  }
//qdbg("gluProject: %f,%f,%f\n",wx,wy,wz);
}
static void Project(DVector3 *vObj,DVector3 *vWin)
// !! Text must be updated
// Projects a window coordinate (vWin) into a 3D coordinate (vObj)
// vWin->z defines the depth into the display
// Is not that fast, since it retrieves matrix state information,
// and it inverts the model*proj matrix.
// For some easy picking, it will do though.
{
  double mModel[16],mPrj[16];
  int    viewport[4],i;
  GLdouble wx,wy,wz;
  GLdouble ox,oy,oz;
  
  // Retrieve matrices and viewport settings
  glGetDoublev(GL_MODELVIEW_MATRIX,mModel);
  glGetDoublev(GL_PROJECTION_MATRIX,mPrj);
  glGetIntegerv(GL_VIEWPORT,viewport);

#ifdef OBS
  for(i=0;i<16;i++)
  {
    qdbg("mModel[%d]=%g\n",i,mModel[i]);
  }  
  for(i=0;i<16;i++)
  {
    qdbg("mPrj[%d]=%g\n",i,mPrj[i]);
  }
#endif
  
  // Reverse projection
  ox=vObj->x;
  oy=vObj->y;
  oz=vObj->z;
  if(!gluProject(ox,oy,oz,mModel,mPrj,viewport,&wx,&wy,&wz))
  {
    qwarn("gluProject failed");
    vWin->SetToZero();
    return;
  }
  
  vWin->x=(dfloat)wx;
  vWin->y=(dfloat)wy;
  vWin->z=(dfloat)wz;
}

/****************************
* RAY - SPHERE Intersection *
****************************/
static bool RaySphereIntersect(const DVector3 *origin,const DVector3 *dir,
static bool RaySphereIntersect(const DVector3 *origin,const DVector3 *dir,  const DVector3 *center,dfloat radius,DVector3 *intersect)
// Determines whether a ray intersects the sphere
// From 'Real-Time Rendering', page 299.
// Calculates intersection point in 'intersect', if wished. Otherwise,
// pass 0 for 'intersect' (is quite a bit faster).
{
  DVector3 l;
  dfloat   d,lSquared,mSquared;
  dfloat   rSquared=radius*radius;
  
#ifdef OBS
qdbg("origin %f,%f,%f\n",origin->x,origin->y,origin->z);
qdbg("dir %f,%f,%f\n",dir->x,dir->y,dir->z);
#endif
  // Calculate line from origin to center
  l.x=center->x-origin->x;
  l.y=center->y-origin->y;
  l.z=center->z-origin->z;
//qdbg("line=%f,%f,%f\n",l.x,l.y,l.z);
  // Calculate length of projection of direction onto that line
  d=l.Dot(*dir);
//qdbg("  proj. d=%f\n",d);
  lSquared=l.DotSelf();
//qdbg("  l^2=%f, radius=%f (^2=%f)\n",lSquared,radius,rSquared);
  if(d<0&&lSquared>rSquared)
  {
    // No intersection
    return FALSE;
  }
  
  // Check for how far we are off the center
  mSquared=lSquared-d*d;
  if(mSquared>rSquared)return FALSE;
  
  // Calculate intersection point, if requested
  if(!intersect)return TRUE;
  
  dfloat q,t;
  q=sqrt(rSquared-mSquared);
  if(lSquared>rSquared)t=d-q;
  else                 t=d+q;
//qdbg("t=%f\n",t);
  // Intersection point is t*(*dir) away from the origin
  intersect->x=origin->x+t*dir->x;
  intersect->y=origin->y+t*dir->y;
  intersect->z=origin->z+t*dir->z;
  return TRUE;
}

/******************************
* RAY - TRIANGLE Intersection *
******************************/
#define CROSS(dest,v1,v2) \
          dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
          dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
          dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
#define SUB(dest,v1,v2)\
          dest[0]=v1[0]-v2[0]; \
          dest[1]=v1[1]-v2[1]; \
          dest[2]=v1[2]-v2[2]; 

// Cull backfacing polygons?
#define OPT_CULL_RAY_TRIANGLE

static int RayTriangleIntersect(const dfloat orig[3],const dfloat dir[3],
static int RayTriangleIntersect(const dfloat orig[3],const dfloat dir[3],  const dfloat vert0[3],const dfloat vert1[3],const dfloat vert2[3],
  dfloat *t,dfloat *u,dfloat *v)
// From Real-Time Rendering, page 305
// Returns 0 if not hit is found
// If a hit is found, t contains the distance along the ray (dir)
// and u/v contain u/v coordinates into the triangle (like texture
// coordinates).
{
  dfloat edge1[3], edge2[3], tvec[3], pvec[3], qvec[3];
  dfloat det,inv_det;

   /* find vectors for two edges sharing vert0 */
   SUB(edge1, vert1, vert0);
   SUB(edge2, vert2, vert0);

   /* begin calculating determinant - also used to calculate U parameter */
   CROSS(pvec, dir, edge2);

   /* if determinant is near zero, ray lies in plane of triangle */
   det = DOT(edge1, pvec);

#ifdef OPT_CULL_RAY_TRIANGLE
  // Culling section; triangle will be culled if not facing the right
  // way.
  if(det<D3_EPSILON)
    return 0;

   /* calculate distance from vert0 to ray origin */
   SUB(tvec, orig, vert0);

   /* calculate U parameter and test bounds */
   *u = DOT(tvec, pvec);
   if (*u < 0.0 || *u > det)
      return 0;

   /* prepare to test V parameter */
   CROSS(qvec, tvec, edge1);

    /* calculate V parameter and test bounds */
   *v = DOT(dir, qvec);
   if (*v < 0.0 || *u + *v > det)
      return 0;

   /* calculate t, scale parameters, ray intersects triangle */
   *t = DOT(edge2, qvec);
   inv_det = 1.0 / det;
   *t *= inv_det;
   *u *= inv_det;
   *v *= inv_det;
#else
  // The non-culling branch
  if(det>-D3_EPSILON&&det<D3_EPSILON)
    return 0;
  inv_det = 1.0 / det;

  /* calculate distance from vert0 to ray origin */
  SUB(tvec, orig, vert0);

  /* calculate U parameter and test bounds */
  *u = DOT(tvec, pvec) * inv_det;
  if (*u < 0.0 || *u > 1.0)
    return 0;

  /* prepare to test V parameter */
  CROSS(qvec, tvec, edge1);

  /* calculate V parameter and test bounds */
  *v = DOT(dir, qvec) * inv_det;
  if (*v < 0.0 || *u + *v > 1.0)
    return 0;

  /* calculate t, ray intersects triangle */
  *t = DOT(edge2, qvec) * inv_det;
#endif
  // We've got an intersection!
  return 1;
}

/********************************
* Finding a triangle in a geode *
********************************/
static bool FindGeodeTriangle(DVector3 *origin,DVector3 *dir,DGeode *geode,
static bool FindGeodeTriangle(DVector3 *origin,DVector3 *dir,DGeode *geode,  RSurfaceInfo *si,DVector3 *inter)
// The ray is traced and tested if it hits any triangle in 'geode'.
// Returns index of triangle.
// If a triangle is hit, the info is saved as cache info in 'si'.
{
  int     g,tri,n,nTris;
  dfloat *pVertex;
  dindex *pIndex;
  DGeob  *geob;
  dfloat  t,u,v;
  dfloat  minT;
  
  // Find the surface CLOSEST to the origin
  minT=9999.0f;
  for(g=0;g<geode->geobs;g++)
  {
    geob=geode->geob[g];
    pVertex=geob->vertex;
    if(!pVertex)continue;
    pIndex=geob->index;
    //for(tri=geob->indices/3;tri>0;tri--,pIndex+=3)
    nTris=geob->indices/3;
    for(tri=0;tri<nTris;tri++,pIndex+=3)
    {
      if(RayTriangleIntersect(&origin->x,&dir->x,
        &pVertex[(pIndex[0])*3],
        &pVertex[(pIndex[1])*3],
        &pVertex[(pIndex[2])*3],
        &t,&u,&v))
      {
//qdbg("Intersect TRI! t=%.2f, u=%.2f, v=%.2f\n",t,u,v);
        // Don't count this triangle if it's further away than
        // a previously found triangle, or the triangle is found
        // in the other direction (above the wheel for example; a bridge
        // possibly).
        if(t<0||t>minT)continue;
        
        // Calculate intersection point
        // 2 ways are possible:
        // - follow the ray: origin+t*direction
        // - interpolate vertices; u and v are barycentric coordinates
        //   which we can average and thus get the weighted average
        //   of the 3 vertices that make up the triangle
        inter->x=origin->x+t*dir->x;
        inter->y=origin->y+t*dir->y;
        inter->z=origin->z+t*dir->z;
        // Store cache information (for next time)
        si->lastTri=tri;
        si->lastGeob=g;
        // Caller must store si->lastNode (!) for cache info to be complete
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**********
* Caching *
**********/
static bool CacheCheckTriangle(const DVector3 *origin,const DVector3 *dir,
static bool CacheCheckTriangle(const DVector3 *origin,const DVector3 *dir,  const DGeode *geode,const RSurfaceInfo *si,DVector3 *inter)
// Check if the cached triangle still hits
// If so, it returs TRUE and the intersection in 'inter'
{
  int     g,tri,n;
  dfloat *pVertex;
  dindex *pIndex;
  DGeob  *geob;
  dfloat  t,u,v;
  
  // Cache info available?
  if(si->lastNode==-1)return FALSE;
  
  // Get back to the right triangle
  geob=geode->geob[si->lastGeob];
  pVertex=geob->vertex;
  if(!pVertex)return FALSE;
  pIndex=&geob->index[si->lastTri*3];
  if(RayTriangleIntersect(&origin->x,&dir->x,
    &pVertex[pIndex[0]*3],
    &pVertex[pIndex[1]*3],
    &pVertex[pIndex[2]*3],
    &t,&u,&v))
  {
//qdbg("Cached tri still hits\n");
    // Calculate intersection point
    inter->x=origin->x+t*dir->x;
    inter->y=origin->y+t*dir->y;
    inter->z=origin->z+t*dir->z;
    return TRUE;
  }
//qdbg("Cache miss!\n");
  return FALSE;
}

/************
* Pick test *
************/
void RTrackVRML::GetSurfaceInfo(DVector3 *pos,DVector3 *dir,
void RTrackVRML::GetSurfaceInfo(DVector3 *pos,DVector3 *dir,  RSurfaceInfo *si)
// Determines what the surface looks like under 'pos'
// 'dir' is the direction of the ray to trace (often like (0,-1,0))
// Currently returns valid (si->): x,y,z
{
  DVector3 vWin;
  DVector3 vOrigin,vDir,vPrj,vIntersect;
  int i,n;
  DCSLNode *node;

//qdbg("RTrackVRML:GetSurfaceInfo()\n");

  // Default if no hit
  si->x=si->y=si->z=0;

  // Build ray definition
  vOrigin=*pos;
  vDir=*dir;
  //vDir.Normalize();
  
  // See if it hits a sphere in the track
  QUICK_BUILD_CULLER;
  
  // Check cached triangle first
  if(si->lastNode>=0)
  {
    if(CacheCheckTriangle(&vOrigin,&vDir,
      GetCuller()->GetNode(si->lastNode)->geode,si,&vIntersect))
    {
      // We're done; no change!
      si->x=vIntersect.x;
      si->y=vIntersect.y;
      si->z=vIntersect.z;
      return;
    }
  }
  
  // Search all geodes, all geobs in every geode, and every
  // triangle in each geob
  n=GetCuller()->GetNodes();
  for(i=0;i<n;i++)
  {
    node=GetCuller()->GetNode(i);
    
#ifdef OBS
    DVector3 v;
    Project(&node->center,&v);
qdbg("Projected node center: %f,%f,%f\n",v.x,v.y,v.z);
#endif

#ifdef OBS
v=node->center;
qdbg("Sphere center: %.2f,%.2f,%.2f, r=%.f\n",v.x,v.y,v.z,node->radius);
#endif
    if(RaySphereIntersect(&vOrigin,&vDir,&node->center,node->radius,0))
    {
      if(FindGeodeTriangle(&vOrigin,&vDir,node->geode,si,&vIntersect))
      {
        // vIntersect contains 3D coordinate of triangle intersection
        //*pt=vIntersect;
        // Pass on point of intersection for surface
        si->x=vIntersect.x;
        si->y=vIntersect.y;
        si->z=vIntersect.z;
#ifdef OBS
qdbg("intersection point=(%.2f,%.2f,%.2f) node %d, geob %d, tri %d\n",
vIntersect.x,vIntersect.y,vIntersect.z,i,si->lastGeob,si->lastTri);
vOrigin.DbgPrint("origin");
vDir.DbgPrint("dir");
#endif
        // Store cache info that wasn't already stored
        // by FindGeodeTriangle()
        si->lastNode=i;
        
#ifdef OBS
        // Don't seek further
        break;
#endif
      }
    }
  }
//qdbg("--\n");
}
}