#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
}