//<copyright>
//
// Copyright (c) 1995,96,97
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
// This file is part of VRweb.
//
// VRweb is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// VRweb is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with VRweb; see the file LICENCE. If not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// Note that the GNU General Public License does not permit incorporating
// the Software into proprietary or commercial programs. Such usage
// requires a separate license from IICM.
//
//</copyright>

//<file>
//
// Name:        vrmlscene.C
//
// Purpose:     implementation of class VRMLScene
//
// Created:     24 Apr 95   Michael Pichler
//
// Changed:      5 Apr 96   Georg Meszaros (BSP)
//
// Changed:      4 Nov 96   Karl Heinz Wagenbrunn (stereo view)
//
// Changed:     20 Feb 97   Alexander Nussbaumer (editing)
//
// Changed:     20 Feb 97   Michael Pichler
//
// $Id: vrmlscene.C,v 1.48 1997/04/30 10:39:49 mpichler Exp $
//
//</file>


#include "vrmlscene.h"
#include "scene3d.h"
#include "vecutil.h"
#include "camera.h"
#include "arrays.h"
#include "bsptree.h"
#include "gecontext.h"

#include <ge3d/ge3d.h>

#include <vrml/QvDB.h>
#include <vrml/QvInput.h>
#include <vrml/QvDebugError.h>
#include <vrml/QvReadError.h>
#include <vrml/QvNode.h>
#include <vrml/QvChildList.h>
#include <vrml/QvState.h>
#include <vrml/QvSFString.h>
#include <vrml/QvTransform.h>
#include <vrml/QvWWWInline.h>
#include <vrml/QvPerspectiveCamera.h>
#include <vrml/QvOrthographicCamera.h>
#include <vrml/QvWWWAnchor.h>

#include <hyperg/hyperg/message.h>
#include <hyperg/utils/verbose.h>

#include <math.h>



/*** VRMLErrorCallback ***/

struct VRMLErrorCallback  // used by VRMLScene only
{
  static Scene3D* scene_;  // assert: scene_ pointer set before callbacks called
  static void debugError (const char* method, const char* errormsg);
  static void readError (const char* error, const char* location);
};


Scene3D* VRMLErrorCallback::scene_ = 0;


void VRMLErrorCallback::debugError (const char* method, const char* errormsg)
{
  char buf [2500];
  sprintf (buf, "VRML error in %.1024s(): %.1024s\n", method, errormsg);
  scene_->errorMessage (buf);
}


void VRMLErrorCallback::readError (const char* error, const char* location)
{
  char buf [2500];
  sprintf (buf, "VRML read error: %.1024s\n%.1024s\n", error, location);
  scene_->errorMessage (buf);
}


/*** VRMLScene ***/


int VRMLScene::doautosmooth_ = 1;
int VRMLScene::doconvexify_ = 1;

// red-green stereo colors: red (left), cyan (right)
int VRMLScene::leftred_ = 1, VRMLScene::leftgreen_ = 0, VRMLScene::leftblue_ = 0;
int VRMLScene::rightred_ = 0, VRMLScene::rightgreen_ = 1, VRMLScene::rightblue_ = 1;

// gmes: BSP settings
int VRMLScene::render_mode_ = VRMLScene::rnd_zbuffer;  // rnd_zbuffer or rnd_bsptree
int VRMLScene::bsp_mode_ = BSP_BACK_FACE_CULLING;  // BSP_*
float VRMLScene::frame_tolerance_ = 10;  // recalculate visibility every n+1 frames
// TODO: if frame_tolerance_ > 0 force complete redraw on mouse release
// frame_tolerance_ = 0 forces a redraw every frame
// float because of NavigationSettingsDialog

float VRMLScene::area_margin_ = 0;
// 0  : use all polygons for visibility calculations
// 0.5: use all polygons which projection is > 0.5 * largest polygon
// 1  : no visibility calculation at all
// currently not used

int VRMLScene::first_x_polygons_ = 100;  // maximum no. of polygons to calculate the visibility
// is currently not used in SVBSPTree::replace


VRMLScene::VRMLScene (Scene3D* scene)
: SceneData (scene)
{
  root_ = 0;
  matbuilt_ = 0;
  numfaces_ = 0;
  numprimitives_ = 0;
  hastextures_ = 0;
  hascamera_ = 0;
  haslight_ = 0;
  numlights_ = 0;
  quickwire_ = 0;
  camera_ = new Camera;  // default camera
  bakcamera_ = new Camera (*camera_);  // copy of original camera
  activepcam_ = 0;
  activeocam_ = 0;
  QvNode::scene_ = scene;
  QvNode::vrmlscene_ = this;

  // anuss
  vrmlversion_ = 1.0;  // set on reading; defaults to 1.0 for File|New
  unselectPropertyNodes ();
  clearUndoStorage ();
  emptyBoundingbox (sel_omin_, sel_omax_);  // reset values
  paste_ = 0;
  materialdialog_ = 0;
  texturedialog_ = 0;
  wwwanchordialog_ = 0;
  structureviewerdialog_ = 0;
  transformdialog_ = 0;
  viewpointdialog_ = 0;
  griddialog_ = 0;
  constraints_ = 1;  // initial value, also taken from SceneMenus
  grid_.x = grid_.y = grid_.z = 0;
  grid_position_.x = grid_position_.y = grid_position_.z = 0.0;
  grid_extent_ = 1;
  grid_distance_ = 0.1;
  camswitch_ = 0;
  startedediting_ = 0;

  // gmes: BSP
  bsp_root_ = 0;
  frames_to_render_ = 0;  // to start with visibility calculation

  // error callbacks
  VRMLErrorCallback::scene_ = scene;
  if (!QvDebugError::callback_)
    QvDebugError::callback_ = &VRMLErrorCallback::debugError;
  if (!QvReadError::callback_)
    QvReadError::callback_ = &VRMLErrorCallback::readError;
} // VRMLScene


VRMLScene::~VRMLScene ()
{
  if (bsp_root_)  // gmes, 19960601
    bsp_root_->reset ();
  delete bsp_root_;  // gmes, 19960311

  delete root_;
  delete camera_;     // unlike SDFScene, both cameras are separate instances
  delete bakcamera_;  // and not pointers into the data structure
  QvNode::scene_ = 0;
  QvNode::vrmlscene_ = 0;

  // anuss
  unselectPropertyNodes ();  // delete some dynamic constructed nodes
  // see also updateEditContext

//root_ = 0;
//bsp_root_ = 0;
//matbuilt_ = 0;
//numfaces_ = 0;
//numprimitives_ = 0;
//hastextures_ = 0;
//hascamera_ = 0;
//activepcam_ = 0;
//activeocam_ = 0;
//haslight_ = 0;
//numlights_ = 0;
}


int VRMLScene::readInput (QvInput& in)
{
  if (root_)
  { HgMessage::error ("reading VRML scene over existent one (internal error).");
    return -1;
  }

  scene_->progress (0.0, Scene3D::progr_readvrml);

  int ok = QvDB::read (&in, root_) && root_;

  if (!ok)
    HgMessage::error ("invalid VRML scene");
  else
    vrmlversion_ = in.getVersion ();  // anuss

  // invalid scene should be destroyed immediately after reading
  return !ok;  // error code
} // readInput


// readInlineVRML
// read VRML inline data from file into node, rebuild scene graph

int VRMLScene::readInlineVRML (QvWWWInline* node, FILE* file)
{
  if (!node || !file || !root_)
    return 0;

  // QVDB::init () not necessary
  QvInput in;
  in.setFilePointer (file);

  // parser log entry
  RString logmsg = "note: parsing WWWInline ";
  logmsg += node->name.value.getString ();
  scene_->errorMessage (logmsg);

  DEBUGNL ("read in children of node " << (void*) node);
  int ok = node->readChildren (&in);
  DEBUGNL ("read children. ok flag: " << ok << ", no. of children: " << node->getNumChildren ());

// note that even when the inline happens to be read from local file
// during the draw operation, we cannot defer a rebuild to the next
// draw, because the node could be USEd later again in the same scene

  if (ok)
  {
    node->state_ = QvWWWInline::s_completed;
    DEBUGNL ("VRMLScene::readInlineVRML: rebuild");

    rebuild (0);
    // caller responsible for redraw
  }
  else
  {
    HgMessage::error ("WWWInline: invalid VRML scene");
    node->state_ = QvWWWInline::s_failed;
  }

  // file will be closed by caller

  DEBUGNL ("VRMLScene::readInlineVRML finished");
  return ok;  // redraw flag

} // readInlineVRML


// font related stuff (fontchars_, getFontChars) see fonts.C


// anuss

int VRMLScene::writeData (ostream& os, int format)
{
  if (format == Scene3D::write_SDF)
  { HgMessage::error ("Cannot convert current VRML-Scene to SDF-Format");
    return 1;
  }

  if (!root_)
  { HgMessage::error ("Cannot save empty Scene");
    return 1;
  }

  QvNode::saveAll (root_, os, vrmlversion_);

  return 0;  // success
}


void VRMLScene::printInfo (int /*all*/)
{
  QvState state;
  if (root_)
    root_->traverse (&state);
}


int VRMLScene::supportsOutputformat (int format)
{
  return (format == Scene3D::write_VRML);
}


void VRMLScene::storeCamera ()
{
  *bakcamera_ = *camera_;  // save current camera (default assignment operator)
}


void VRMLScene::restoreCamera ()
{
  *camera_ = *bakcamera_;  // restore last saved camera (default assignment op.)
}


void VRMLScene::hasCamera (QvPerspectiveCamera* cam, const char* name)
{
  if (!hascamera_)  // first one
  { activatePCam (cam);
    hascamera_ = 1;
    activepcam_ = cam;
  }
  RString truename = name;
  RString nickname = truename;
  nickname.subst ('_', ' ');

  scene_->registerCamera (truename, nickname, cam, NULL);

  if (!camswitch_ && cam->camswitch_)
    camswitch_ = cam->camswitch_; // for editing viewpoints
}


void VRMLScene::hasCamera (QvOrthographicCamera* cam, const char* name)
{
  if (!hascamera_)  // first one
  { activateOCam (cam);
    hascamera_ = 1;
    activeocam_ = cam;
  }
  RString truename = name;
  RString nickname = truename;
  nickname.subst ('_', ' ');

  scene_->registerCamera (truename, nickname, NULL, cam);

  if (!camswitch_ && cam->camswitch_)
    camswitch_ = cam->camswitch_; // for editing viewpoints
}


void VRMLScene::activatePCam (QvPerspectiveCamera* cam)
{
  if (!camera_ || !cam)
    return;

  // cerr << "activated perspective camera" << endl;
  camera_->reset ();  // position, orientation done by camera node
  camera_->perspectiveCam (cam->yangle_);  // radians
  storeCamera ();
  cam->switchto ();
}


void VRMLScene::activateOCam (QvOrthographicCamera* cam)
{
  if (!camera_ || !cam)
    return;

  // cerr << "activated orthographic camera" << endl;
  camera_->reset ();
  camera_->orthographicCam (cam->height.value);  // vp.height
  storeCamera ();
  cam->switchto ();
}


void VRMLScene::activateCamera (
  const char* name,
  QvPerspectiveCamera* pcam, QvOrthographicCamera* ocam
)
{
  if (pcam)
    activatePCam (pcam);
  else
    activateOCam (ocam);

  scene_->statusMessage (name);

  if (pcam != activepcam_ || ocam != activeocam_)
  {
    activepcam_ = pcam;
    activeocam_ = ocam;

    DEBUGNL ("VRMLScene: rebuild after camera change");
    rebuild (0);
  }
  // caller responsible for redraw
} // activateCamera


void VRMLScene::deactivateLights (int n)
{
  if (numlights_ > 7)
  { DEBUGNL ("warning: only up to 8 light sources (incl. headlight) in OpenGL (" << numlights_ << " in scene)");
  }
  //cerr << "turning off lights " << n+1 << " to " << numlights_ << endl;
  // turn off all lights with index > n
  while (numlights_ > n)
    ge3d_switchlight (numlights_--, 0);
}


// void VRMLScene::rebuild and colorRebuild: see wrlbuild.C

void VRMLScene::draw (int curmode)
{
  static colorRGB white = { 1.0, 1.0, 1.0 };
  int eye;

  if (!root_)
    return;

  QvNode::scene_ = scene_;
  QvNode::vrmlscene_ = this;
  QvNode::curdrawmode_ = curmode;

  int rendermode = render_mode_;
  if (scene_->front2backDrawing ())
    rendermode = rnd_bsptree;


  if (!matbuilt_)  // preprocessing
  {
    matbuilt_ = 1;
    DEBUGNL ("VRMLScene: preprocessing (first draw)");
    ge3d_init_ ();  // initialize ge3d library

    QvState state;
    RString parenturl = scene_->mostRecentURL ();

    ge3dPushIdentity ();  // top node need not be a separator

    root_->build (&state);  // preprocessing
    // done on first draw because graphics libraries require an opened window

    ge3d_pop_matrix ();

    if (root_->hasextent_)
    {
      DEBUGNL ("scene bounding box: " << root_->wmin_ << ", " << root_->wmax_);

      scene_->setBoundings (root_->wmin_, root_->wmax_);  // scene bounding box
      float clip = Scene3D::clipFarFactor * scene_->size ();
      DEBUGNL ("far clipping plane (" << Scene3D::clipFarFactor << " * size): " << clip);

      // prevent scene from being clipped off when camera is too far away from scene
      point3D center;  // by setBoundings
      scene_->getCenter (center);
      // currently camera at origin in world coordinates; later: subtract camera position
      DEBUGNL ("scene center: " << center << ", size: " << scene_->size ());

      float dist = norm3D (center) + 3 * scene_->size ();
      if (clip < dist)
      { DEBUGNL ("far clipping plane increased to " << dist);
        clip = dist;
      }

      // if (camera_->getyon () < clip)
      camera_->setyon (clip),
      bakcamera_->setyon (clip);

      clip /= Scene3D::clipNearRatio;  // near clipping plane
      DEBUGNL ("near clipping plane (1/" << Scene3D::clipNearRatio << " of far; "
               "set to " << Scene3D::clipNearMaximum << " if larger): " << clip);
      if (clip > Scene3D::clipNearMaximum)
        clip = Scene3D::clipNearMaximum;
      camera_->sethither (clip),
      bakcamera_->sethither (clip);
    }

    scene_->showNumFaces ();  // only available after build
    // may change after dynamic loading of inline scenes

    if (!hascamera_)  // define a default camera (based on bounding box)
    {
      viewAll ();
      *bakcamera_ = *camera_;  // save this camera
    }

    // goto destination (#viewpoint)
    const char* fispos = strrchr (parenturl.string (), '#');
    if (fispos)
    {
      RString vpname = parenturl.gRight (fispos - parenturl.string () + 1);
      DEBUGNL ("activating viewpoint " << vpname << " of the URL " << parenturl);
      scene_->activateCamera (vpname);
    }

    DEBUGNL ("VRMLScene: preprocessing finished");
  } // preprocessing

  // build the BSP Tree, Georg Meszaros
  if (rendermode == rnd_bsptree && !bsp_root_)
  {
    DEBUGNL ("building BSP tree");
    buildBSPTree ();  // see bspbuild.C

    num_bsp_faces_ = bsp_root_->faceNumber ();
    num_bsp_nodes_ = bsp_root_->nodeNumber ();
    scene_->showBSPInfo ();
    DEBUGNL ("building BSP tree finished");
  }

  ge3dHint (hint_depthbuffer, rendermode == rnd_zbuffer);

  ge3d_setmode (curmode);  // activate drawing mode

  // TODO: overall material (also for anchor highlighting) ##
  ge3dFillColor (&white);  // default line/fill color
  ge3dHint (hint_quadslices, (int) Scene3D::QuadSlices_);

  ge3dLoadIdentity ();  // replace current matrix with identity (for camera)

  int stereo = scene_->stereoMode ();
  quickwire_ = stereo && (curmode == ge3d_wireframe) && (GEContext::implementationHints () & GEContext::impl_slow);

 for (eye = stereo;  eye < 3;  eye++)
 {
  // cerr << "draw[" << eye << "]";
  camera_->setCamera (scene_, eye);
  // side effects of viewing and scene camera to each other (?)

  if (scene_->manipulationAllowed ())
    drawGrid ();

  if (!haslight_ || scene_->viewingLight ())  // default viewing light (headlight)
  {
    static const vector3D vwlgtdir = { 0, 0, 1 };  // towards camera
    ge3dSetLightSource (0, &scene_->col_viewinglight, &vwlgtdir, 0.0, 1);
    ge3d_switchlight (0, 1);
  }

  if (stereo)
  {
    if (quickwire_)
    {
      if (eye == 1)
        ge3d_setlinecolor (rightred_, rightgreen_, rightblue_);
      else
        ge3d_setlinecolor (leftred_, leftgreen_, leftblue_);
    }
    else if (eye == 1)  // right: red
      ge3d_colormask (rightred_, rightgreen_, rightblue_, 1);
    else  // left: green
      ge3d_colormask (leftred_, leftgreen_, leftblue_, 1);
  }

  // for screendumps only
  // ge3d_setlinewidth(3);

  // wireframes are usually not to be rendered via BSP tree (only for debugging)

  if (rendermode == rnd_zbuffer || curmode == ge3d_wireframe)
  {
    // light sources are activated within scene graph
    numlights_ = 0;

    // no backfaceculling unless never twosided
    ge3dHint (hint_backfaceculling, scene_->twosidedpolys () == Scene3D::twosided_never);
    // do lighting unless unless turned off globally
    ge3dHint (hint_lighting, scene_->dolighting () != Scene3D::lighting_never);
    // texture lighting flag
    ge3dHint (hint_texlighting, scene_->textureLighting ());

    // textures requested on demand during draw

    drawVRML ();  // draw the scene graph (see wrldraw.C)

//   ge3d_setmode (ge3d_wireframe);  // test: show world bounding box
//   ge3d_setlinecolor (1.0, 0.0, 0.0);
//   ge3dCube (&root_->wmin_, &root_->wmax_);

    deactivateLights ();  // deactivate remaining light sources
  }
  else // if (rendermode == rnd_bsptree)
  {
    drawBSPTree ();  // see bspdraw.C
  } // BSP


  ge3d_switchlight (0, 0);  // turn off default viewing light

  ge3dDefaultMaterial ();  // some materials (e.g. emissive) interfere with wireframe GUI drawings
  ge3dDoTexturing (0);  // turn off texturing

  ge3dHint (hint_lighting, 1);

  if (!stereo)  // one viewpoint
    return;     // ge3dDefaultMaterial was called above

  if (eye == 1)  // clear depth buffer between eyes
    ge3d_clearzbuffer ();

 } // for eye

  // restore colormask after stereo view
  ge3d_colormask (1, 1, 1, 1);
  // ge3dDefaultMaterial was called above

} // draw



// VRMLScene::pickObject implemented in wrlpick.C


// selectedPosition
// convert path to selected node into string suitable for
// source anchor definition in Hyper-G
// position (if could be set) starts with a blank

// TODO 0: traverse scene graph while converting to string
// TODO 1: skip anchor nodes introduced by merging Hyper-G anchors into
// the scene graph
// TODO 2: raise error when anchor is defined inside an inline node
// (ideally, a source anchor would then be created for inline object)
// TODO 3: give warning when other node selected than geometric primitive
// or Separator (changes semantics of scene; anchor behaves as separator)

void VRMLScene::selectedPosition (RString& pos)
{
  pos = "";
  const IntArray* selpath = scene_->selectedPath ();
  if (!selpath)
    return;

  char indexstr [32];

  int i = selpath->count ();
  const int* data = selpath->data ();

  DEBUG ("selected path: " << *selpath << flush);  // operator << adds '\n'

  while (i--)
  {
    sprintf (indexstr, " %d", *data++);
    pos += indexstr;
  }
//cerr << "position string: " << pos << endl;

} // selectedPosition


// getParentByPath
// returns the parent node according to the path
// when getpar_noHyperGanchors is set, Hyper-G anchors along the path are skipped

QvGroup* VRMLScene::getParentByPath (const IntArray* patharr, int flags) const
{
  if (!patharr || !root_)
    return 0;

  int depth = patharr->count ();
  if (!depth)
    return 0;
  depth--;

  int val;
  const int* path = patharr->data ();
  QvGroup* go = (QvGroup*) root_;
  // loop invariant: go really pointing to grouping node (derived from QvGroup)

  while (depth--)
  {
    val = *path++;

    if (go && (val < go->getNumChildren ()))
    {
      go = (QvGroup*) go->getChild (val);

      if (flags & getpar_noHyperGanchors)
      { while (go && go->nodeType () == QvNodeType::QvWWWAnchor
                  && go->getNumChildren () > 0 && ((QvWWWAnchor*) go)->hganchorid_)
          go = (QvGroup*) go->getChild (0);
      }

      if (!go || !go->isGroupNode ())
      { // maintain loop invariant
        go = 0;
        break;
      }
    }
    else
    { go = 0;
      break;
    }
  }

  return (QvGroup*) go;
} // getParentByPath



// clearAnchors
// implementation see wrlpass.C
// keeping a source anchor list and clearing the anchors therein fails
// when the scene graph is restructured on editing (delete, insert),
// so clearAnchors must do a scene graph traversal, oof.


// getNodeByName
// implementation see wrlpass.C
// find a named node and its parent and the childindex


// findTexture
// implementation see wrlpass.C
// find Texture2 node by file name field


// defineHyperGanchor
// insert Hyper-G source anchor given by position into the scene graph;
// WWWAnchor node is flagged to prevent being part of path on
// insertion of other anchors (getParentByPath) or definition
// of new ones (selectedPosition); called by Hg3dViewer::storeanchor

void VRMLScene::defineHyperGanchor (long id, const RString& aobj, const char* posstr, const RString& title)
{
  QvGroup* parent = 0;
  int childindex = 0;

  if (!id)
  { cerr << "harscened: anchor id 0 unexpected" << endl;
    return;
  }

  if (!strncmp (posstr, "Path ", 5))  // path given
  {
    IntArray path;
    const char* p = posstr + 5;
    while (*p == ' ')
      p++;

    while (*p)
    {
      if (!sscanf (p, "%d", &childindex))
      { cerr << "harscened: invalid token in VRML source anchor position '" << posstr << "'" << endl;
        return;
      }
      path.append (childindex);
      // got 1 integer value
      while ('0' <= *p && *p <= '9')
        p++;
      while (*p == ' ')
        p++;
    }
    // childindex contains last index read, i.e. child index of parent

    DEBUG ("converted position " << posstr << " to path " << path << flush);

    parent = getParentByPath (&path, getpar_noHyperGanchors);
    // in case parent separator got an anchor wrapped around,
    // getParentByPath will return the real parent node for the anchor;
    // if the anchor source already got an anchor, the new one will be
    // wrapped around; as in VRML the innermost anchor "wins", the
    // first anchor specified for a node will be taken.

  } // "Path "
  else
  {
    // assume node name (not unique!), determine parent and childindex
    // QvInput::findReference would find the node, but (i) QvInput only exists during
    // parsing and (ii) has not parent/childindex information
    DEBUGNL ("searching for node named '" << posstr << "'");

    getNodeByName (posstr, parent, childindex);

    // cerr << "harscened: VRML source anchor position '" << posstr << "': invalid or unsupported format." << endl;
    // return;
  }

  if (!parent || childindex < 0 || childindex >= parent->getNumChildren ())
  { cerr << "harscened: no VRML node corresponding to source anchor position '" << posstr << "' found." << endl;
    return;
  }

  DEBUGNL ("harscened: going to add child no. " << childindex << " to parent node.");

  QvChildList* childlist = parent->getChildren ();  // non-NULL
  QvNode* origchild = parent->getChild (childindex);
  QvWWWAnchor* hganchor = new QvWWWAnchor ();
  hganchor->getChildren ()->append (origchild);  // move origchild into new anchor; ref
  childlist->remove (childindex);  // remove origchild at old position; unref
  childlist->insert (childindex, hganchor);  // ref

  hganchor->hganchorid_ = id;  // set anchor ID

  // parentURL_ used for anchor object
  hganchor->parentURL_ = aobj.string ();
  // might set name to Hint (destination URL) in certain cases

  // description: anchor title
  hganchor->description.value = title.string ();

  // take child's bounding box (normally done in build)
  // this will be wrong when origchild has a transformation that formerly "leaked out"
  if (origchild->hasextent_)
  { // no transformation associated with anchor
    hganchor->hasextent_ = 1;
    // only object bounding box needed for picking
    hganchor->omin_ = origchild->omin_;
    hganchor->omax_ = origchild->omax_;
    hganchor->wmin_ = origchild->wmin_;
    hganchor->wmax_ = origchild->wmax_;
  }

} // defineHyperGanchor
