#VRML V2.0 utf8 PROTO AvoidConeChaser [ field SFVec3f position 0 0 0 field SFVec3f goal 0 0 0 field SFFloat speed 1 field MFVec3f obstacles [ ] field SFInt32 index 0 field SFVec3f direction 0 0 1 field SFVec3f scale 1 1 1 field SFFloat turnLimit 1 # radians per second eventIn SFVec3f set_goal eventIn MFVec3f obstacles_changed eventOut MFFloat report_position ] { Group { children [ DEF MOVEME Transform { translation IS position children Transform { translation 0 0 -.25 rotation 1 0 0 1.57 scale IS scale children Shape { appearance Appearance { material Material {} } geometry Cone {} } } } DEF HEART TimeSensor { loop TRUE } # update direction and position when receive "heartbeats" DEF SCRIPT Script { eventIn SFTime beat eventIn SFVec3f set_goal IS set_goal eventIn MFVec3f obstacles_changed IS obstacles_changed eventOut SFVec3f position_changed eventOut SFRotation rotation_changed # update own rotation eventOut MFFloat report_position IS report_position field SFInt32 index IS index field SFVec3f direction IS direction field SFVec3f position IS position field SFVec3f goal IS goal field SFFloat speed IS speed field SFTime lastBeat 0 field MFVec3f obstacles IS obstacles field SFFloat repulse 10 field SFFloat attract 2 field SFFloat power 2 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; } function repulseForce(obstacle) { v = position.subtract(obstacle); ods = repulse * 1/Math.pow(v.length(), power); return v.normalize().multiply(ods); } // 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() { // OLD: desiredDirection = goal.subtract(position).normalize(); // NEW: // compute force of attraction towards goal ('Go G-Force!') gforce = goal.subtract(position).normalize().multiply(attract); // compute repulsive force away from obstacles rforce = new SFVec3f(0,0,0); for(i=0; i<obstacles.length; i++) { if(i!=index) { r = repulseForce(obstacles[i]); rforce = rforce.add(r); } } force = gforce.add(rforce); desiredDirection = force.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 calcDirection(t) { // update direction and rotation maxAngleThisBeat = turnLimit * t; 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; x = Math.round(direction.x * 10) / 10; y = Math.round(direction.y * 10) / 10; z = Math.round(direction.z * 10) / 10; direction = r.multVec(new SFVec3f(0, 0, 1)); } function set_goal(val) { goal = val; } function beat(val) { if(first) { // initialize lastBeat on first heartbeat first = FALSE; } else { timeElapsed = val - lastBeat; calcDirection(timeElapsed); position = position.add(direction.multiply(speed*timeElapsed)); position_changed = position; report_position = new MFFloat(index, position.x, position.y, position.z); } lastBeat = val; } function obstacles_changed(val) { obstacles = val; } " } ] } ROUTE HEART.time TO SCRIPT.beat ROUTE SCRIPT.position_changed TO MOVEME.translation ROUTE SCRIPT.rotation_changed TO MOVEME.rotation }