//
//  Vrml 97 library
//  Copyright (C) 1998 Chris Morley
//
//  %W% %G%
//  VrmlNodeVisibilitySensor.cpp

#include "config.h"
#include "VrmlNodeVisibilitySensor.h"
#include "VrmlNodeType.h"
#include "VrmlNodeNavigationInfo.h"

#include "MathUtils.h"
#include "System.h"
#include "Viewer.h"
#include "VrmlScene.h"

// VisibilitySensor factory. 

static VrmlNode *creator( VrmlScene *scene ) 
{
  return new VrmlNodeVisibilitySensor(scene);
}


// Define the built in VrmlNodeType:: "VisibilitySensor" fields

VrmlNodeType *VrmlNodeVisibilitySensor::defineType(VrmlNodeType *t)
{
  static VrmlNodeType *st = 0;

  if (! t)
    {
      if (st) return st;		// Only define the type once.
      t = st = new VrmlNodeType("VisibilitySensor", 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);

  return t;
}

VrmlNodeType *VrmlNodeVisibilitySensor::nodeType() const { return defineType(0); }


VrmlNodeVisibilitySensor::VrmlNodeVisibilitySensor( 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_enterTime(0.0),
  d_exitTime(0.0)
{
  setModified();
}

VrmlNodeVisibilitySensor::~VrmlNodeVisibilitySensor()
{
}


VrmlNode *VrmlNodeVisibilitySensor::cloneMe() const
{
  return new VrmlNodeVisibilitySensor(*this);
}


ostream& VrmlNodeVisibilitySensor::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 visibility events.
//
// This is in a render() method since the it needs to be computed
// with respect to the accumulated transformations above it in the
// scene graph. Move to update() when xforms are accumulated in Groups...
//

void VrmlNodeVisibilitySensor::render(Viewer *viewer)
{

  if (d_enabled.get())
    {
      VrmlSFTime timeNow( theSystem->time() );
      float xyz[2][3];

      // hack: enclose box in a sphere...
      xyz[0][0] = d_center.x();
      xyz[0][1] = d_center.y();
      xyz[0][2] = d_center.z();
      xyz[1][0] = d_center.x() + d_size.x();
      xyz[1][1] = d_center.y() + d_size.y();
      xyz[1][2] = d_center.z() + d_size.z();
      viewer->transformPoints( 2, &xyz[0][0] );
      float dx = xyz[1][0]-xyz[0][0];
      float dy = xyz[1][1]-xyz[0][1];
      float dz = xyz[1][2]-xyz[0][2];
      float r  = dx*dx + dy*dy + dz*dz;
      if (! FPZERO(r) ) r = sqrt( r );

      // Was the sphere visible last time through? How does this work
      // for USE'd nodes? I need a way for each USE to store whether
      // it was active.
      bool wasIn = d_isActive.get();

      // Is the sphere visible? ...
      bool inside = xyz[0][2] < 0.0; // && z > - scene->visLimit()
      if (inside)
	{
	  VrmlNodeNavigationInfo *ni = d_scene->bindableNavigationInfoTop();
	  if (ni &&
	      ! FPZERO(ni->visibilityLimit()) &&
	      xyz[0][2] < - ni->visibilityLimit())
	    inside = false;
	}

      // This bit assumes 90degree fieldOfView to get rid of trig calls...
      if (inside)
	inside = ( fabs(double(xyz[0][0])) < -0.5 * xyz[0][2] + r &&
		   fabs(double(xyz[0][1])) < -0.5 * xyz[0][2] + r );

      // Just became visible
      if (inside && ! wasIn)
	{
	  theSystem->debug("VS enter %g, %g, %g\n",
			xyz[0][0], xyz[0][1], xyz[0][2]);

	  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("VS exit %g, %g, %g\n",
			xyz[0][0], xyz[0][1], xyz[0][2]);

	  d_isActive.set(false);
	  eventOut(timeNow.get(), "isActive", d_isActive );

	  d_exitTime = timeNow;
	  eventOut(timeNow.get(), "exitTime", d_exitTime);
	}
    }

  else
    clearModified();
}


// Set the value of one of the node fields.

void VrmlNodeVisibilitySensor::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);
}

