import javax.media.j3d.*;
import javax.vecmath.*;
import java.util.Enumeration;

/**
 * This Behavior can be used to keep looking at a specific object.  
 * @author Marco Lohse
 * @version 0.1
 */
public class LookAtBehavior extends Behavior
{
  // behavior 
  private WakeupCriterion[] wakeupCriterions;
  private WakeupOr          wakeupOr;
  private WakeupOnTransformChange criterion;

  // the watchingTarget will change in response to the 
  // movingTarget´s changes
  protected TransformGroup watchingTarget;
  protected TransformGroup movingTarget;

  // objects for computations
  private Transform3D trans1 = new Transform3D();
  private Transform3D trans2 = new Transform3D();
  private Transform3D trans3 = new Transform3D();
  private Point3d point1 = new Point3d();
  private Point3d point2 = new Point3d();
  private Vector3d translation1 = new Vector3d();
  private Vector3d translation2 = new Vector3d();
  private Vector3d up           = new Vector3d(0, 1, 0);
  
 /**
  * Creates a behavior which will update one TransformGroup´s Transform3D 
  * in response to changes of the other TransformGroup´s Transform3D. 
  * @param t1 this TransformGroup´s Transform3D object will be updated
  * @param t2 this TransformGroup´s Transform3D´s translational component will be used
  * @param bounds bounding volume for this behavior
  */ 
  public LookAtBehavior(TransformGroup t1, TransformGroup t2, Bounds bounds) 
  {
    if(t1.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE) &&
       t1.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)  &&
       t2.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) 
    {
      watchingTarget = t1;
      movingTarget = t2;
  
      wakeupCriterions = new WakeupCriterion[1];
      criterion = new WakeupOnTransformChange(movingTarget);
  
      this.setSchedulingBounds(bounds);
      
      // setup watchingTarget to look at movingTarget
      watchingTarget.getTransform(trans1);
      movingTarget.getTransform(trans2);
      trans1.get(translation1);
      trans2.get(translation2);
      if(!translation1.equals(translation2)) {
        point1.set(translation1);
        point2.set(translation2);
        trans3.lookAt(point1, point2, up);
        trans3.invert();
        watchingTarget.setTransform(trans3);
      }
    }
    else {
      throw new RestrictedAccessException("first parameter needs to have TransformGroup.ALLOW_TRANSFORM_WRITE and TransformGroup.ALLOW_TRANSFORM_READ, second parameter needs TransformGroup.ALLOW_TRANSFORM_READ");
    }
  }

  public void initialize()
  {
    // set initial wakeup condition
    wakeupCriterions[0] = criterion;
    wakeupOr = new WakeupOr(wakeupCriterions);
    this.wakeupOn(wakeupOr);
  }

  // called by Java 3D when appropriate stimulus occures
  public void processStimulus(Enumeration criteria)
  {
    // setup watchingTarget to look at movingTarget
    if(criterion.hasTriggered()) {
      watchingTarget.getTransform(trans1);
      movingTarget.getTransform(trans2);
      trans1.get(translation1);
      trans2.get(translation2);
      if(!translation1.equals(translation2)) {
        point1.set(translation1);
        point2.set(translation2);
        trans3.lookAt(point1, point2, up);
        trans3.invert();
        watchingTarget.setTransform(trans3);
      }
    }

    this.wakeupOn(wakeupOr);
  }
}