Cone Chaser

#VRML V2.0 utf8

PROTO ConeChaser [
	      field SFVec3f position 0 0 0
	      field SFVec3f goal 0 0 0
	      field SFVec3f direction 0 0 1
	      field SFVec3f scale 1 1 1
	      field SFFloat speed 1  # meters per second
	      field SFFloat turnLimit 1   # radians per second
	      eventIn SFVec3f set_goal
	     ]
{
   Group {
      children [
	 DEF MOVEME Transform {
	    translation IS position
	    children Transform {
	       translation 0 0 -.5
	       rotation 1 0 0  1.57 
	       scale IS scale
	       children Shape {
		  appearance Appearance { material Material {} }
		  geometry Cone {}
	       }
	    }
	 }

	 # heart sends "regular" events to body for state updates
	 DEF HEART TimeSensor { loop TRUE }

	 # Calculate and update direction and position 
         # when receive "heartbeats".  When new goal is received,
         # calculate a new desired direction, but don't actually change
         # rotation, direction, etc... let beat event do that.
         # Otherwise it will turn faster when the goal is moving.
	 DEF SCRIPT Script {
	    eventIn SFTime beat
	    eventIn SFVec3f set_goal IS set_goal
	    eventOut SFVec3f position_changed  # update own position
	    eventOut SFRotation rotation_changed  # update own rotation
	    field SFVec3f direction IS direction
	    field SFVec3f position IS position
	    field SFVec3f goal IS goal
	    field SFFloat speed IS speed  # meters per second 
	    field SFTime lastBeat 0
	    field SFFloat rotation 0
	    field SFFloat turnLimit IS turnLimit  # max radians per second
	    field SFFloat changeAngle 0  # desired amount to rotate by
	    field SFFloat twopi 6.2832  # so don't keep recalculating
	    field SFBool first TRUE

	    url "vrmlscript:

// clamp val between min and max numbers
function clamp(val, min, max) {
   if(val<min) return min;
   else if(val > max) return max;
   else return val;
}

// Every so often, a script node's eventsProcessed function is called
// after a few events have been received.  Complicated calculations should
// go here.  In this case, calculating the new direction we should be
// travelling.
function eventsProcessed() {
   desiredDirection = goal.subtract(position).normalize();

   // calc rotation from z to be going in desiredDirection
   a = new SFVec3f(0, 0, 1);
   b = desiredDirection;
   adb = a.dot(b);
   desiredRotation = Math.acos(a.dot(b));
   if(b.x < 0) desiredRotation = twopi - desiredRotation;

   // calc angle we need to change to reach desiredRotation
   desiredAngle = desiredRotation - rotation;
   if(desiredAngle > Math.PI) desiredAngle += -twopi;
   else if(desiredAngle < -Math.PI) desiredAngle += twopi;

   // clamp the angle to our turnLimit to slow turn
   changeAngle = clamp(desiredAngle, -turnLimit, turnLimit);
}

function set_goal(val) {
   goal = val;
}

function beat(val) {
   // on first heartbeat, just store time
   if(lastBeat == 0) {
      lastBeat = val;
   }
   else {
      timeElapsed = val - lastBeat;

      position = position.add(direction.multiply(speed*timeElapsed));
      position_changed = position;
      
      // update direction and rotation
      maxAngleThisBeat = turnLimit * timeElapsed;
      angle = clamp(changeAngle, -maxAngleThisBeat, maxAngleThisBeat);
      rotation += angle;
      if (rotation < 0) rotation = rotation + twopi;
      if (rotation > twopi) rotation = rotation - twopi;
      r = new SFRotation(0, 1, 0, rotation);
      rotation_changed = r;
      direction = r.multVec(new SFVec3f(0, 0, 1));
      
      lastBeat = val;
   }
}

"
	 }
	]
   }

   ROUTE HEART.time TO SCRIPT.beat
   ROUTE SCRIPT.position_changed TO MOVEME.translation
   ROUTE SCRIPT.rotation_changed TO MOVEME.rotation
}


mrl