Critter

#VRML V2.0 utf8

PROTO Critter [
	       field SFVec3f position 0 0 0
	       field SFFloat speed 1
	       field SFVec3f obstacle 0 0 1
	       eventIn SFVec3f set_obstacle
              ]
{
   Group {
      children [
	 DEF MOVEME Transform {
	    translation IS position
	    children Shape {
	       appearance Appearance { material DEF MAT Material {} }
	       geometry Box { size .5 .5 .5 }
	    }
	 }

	 DEF HEART TimeSensor { loop TRUE }

	 # update direction and position when receive "heartbeats"
	 DEF SCRIPT Script {
	    eventIn SFTime beat
	    eventIn SFVec3f set_obstacle IS set_obstacle
	    eventIn SFBool touched
	    eventOut SFVec3f position_changed
	    eventOut SFColor color_changed
	    field SFVec3f direction 0 0 1
	    field SFVec3f position IS position
	    field SFVec3f goal 0 0 0
	    field SFFloat speed IS speed
	    field SFTime lastBeat 0
	    field SFVec3f obstacle IS obstacle
	    field SFVec3f prevObstacle IS obstacle
	    field SFBool first TRUE
	    field SFFloat repulse 5
	    field SFFloat attract 1
	    field SFFloat power 1
	    field SFInt32 touches 0
	    field SFBool afraid TRUE  # if not afraid, then curious
	    field SFBool asleep FALSE
	    field SFBool angry FALSE
	    url "vrmlscript:

function touched(val) {
   if(val) { 
      asleep = FALSE;
      ++touches; 
      color_changed = new SFColor(Math.random(), Math.random(), Math.random());
   }
   if(touches > 2) { 
      if(afraid) { afraid = FALSE; }
      else { afraid = TRUE; }
      touches = 0;
   }
}

function repulseForce() {
   v = position.subtract(obstacle);
   ods = repulse * 1/Math.pow(v.length(), power);
   return v.normalize().multiply(ods);
}

function calcDirection() {
   // compute force of attraction towards goal
   gforce = goal.subtract(position).normalize().multiply(attract);
   rforce = repulseForce();
   if(!afraid) { rforce = rforce.multiply(-1); }
   force = gforce.add(rforce);
   direction = force.normalize();
}

function set_goal(val) {
   goal = val;
   // calcDirection();
}

function beat(val) {
   if(first) { // initialize lastBeat on first heartbeat
      lastBeat = val; 
      first = FALSE;
   }
   else {
      timeElapsed = val - lastBeat;
      calcDirection();
      if(!asleep) {
	 position = position.add(direction.multiply(speed*timeElapsed));
	 position_changed = position;
      }

      // occasionally wake or fall asleep at random
      if(Math.random() < .003) {
	 if(asleep) { // wakeup!
	    asleep = FALSE; 
	    color_changed = new SFColor(Math.random(), Math.random(), Math.random());
	 }
	 else { // go to sleep
	    asleep = TRUE; 
	    color_changed = new SFColor(.1, .1, .1); 
	 }
      }

      lastBeat = val;
   }
}

function set_obstacle(val) {
   obstacle = val;
}

"
	 }

        DEF TOUCH TouchSensor {}
	]
   }

   ROUTE HEART.time TO SCRIPT.beat
   ROUTE SCRIPT.position_changed TO MOVEME.translation
   ROUTE TOUCH.isActive TO SCRIPT.touched
   ROUTE SCRIPT.color_changed TO MAT.diffuseColor
}


mrl