//
//  Vrml 97 library
//  Copyright (C) 1998 Chris Morley
//
//  %W% %G%
//  VrmlNodeProximitySensor.cpp

#include "config.h"
#include "VrmlNodeProximitySensor.h"
#include "VrmlNodeType.h"

#include "MathUtils.h"
#include "System.h"
#include "Viewer.h"
#include "VrmlScene.h"

// ProximitySensor factory. 

static VrmlNode *creator( VrmlScene *scene ) 
{
  return new VrmlNodeProximitySensor(scene);
}


// Define the built in VrmlNodeType:: "ProximitySensor" fields

VrmlNodeType *VrmlNodeProximitySensor::defineType(VrmlNodeType *t)
{
  static VrmlNodeType *st = 0;

  if (! t)
    {
      if (st) return st;		// Only define the type once.
      t = st = new VrmlNodeType("ProximitySensor", creator);
    }

  VrmlNodeChild::defineType(t);	// Parent class
  t->addExposedField("center", VrmlField::SFVEC3F);
  t->addExposedField("enabled", VrmlField::SFBOOL);
  t->addExposedField("size", VrmlField::SFVEC3F);
  t->addEventOut("enterTime", VrmlField::SFTIME);
  t->addEventOut("exitTime", VrmlField::SFTIME);
  t->addEventOut("isActive", VrmlField::SFBOOL);
  t->addEventOut("orientation_changed", VrmlField::SFROTATION);
  t->addEventOut("position_changed", VrmlField::SFVEC3F);

  return t;
}

VrmlNodeType *VrmlNodeProximitySensor::nodeType() const { return defineType(0); }


VrmlNodeProximitySensor::VrmlNodeProximitySensor( VrmlScene *scene ) :
  VrmlNodeChild(scene),
  d_center(0.0, 0.0, 0.0),
  d_enabled(true),
  d_size(0.0, 0.0, 0.0),
  d_isActive(false),
  d_position(0.0, 0.0, 0.0),
  d_enterTime(0.0),
  d_exitTime(0.0)
{
  setModified();
}

VrmlNodeProximitySensor::~VrmlNodeProximitySensor()
{
}

VrmlNode *VrmlNodeProximitySensor::cloneMe() const
{
  return new VrmlNodeProximitySensor(*this);
}


ostream& VrmlNodeProximitySensor::printFields(ostream& os, int indent)
{
  if (! FPZERO(d_center.x()) ||
      ! FPZERO(d_center.y()) ||
      ! FPZERO(d_center.z()) )
    PRINT_FIELD(center);
  if (! d_enabled.get())
    PRINT_FIELD(enabled);
  if (! FPZERO(d_size.x()) ||
      ! FPZERO(d_size.y()) ||
      ! FPZERO(d_size.z()) )
    PRINT_FIELD(size);
      
  return os;
}

//
// Generate proximity events. If necessary, events prior to the current
// time are generated due to interpolation of enterTimes and exitTimes. 
// The timestamp should never be increased.
//
// This is in a render() method since the it needs the viewer position
// with respect to the local coordinate system.
// Could do this with VrmlNode::inverseTransform(double [4][4]) now...
//
// The positions and times are not interpolated to report the exact
// place and time of entries and exits from the sensor regions as
// required by the spec, since that would require the last viewer position
// to be stored in the node, which is problematic in the presence of
// DEF/USEd nodes...
// I suppose the scene could keep the last viewer position in the global
// coordinate system and it could be transformed all the way down the
// scenegraph, but that sounds painful.

void VrmlNodeProximitySensor::render(Viewer *viewer)
{
  if (d_enabled.get() &&
      d_size.x() > 0.0 &&
      d_size.y() > 0.0 &&
      d_size.z() > 0.0 &&
      viewer->getRenderMode() == Viewer::RENDER_MODE_DRAW)
    {
      VrmlSFTime timeNow( theSystem->time() );
      float x, y, z;

      // Is viewer inside the box?
      viewer->getPosition( &x, &y, &z );
      bool inside = (fabs(double(x - d_center.x())) <= 0.5 * d_size.x() &&
 		     fabs(double(y - d_center.y())) <= 0.5 * d_size.y() &&
 		     fabs(double(z - d_center.z())) <= 0.5 * d_size.z());
      bool wasIn = d_isActive.get();

      // Check if viewer has entered the box
      if (inside && ! wasIn)
	{
	  theSystem->debug("PS.%s::render ENTER %g %g %g\n", name(), x, y, z);

	  d_isActive.set(true);
	  eventOut(timeNow.get(), "isActive", d_isActive);

	  d_enterTime = timeNow;
	  eventOut(timeNow.get(), "enterTime", d_enterTime);
	}

      // Check if viewer has left the box
      else if (wasIn && ! inside)
	{
	  theSystem->debug("PS.%s::render EXIT %g %g %g\n", name(), x, y, z);

	  d_isActive.set(false);
	  eventOut(timeNow.get(), "isActive", d_isActive );

	  d_exitTime = timeNow;
	  eventOut(timeNow.get(), "exitTime", d_exitTime);
	}

      // Check for movement within the box
      if (wasIn || inside)
	{
	  if ( ! FPEQUAL(d_position.x(), x) ||
	       ! FPEQUAL(d_position.y(), y) ||
	       ! FPEQUAL(d_position.z(), z) )
	    {
	      d_position.set( x, y, z );
	      eventOut(timeNow.get(), "position_changed", d_position);
	    }

	  float xyzr[4];
	  viewer->getOrientation( xyzr );
	  if ( ! FPEQUAL(d_orientation.x(), xyzr[0]) ||
	       ! FPEQUAL(d_orientation.y(), xyzr[1]) ||
	       ! FPEQUAL(d_orientation.z(), xyzr[2]) ||
	       ! FPEQUAL(d_orientation.r(), xyzr[3]) )
	    {
	      d_orientation.set( xyzr[0], xyzr[1], xyzr[2], xyzr[3] );
	      eventOut(timeNow.get(), "orientation_changed", d_orientation);
	    }
	}
    }

  else
    clearModified();
}


// Set the value of one of the node fields.

void VrmlNodeProximitySensor::setField(const char *fieldName,
				       const VrmlField &fieldValue)
{
  if TRY_FIELD(center, SFVec3f)
  else if TRY_FIELD(enabled, SFBool)
  else if TRY_FIELD(size, SFVec3f)
  else
    VrmlNodeChild::setField(fieldName, fieldValue);
}

