/**************************************************************************\
 *
 *  This file is part of the Coin 3D visualization library.
 *  Copyright (C) 1998-2006 by Systems in Motion.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 *
 *  For using Coin with software that can not be combined with the GNU
 *  GPL, and for taking advantage of the additional benefits of our
 *  support services, please contact Systems in Motion about acquiring
 *  a Coin Professional Edition License.
 *
 *  See http://www.coin3d.org/ for more information.
 *
 *  Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
 *  http://www.sim.no/  sales@sim.no  coin-support@coin3d.org
 *
\**************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#ifdef HAVE_VRML97

/*!
  \class SoVRMLLOD SoVRMLLOD.h Inventor/VRMLnodes/SoVRMLLOD.h
  \brief The SoVRMLLOD class is used to represent various levels of detail based on distance.
  \ingroup VRMLnodes

  \WEB3DCOPYRIGHT

  \verbatim
  LOD {
    exposedField MFNode  level    []
    field        SFVec3f center   0 0 0    # (-,)
    field        MFFloat range    []       # (0,)
  }
  \endverbatim

  The LOD node specifies various levels of detail or complexity for a
  given object, and provides hints allowing browsers to automatically
  choose the appropriate version of the object based on the distance
  from the user. The level field contains a list of nodes that
  represent the same object or objects at varying levels of detail,
  ordered from highest level of detail to the lowest level of
  detail. The range field specifies the ideal distances at which to
  switch between the levels. Subclause 4.6.5, Grouping and children
  nodes
  (<http://www.web3d.org/x3d/specifications/vrml/ISO-IEC-14772-VRML97/part1/concepts.html#4.6.5>),
  contains details on the types of nodes that are legal values
  for level.

  The center field is a translation offset in the local coordinate
  system that specifies the centre of the LOD node for distance
  calculations.

  The number of nodes in the level field shall exceed the number of
  values in the range field by one (i.e., N+1 level values for N range
  values). The range field contains monotonic increasing values that
  shall be greater than zero. In order to calculate which level to
  display, first the distance is calculated from the viewer's
  location, transformed into the local coordinate system of the LOD
  node (including any scaling transformations), to the center point of
  the LOD node.  Then, the LOD node evaluates the step function L(d)
  to choose a level for a given value of d (where d is the distance
  from the viewer position to the centre of the LOD node).  Let n
  ranges, R0, R1, R2, ..., Rn-1, partition the domain (0, +infinity)
  into n+1 subintervals given by (0, R0), [R0, R1)...  , [Rn-1,
  +infinity). Also, let n levels L0, L1, L2, ..., Ln-1 be the values
  of the step function function L(d). The level node, L(d), for a
  given distance d is defined as follows:

  \verbatim
    L(d) = L0,   if d < R0,
         = Li+1, if Ri <= d < Ri+1, for -1 < i < n-1,
         = Ln-1, if d >= Rn-1.
  \endverbatim

  Specifying too few levels will result in the last level being used
  repeatedly for the lowest levels of detail. If more levels than
  ranges are specified, the extra levels are ignored. An empty range
  field is an exception to this rule. This case is a hint to the
  browser that it may choose a level automatically to maintain a
  constant display rate. Each value in the range field shall be
  greater than the previous value.

  LOD nodes are evaluated top-down in the scene graph. Only the
  descendants of the currently selected level are rendered. All nodes
  under an LOD node continue to receive and send events regardless of
  which LOD node's level is active. For example, if an active
  TimeSensor node is contained within an inactive level of an LOD
  node, the TimeSensor node sends events regardless of the LOD node's
  state.
*/

/*!
  \var SoMFFloat SoVRMLLOD::range
  The range for each level.
*/

/*!
  \var SoSFVec3f SoVRMLLOD::center
  The center used when calculating distance.
*/

/*!
  \var SoMFNode SoVRMLLOD::level
  The different levels.
*/

#include <Inventor/VRMLnodes/SoVRMLLOD.h>

#include <Inventor/nodes/SoSubNodeP.h>
#include <Inventor/VRMLnodes/SoVRMLMacros.h>
#include <Inventor/VRMLnodes/SoVRMLParent.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoWriteAction.h>
#include <Inventor/elements/SoCacheElement.h>
#include <Inventor/elements/SoGLCacheContextElement.h>
#include <Inventor/elements/SoModelMatrixElement.h>
#include <Inventor/elements/SoViewVolumeElement.h>
#include <Inventor/misc/SoChildList.h>
#include <Inventor/misc/SoState.h>
#include <Inventor/SbMatrix.h>
#include <Inventor/misc/SoGL.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/system/gl.h>
#include <Inventor/C/glue/glp.h>

#include "../nodes/SoSoundElementHelper.h"

// *************************************************************************

class SoVRMLLODP  : public SoSoundElementHelper
{
public:
  SbBool childlistvalid;
};

#define PRIVATE(obj) ((obj)->pimpl)

// *************************************************************************

SO_NODE_SOURCE(SoVRMLLOD);

// *************************************************************************

// Doc in parent
void
SoVRMLLOD::initClass(void) // static
{
  SO_NODE_INTERNAL_INIT_CLASS(SoVRMLLOD, SO_VRML97_NODE_TYPE);
}

/*!
  Constructor.
*/
SoVRMLLOD::SoVRMLLOD(void)
{
  this->commonConstructor();
}

/*!
  Destructor.
*/
SoVRMLLOD::~SoVRMLLOD() // virtual, protected
{
  delete PRIVATE(this);
}

/*!
  Constructor. \a levels is the expected number of levels.
*/
SoVRMLLOD::SoVRMLLOD(int levels)
  : SoGroup(levels)
{
  this->commonConstructor();
}

void
SoVRMLLOD::commonConstructor(void)
{
  PRIVATE(this) = new SoVRMLLODP;
  PRIVATE(this)->childlistvalid = FALSE;
  
  SO_VRMLNODE_INTERNAL_CONSTRUCTOR(SoVRMLLOD);

  SO_VRMLNODE_ADD_FIELD(center, (0.0f, 0.0f, 0.0f));
  SO_VRMLNODE_ADD_EMPTY_MFIELD(range);
  SO_VRMLNODE_ADD_EMPTY_EXPOSED_MFIELD(level);

  // HACK WARNING: All children of this node are stored in the level
  // field. Avoid double notifications (because of notification
  // through SoChildList) be reallocating the SoChildList with a
  // NULL-parent here. SoGroup will have allocated an SoChildList in
  // its constructor when we get here.
  delete this->SoGroup::children;
  this->SoGroup::children = new SoChildList(NULL);
}

// *************************************************************************

// Doc in parent
SbBool
SoVRMLLOD::affectsState(void) const // virtual
{
  return FALSE;
}

/*!
  Adds a new level.
*/
void
SoVRMLLOD::addLevel(SoNode * levelptr)
{
  this->addChild(levelptr);
}

/*!
  Inserts a new level.
*/
void
SoVRMLLOD::insertLevel(SoNode * levelptr,
                       int idx)
{
  this->insertChild(levelptr, idx);
}

/*!
  Returns a level.
*/
SoNode *
SoVRMLLOD::getLevel(int idx) const
{
  return this->getChild(idx);
}

/*!
  Find \a node, and return the level index or -1 if not found.
*/
int
SoVRMLLOD::findLevel(const SoNode * node) const
{
  return this->findChild(node);
}

/*!
  Return the number of levels.
*/
int
SoVRMLLOD::getNumLevels(void) const
{
  return this->level.getNum();
}

/*!
  Removes the level at index \a idx.
*/
void
SoVRMLLOD::removeLevel(int idx)
{
  this->removeChild(idx);
}

/*!
  Find \a level, and remove it if found.
*/
void
SoVRMLLOD::removeLevel(SoNode * levelptr)
{
  this->removeChild(levelptr);
}

/*!
  Remove all levels.
*/
void
SoVRMLLOD::removeAllLevels(void)
{
  this->removeAllChildren();
}

/*!
  Replace the level at \a idx with \a node.
*/
void
SoVRMLLOD::replaceLevel(int idx,
                        SoNode * node)
{
  this->replaceChild(idx, node);
}

/*!
  Find \a oldnode, and replace it with \a newnode.
*/
void
SoVRMLLOD::replaceLevel(SoNode * oldnode,
                        SoNode * newnode)
{
  this->replaceChild(oldnode, newnode);
}

// Doc in parent
void
SoVRMLLOD::doAction(SoAction * action)
{
  int numindices;
  const int * indices;
  SoAction::PathCode pathcode = action->getPathCode(numindices, indices);
  if (pathcode == SoAction::IN_PATH) {
    this->getChildren()->traverseInPath(action, numindices, indices);
  }
  else {
    int idx = this->whichToTraverse(action);;
    if (idx >= 0) {
      this->getChildren()->traverse(action, idx);
      PRIVATE(this)->enableTraversingOfInactiveChildren();
      PRIVATE(this)->traverseInactiveChildren(this, action, idx, pathcode,
                                              this->getNumChildren(), 
                                              this->getChildren());
    }
  }
}

// Doc in parent
void
SoVRMLLOD::callback(SoCallbackAction * action)
{
  SoVRMLLOD::doAction((SoAction*)action);
}

// Doc in parent
void
SoVRMLLOD::GLRender(SoGLRenderAction * action)
{
  switch (action->getCurPathCode()) {
  case SoAction::NO_PATH:
  case SoAction::BELOW_PATH:
    SoVRMLLOD::GLRenderBelowPath(action);
    break;
  case SoAction::IN_PATH:
    SoVRMLLOD::GLRenderInPath(action);
    break;
  case SoAction::OFF_PATH:
    SoVRMLLOD::GLRenderOffPath(action);
    break;
  default:
    assert(0 && "unknown path code.");
    break;
  }
}

// Doc in parent
void
SoVRMLLOD::rayPick(SoRayPickAction * action)
{
  SoVRMLLOD::doAction((SoAction*) action);
}

// Doc in parent
void
SoVRMLLOD::getBoundingBox(SoGetBoundingBoxAction * action)
{
  // FIXME: SGI OIV seems to do some extra work here, but the manual
  // pages states that it should do a normal SoGroup traversal.
  // we should _not_ use whichToTraverse() to calculate bbox as
  // this would cause cache dependencies on the camera and
  // the model matrix.                       pederb, 2001-02-21
  inherited::getBoundingBox(action);
}

// Doc in parent
void
SoVRMLLOD::search(SoSearchAction * action)
{
  inherited::search(action);
}

// Doc in parent
void
SoVRMLLOD::write(SoWriteAction * action)
{
  SoNode::write(action);
}

// Doc in parent
void
SoVRMLLOD::getPrimitiveCount(SoGetPrimitiveCountAction * action)
{
  SoVRMLLOD::doAction((SoAction*) action);
}

// Doc in parent
void
SoVRMLLOD::audioRender(SoAudioRenderAction * action)
{
  PRIVATE(this)->preAudioRender(this, action);
  SoVRMLLOD::doAction((SoAction*) action);
  PRIVATE(this)->postAudioRender(this, action);
}

// Doc in parent
void
SoVRMLLOD::GLRenderBelowPath(SoGLRenderAction * action)
{
  int idx = this->whichToTraverse(action);
  if (idx >= 0) {
    SoNode * child = (SoNode*) this->getChildren()->get(idx);
    action->pushCurPath(idx, child);
    if (!action->abortNow()) {
      child->GLRenderBelowPath(action);
#if COIN_DEBUG
      // The GL error test is default disabled for this optimized
      // path.  If you get a GL error reporting an error in the
      // Separator node, enable this code by setting the environment
      // variable COIN_GLERROR_DEBUGGING to "1" to see exactly which
      // node caused the error.
      static SbBool chkglerr = sogl_glerror_debugging();
      if (chkglerr) {
        cc_string str;
        cc_string_construct(&str);
        const unsigned int errs = coin_catch_gl_errors(&str);
        if (errs > 0) {
          SoDebugError::post("SoVRMLLOD::GLRenderBelowPath",
                             "glGetError()s => '%s', nodetype: '%s'",
                             cc_string_get_text(&str),
                             (*this->getChildren())[idx]->getTypeId().getName().getString());
        }
        cc_string_clean(&str);
      }
#endif // COIN_DEBUG
    }
    action->popCurPath();
  }
  // don't auto cache LOD nodes.
  SoGLCacheContextElement::shouldAutoCache(action->getState(),
                                           SoGLCacheContextElement::DONT_AUTO_CACHE);
}

// Doc in parent
void
SoVRMLLOD::GLRenderInPath(SoGLRenderAction * action)
{
  int numindices;
  const int * indices;
  SoAction::PathCode pathcode = action->getPathCode(numindices, indices);

  if (pathcode == SoAction::IN_PATH) {
    for (int i = 0; (i < numindices) && !action->hasTerminated(); i++) {
      int idx = indices[i];
      SoNode * node = this->getChild(idx);
      action->pushCurPath(idx, node);
      if (!action->abortNow()) {
        node->GLRenderInPath(action);
      }
      action->popCurPath(pathcode);
    }
  }
  else {
    assert(pathcode == SoAction::BELOW_PATH);
    SoVRMLLOD::GLRenderBelowPath(action);
  }
}

// Doc in parent
void
SoVRMLLOD::GLRenderOffPath(SoGLRenderAction * action)
{
  int idx = this->whichToTraverse(action);;
  if (idx >= 0) {
    SoNode * node = this->getChild(idx);
    if (node->affectsState()) {
      action->pushCurPath(idx, node);
      if (!action->abortNow()) {
        node->GLRenderOffPath(action);
      }
      action->popCurPath();
    }
  }
}

// Doc in parent
void
SoVRMLLOD::addChild(SoNode * child)
{
  this->level.addNode(child);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
void
SoVRMLLOD::insertChild(SoNode * child, int idx)
{
  this->level.insertNode(child, idx);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
SoNode *
SoVRMLLOD::getChild(int idx) const
{
  return this->level.getNode(idx);
}

// Doc in parent
int
SoVRMLLOD::findChild(const SoNode * child) const
{
  return this->level.findNode(child);
}

// Doc in parent
int
SoVRMLLOD::getNumChildren(void) const // virtual
{
  return this->level.getNumNodes();
}

// Doc in parent
void
SoVRMLLOD::removeChild(int idx)
{
  this->level.removeNode(idx);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
void
SoVRMLLOD::removeChild(SoNode * child)
{
  this->level.removeNode(child);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
void
SoVRMLLOD::removeAllChildren(void)
{
  this->level.removeAllNodes();
  SoGroup::children->truncate(0);
  PRIVATE(this)->childlistvalid = TRUE;
}

// Doc in parent
void
SoVRMLLOD::replaceChild(int idx, SoNode * child)
{
  this->level.replaceNode(idx, child);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
void
SoVRMLLOD::replaceChild(SoNode * old,
                           SoNode * child)
{
  this->level.replaceNode(old, child);
  PRIVATE(this)->childlistvalid = FALSE;
}

// Doc in parent
void
SoVRMLLOD::notify(SoNotList * list)
{
  SoField * f = list->getLastField();
  if (f == &this->level) {
    PRIVATE(this)->childlistvalid = FALSE;
  }
  inherited::notify(list);
  PRIVATE(this)->notifyCalled();
}

// Doc in parent
SbBool
SoVRMLLOD::readInstance(SoInput * in,
                        unsigned short flags)
{
  SoGroup::children->truncate(0);
  SbBool oldnot = this->level.enableNotify(FALSE);
  SbBool ret = inherited::readInstance(in, flags);
  if (oldnot) this->level.enableNotify(TRUE);
  PRIVATE(this)->childlistvalid = FALSE;
  return ret;
}

// Doc in parent
void
SoVRMLLOD::copyContents(const SoFieldContainer * from,
                        SbBool copyConn)
{
  SoGroup::children->truncate(0);
  SoNode::copyContents(from, copyConn);
  PRIVATE(this)->childlistvalid = FALSE;
}

/*!
  Returns the child to traverse based on distance to current viewpoint.
*/
int
SoVRMLLOD::whichToTraverse(SoAction * action)
{
  // FIXME: according to the spec, if range is empty, we should decide
  // a level to try to maintain a constant/high frame rate...
  // pederb, 2002-06-10
  SoState * state = action->getState();
  const SbMatrix & mat = SoModelMatrixElement::get(state);
  const SbViewVolume & vv = SoViewVolumeElement::get(state);

  SbVec3f worldcenter;
  mat.multVecMatrix(this->center.getValue(), worldcenter);

  float dist = (vv.getProjectionPoint() - worldcenter).length();

  int i;
  int n = this->range.getNum();

  for (i = 0; i < n; i++) {
    if (dist < this->range[i]) break;
  }
  if (i >= this->getNumChildren()) i = this->getNumChildren() - 1;
  return i;
}

// Doc in parent
SoChildList *
SoVRMLLOD::getChildren(void) const
{
  if (!PRIVATE(this)->childlistvalid) {
    SoVRMLParent::updateChildList(this->level.getValues(0),
                                  this->level.getNum(),
                                  *SoGroup::children);
    ((SoVRMLLOD*)this)->pimpl->childlistvalid = TRUE;
  }
  return SoGroup::children;
}

#undef PRIVATE

#endif // HAVE_VRML97
