From 161e04f1a81471e45b03eff5bab2e00b35178064 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 5 Mar 2014 02:20:03 -0800 Subject: [PATCH] flocking bird JS --- examples/flockingBirds.js | 464 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 examples/flockingBirds.js diff --git a/examples/flockingBirds.js b/examples/flockingBirds.js new file mode 100644 index 0000000000..12b402ab40 --- /dev/null +++ b/examples/flockingBirds.js @@ -0,0 +1,464 @@ +// +// flockingBirds.js +// hifi +// +// Created by Brad Hefta-Gaub on 3/4/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that generates particles that act like flocking birds +// +// All birds, even flying solo... +// birds don't like to fall too fast +// if they fall to fast, they will go into a state of thrusting up, until they reach some max upward velocity, then +// go back to gliding +// birds don't like to be below a certain altitude +// if they are below that altitude they will keep thrusting up, until they get ove + +// flocking +// try to align your velocity with velocity of other birds +// try to fly toward center of flock +// but dont get too close +// + +var birdsInFlock = 40; + +var birdLifetime = 60; // 2 minutes +var count=0; // iterations + +var enableFlyTowardPoints = true; // some birds have a point they want to fly to +var enabledClustedFlyTowardPoints = true; // the flyToward points will be generally near each other +var flyToFrames = 10; // number of frames the bird would like to attempt to fly to it's flyTo point +var PROBABILITY_OF_FLY_TOWARD_CHANGE = 0.01; // chance the bird will decide to change its flyTo point +var PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD = 0.2; // chance the bird will decide to flyTo, otherwise it follows +var flyingToCount = 0; // count of birds currently flying to someplace +var flyToCluster = { }; // the point that the cluster of flyTo points is based on when in enabledClustedFlyTowardPoints + +// Bird behaviors +var enableAvoidDropping = true; // birds will resist falling too fast, and will thrust up if falling +var enableAvoidMinHeight = true; // birds will resist being below a certain height and thrust up to get above it +var enableAvoidMaxHeight = true; // birds will resist being above a certain height and will glide to get below it +var enableMatchFlockVelocity = true; // birds will thrust to match the flocks average velocity +var enableThrustTowardCenter = true; // birds will thrust to try to move toward the center of the flock +var enableAvoidOtherBirds = true; // birds will thrust away from all other birds +var startWithVelocity = true; +var flockGravity = { x: 0, y: -1, z: 0}; + +// NOTE: these two features don't seem to be very interesting, they cause odd behaviors +var enableRandomXZThrust = false; // leading birds randomly decide to thrust in some random direction. +var enableSomeBirdsLead = false; // birds randomly decide not fly toward flock, causing other birds to follow +var leaders = 0; // number of birds leading +var PROBABILITY_TO_LEAD = 0.1; // probabolity a bird will choose to lead + +var birds = new Array(); // array of bird state data + + +var flockStartPosition = { x: 100, y: 10, z: 100}; +var flockStartVelocity = { x: 0, y: 0, z: 0}; +var flockStartThrust = { x: 0, y: 0, z: 0}; // slightly upward against gravity +var INITIAL_XY_VELOCITY_SCALE = 2; +var birdRadius = 0.0625; +var baseBirdColor = { red: 0, green: 255, blue: 255 }; +var glidingColor = { red: 255, green: 0, blue: 0 }; +var thrustUpwardColor = { red: 0, green: 255, blue: 0 }; +var thrustXYColor = { red: 128, green: 0, blue: 128 }; // will be added to any other color +var leadingOrflyToColor = { red: 200, green: 200, blue: 255 }; + +var tooClose = birdRadius * 3; // how close birds are willing to be to each other +var droppingTooFast = -1; // birds don't like to fall too fast +var risingTooFast = 1; // birds don't like to climb too fast +var upwardThrustAgainstGravity = -10; // how hard a bird will attempt to thrust up to avoid downward motion +var droppingAdjustFrames = 5; // birds try to correct their min height in only a couple frames +var minHeight = 10; // meters off the ground +var maxHeight = 50; // meters off the ground +var adjustFrames = 10; // typical number of frames a bird will assume for it's adjustments +var PROBABILITY_OF_RANDOM_XZ_THRUST = 0.25; +var RANDOM_XZ_THRUST_SCALE = 5; // random -SCALE...to...+SCALE in X or Z direction +var MAX_XY_VELOCITY = 10; + +// These are multiplied by every frame since a change. so after 50 frames, there's a 50% probability of change +var PROBABILITY_OF_STARTING_XZ_THRUST = 0.0025; +var PROBABILITY_OF_STOPPING_XZ_THRUST = 0.01; + +var FLY_TOWARD_XZ_DISTANCE = 100; +var FLY_TOWARD_Y_DISTANCE = 0; +var FLY_TOWARD_XZ_CLUSTER_DELTA = 5; +var FLY_TOWARD_Y_CLUSTER_DELTA = 0; + +function createBirds() { + if (enabledClustedFlyTowardPoints) { + flyToCluster = { x: flockStartPosition.x + Math.random() * FLY_TOWARD_XZ_DISTANCE, + y: flockStartPosition.y + Math.random() * FLY_TOWARD_Y_DISTANCE, + z: flockStartPosition.z + Math.random() * FLY_TOWARD_XZ_DISTANCE}; + } + + for(var i =0; i < birdsInFlock; i++) { + var velocity; + + position = Vec3.sum(flockStartPosition, { x: Math.random(), y: Math.random(), z: Math.random() }); // add random + + var flyingToward = position; + var isFlyingToward = false; + if (enableFlyTowardPoints) { + if (Math.random() < PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD) { + flyingToCount++; + isFlyingToward = true; + if (enabledClustedFlyTowardPoints) { + flyingToward = { x: flyToCluster.x + Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA, + y: flyToCluster.y + Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA, + z: flyToCluster.z + Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA}; + } else { + flyingToward = { x: flockStartPosition.x + Math.random() * FLY_TOWARD_XZ_DISTANCE, + y: flockStartPosition.y + Math.random() * FLY_TOWARD_Y_DISTANCE, + z: flockStartPosition.z + Math.random() * FLY_TOWARD_XZ_DISTANCE}; + } + } + + Vec3.print("birds["+i+"].flyingToward=",flyingToward); + } + + birds[i] = { + particle: {}, + properties: {}, + thrust: Vec3.sum(flockStartThrust, { x:0, y: 0, z: 0 }), + gliding: true, + xzThrust: { x:0, y: 0, z: 0}, + xzthrustCount: 0, + isLeading: false, + flyingToward: flyingToward, + isFlyingToward: isFlyingToward, + }; + + if (enableSomeBirdsLead) { + if (Math.random() < PROBABILITY_TO_LEAD) { + birds[i].isLeading = true; + } + if (leaders == 0 && i == (birdsInFlock-1)) { + birds[i].isLeading = true; + } + if (birds[i].isLeading) { + leaders++; + velocity = { x: 2, y: 0, z: 2}; + print(">>>>>>THIS BIRD LEADS!!!! i="+i); + } + } + + if (startWithVelocity) { + velocity = Vec3.sum(flockStartVelocity, { x: (Math.random() * INITIAL_XY_VELOCITY_SCALE), + y: 0, + z: (Math.random() * INITIAL_XY_VELOCITY_SCALE) }); // add random + } else { + velocity = { x: 0, y: 0, z: 0}; + } + birds[i].particle = Particles.addParticle({ + position: position, + velocity: velocity, + gravity: flockGravity, + damping: 0, + radius: birdRadius, + color: baseBirdColor, + lifetime: birdLifetime + }); + + } + print("flyingToCount=" + flyingToCount); +} + +var wantDebug = false; +function updateBirds(deltaTime) { + count++; + + // get all our bird properties, and calculate the current flock velocity + var averageVelocity = { x: 0, y: 0, z: 0}; + var averagePosition = { x: 0, y: 0, z: 0}; + var knownBirds = 0; + for(var i =0; i < birdsInFlock; i++) { + // identifyParticle() will check to see that the particle handle we have is in sync with the domain/server + // context. If the handle is for a created particle that now has a known ID it will be updated to be a + // handle with a known ID. + birds[i].particle = Particles.identifyParticle(birds[i].particle); + + if (birds[i].particle.isKnownID) { + birds[i].properties = Particles.getParticleProperties(birds[i].particle); + if (birds[i].properties.isKnownID) { + knownBirds++; + averageVelocity = Vec3.sum(averageVelocity, birds[i].properties.velocity); + averagePosition = Vec3.sum(averagePosition, birds[i].properties.position); + } + } + } + + if (knownBirds == 0 && count > 100) { + Script.stop(); + return; + } + averageVelocity = Vec3.multiply(averageVelocity, (1 / Math.max(1, knownBirds))); + averagePosition = Vec3.multiply(averagePosition, (1 / Math.max(1, knownBirds))); + + if (wantDebug) { + Vec3.print("averagePosition=",averagePosition); + Vec3.print("averageVelocity=",averageVelocity); + print("knownBirds="+knownBirds); + } + + var flyToClusterChanged = false; + if (enabledClustedFlyTowardPoints) { + if (Math.random() < PROBABILITY_OF_FLY_TOWARD_CHANGE) { + flyToClusterChanged = true; + flyToCluster = { x: averagePosition.x + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2, + y: averagePosition.y + (Math.random() * FLY_TOWARD_Y_DISTANCE) - FLY_TOWARD_Y_DISTANCE/2, + z: averagePosition.z + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2}; + } + } + + // iterate all birds again, adjust their thrust for various goals + for(var i =0; i < birdsInFlock; i++) { + + birds[i].thrust = { x: 0, y: 0, z: 0 }; // assume no thrust... + + if (birds[i].particle.isKnownID) { + + if (enableFlyTowardPoints) { + // if we're flying toward clusters, and the cluster changed, and this bird is flyingToward + // then we need to update it's flyingToward + if (enabledClustedFlyTowardPoints && flyToClusterChanged && birds[i].isFlyingToward) { + flyingToward = { x: flyToCluster.x + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2, + y: flyToCluster.y + (Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA) - FLY_TOWARD_Y_CLUSTER_DELTA/2, + z: flyToCluster.z + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2}; + birds[i].flyingToward = flyingToward; + } + + // there a random chance this bird will decide to change it's flying toward state + if (Math.random() < PROBABILITY_OF_FLY_TOWARD_CHANGE) { + var wasFlyingTo = birds[i].isFlyingToward; + + // there's some chance it will decide it should be flying toward + if (Math.random() < PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD) { + + // if we're flying toward clustered points, then we randomize from the cluster, otherwise we pick + // completely random places based on flocks current averagePosition + if (enabledClustedFlyTowardPoints) { + flyingToward = { x: flyToCluster.x + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2, + y: flyToCluster.y + (Math.random() * FLY_TOWARD_Y_CLUSTER_DELTA) - FLY_TOWARD_Y_CLUSTER_DELTA/2, + z: flyToCluster.z + (Math.random() * FLY_TOWARD_XZ_CLUSTER_DELTA) - FLY_TOWARD_XZ_CLUSTER_DELTA/2}; + } else { + flyingToward = { x: averagePosition.x + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2, + y: averagePosition.y + (Math.random() * FLY_TOWARD_Y_DISTANCE) - FLY_TOWARD_Y_DISTANCE/2, + z: averagePosition.z + (Math.random() * FLY_TOWARD_XZ_DISTANCE) - FLY_TOWARD_XZ_DISTANCE/2}; + } + birds[i].flyingToward = flyingToward; + birds[i].isFlyingToward = true; + } else { + birds[i].flyingToward = {}; + birds[i].isFlyingToward = false; + } + + // keep track of our bookkeeping + if (!wasFlyingTo && birds[i].isFlyingToward) { + flyingToCount++; + } + if (wasFlyingTo && !birds[i].isFlyingToward) { + flyingToCount--; + } + print(">>>> CHANGING flyingToCount="+flyingToCount); + if (birds[i].isFlyingToward) { + Vec3.print("... now birds["+i+"].flyingToward=", birds[i].flyingToward); + } + } + + // actually apply the thrust after all that + if (birds[i].isFlyingToward) { + var flyTowardDelta = Vec3.subtract(birds[i].flyingToward, birds[i].properties.position); + var thrustTowardFlyTo = Vec3.multiply(flyTowardDelta, 1/flyToFrames); + birds[i].thrust = Vec3.sum(birds[i].thrust, thrustTowardFlyTo); + } + } + + + // adjust thrust to avoid dropping to fast + if (enableAvoidDropping) { + if (birds[i].gliding) { + if (birds[i].properties.velocity.y < droppingTooFast) { + birds[i].gliding = false; // leave thrusting against gravity till it gets too high + //print("birdGliding["+i+"]=false <<<< try to conteract gravity <<<<<<<<<<<<<<<<<<<<"); + } + } + } + + // if the bird is currently not gliding, check to see if it's rising too fast + if (!birds[i].gliding && birds[i].properties.velocity.y > risingTooFast) { + //Vec3.print("bird rising too fast will glide bird["+i+"]=",birds[i].properties.velocity.y); + birds[i].gliding = true; + } + + // adjust thrust to avoid minHeight, we don't care about rising too fast in this case, so we do it + // after the rising too fast check + if (enableAvoidMinHeight) { + if (birds[i].properties.position.y < minHeight) { + //Vec3.print("**** enableAvoidMinHeight... enable thrust against gravity... bird["+i+"].position=",birds[i].properties.position); + birds[i].gliding = false; + } + } + + // adjust thrust to avoid maxHeight + if (enableAvoidMaxHeight) { + if (birds[i].properties.position.y > maxHeight) { + //Vec3.print("********************* bird above max height will glide bird["+i+"].position=",birds[i].properties.position); + birds[i].gliding = true; + } + } + + // if the bird is currently not gliding, then it is applying a thrust upward against gravity + if (!birds[i].gliding) { + // as long as we're not rising too fast, keep thrusting... + if (birds[i].properties.velocity.y < risingTooFast) { + var thrustAdjust = {x: 0, y: (flockGravity.y * upwardThrustAgainstGravity), z: 0}; + //Vec3.print("bird fighting gravity thrustAdjust for bird["+i+"]=",thrustAdjust); + birds[i].thrust = Vec3.sum(birds[i].thrust, thrustAdjust); + } else { + //print("%%% non-gliding bird, thrusting too much..."); + } + } + + if (enableRandomXZThrust && birds[i].isLeading) { + birds[i].xzThrustCount++; + + // we will randomly decide to enable XY thrust, in which case we will set the thrust and leave it + // that way till we randomly shut it off. + + // if we don't have a thrust, check against probability of starting it, and create a random thrust if + // probability occurs + if (Vec3.length(birds[i].xzThrust) == 0) { + var probabilityToStart = (PROBABILITY_OF_STARTING_XZ_THRUST * birds[i].xzThrustCount); + //print("probabilityToStart=" + probabilityToStart); + if (Math.random() < probabilityToStart) { + var xThrust = (Math.random() * (RANDOM_XZ_THRUST_SCALE * 2)) - RANDOM_XZ_THRUST_SCALE; + var zThrust = (Math.random() * (RANDOM_XZ_THRUST_SCALE * 2)) - RANDOM_XZ_THRUST_SCALE; + + birds[i].xzThrust = { x: zThrust, y: 0, z: zThrust }; + birds[i].xzThrustCount = 0; + //Vec3.print(">>>>>>>>>> STARTING XY THRUST birdXYthrust["+i+"]=", birds[i].xzThrust); + } + } + + // if we're thrusting... then check for probability of stopping + if (Vec3.length(birds[i].xzThrust)) { + var probabilityToStop = (PROBABILITY_OF_STOPPING_XZ_THRUST * birds[i].xzThrustCount); + //print("probabilityToStop=" + probabilityToStop); + if (Math.random() < probabilityToStop) { + birds[i].xzThrust = { x: 0, y: 0, z: 0}; + //Vec3.print(">>>>>>>>>> STOPPING XY THRUST birdXYthrust["+i+"]=", birds[i].xzThrust); + birds[i].xzThrustCount = 0; + } + + if (birds[i].properties.velocity.x > MAX_XY_VELOCITY) { + birds[i].xzThrust.x = 0; + //Vec3.print(">>>>>>>>>> CLEARING X THRUST birdXYthrust["+i+"]=", birds[i].xzThrust); + } + if (birds[i].properties.velocity.z > MAX_XY_VELOCITY) { + birds[i].xzThrust.z = 0; + //Vec3.print(">>>>>>>>>> CLEARING Y THRUST birdXYthrust["+i+"]=", birds[i].xzThrust); + } + + if (Vec3.length(birds[i].xzThrust)) { + birds[i].thrust = Vec3.sum(birds[i].thrust, birds[i].xzThrust); + } + } + } + + // adjust thrust to move their velocity toward average flock velocity + if (enableMatchFlockVelocity) { + if (birds[i].isLeading) { + print("this bird is leading... i="+i); + } else { + var velocityDelta = Vec3.subtract(averageVelocity, birds[i].properties.velocity); + var thrustAdjust = velocityDelta; + birds[i].thrust = Vec3.sum(birds[i].thrust, thrustAdjust); + } + } + + // adjust thrust to move their velocity toward average flock position + if (enableThrustTowardCenter) { + if (birds[i].isLeading) { + print("this bird is leading... i="+i); + } else { + var positionDelta = Vec3.subtract(averagePosition, birds[i].properties.position); + var thrustTowardCenter = Vec3.multiply(positionDelta, 1/adjustFrames); + birds[i].thrust = Vec3.sum(birds[i].thrust, thrustTowardCenter); + } + } + + + // adjust thrust to avoid other birds + if (enableAvoidOtherBirds) { + var sumThrustThisBird = { x: 0, y: 0, z: 0 }; + + for(var j =0; j < birdsInFlock; j++) { + + // if this is not me, and a known bird, then check our position + if (birds[i].properties.isKnownID && j != i) { + var positionMe = birds[i].properties.position; + var positionYou = birds[j].properties.position; + var awayFromYou = Vec3.subtract(positionMe, positionYou); // vector pointing away from "you" + var distance = Vec3.length(awayFromYou); + if (distance < tooClose) { + // NOTE: this was Philip's recommendation for "avoiding" other birds... + // Vme -= Vec3.multiply(Vme, normalize(PositionMe - PositionYou)) + // + // But it doesn't seem to work... Here's my JS implementation... + // + // var velocityMe = birds[i].properties.velocity; + // var thrustAdjust = Vec3.cross(velocityMe, Vec3.normalize(awayFromYou)); + // sumThrustThisBird = Vec3.sum(sumThrustThisBird, thrustAdjust); + // + // Instead I just apply a thrust equal to the vector away from all the birds + sumThrustThisBird = Vec3.sum(sumThrustThisBird, awayFromYou); + } + } + } + birds[i].thrust = Vec3.sum(birds[i].thrust, sumThrustThisBird); + } + } + } + + + // iterate all birds again, apply their thrust + for(var i =0; i < birdsInFlock; i++) { + if (birds[i].particle.isKnownID) { + + var color; + if (birds[i].gliding) { + color = glidingColor; + } else { + color = thrustUpwardColor; + } + if (Vec3.length(birds[i].xzThrust)) { + color = Vec3.sum(color, thrustXYColor); + } + + var velocityMe = birds[i].properties.velocity; + // add thrust to velocity + var newVelocity = Vec3.sum(velocityMe, Vec3.multiply(birds[i].thrust, deltaTime)); + + if (birds[i].isLeading || birds[i].isFlyingToward) { + Vec3.print("this bird is leading/flying toward... i="+i+" velocity=",newVelocity); + color = leadingOrflyToColor; + } + + if (wantDebug) { + Vec3.print("birds["+i+"].position=", birds[i].properties.position); + Vec3.print("birds["+i+"].oldVelocity=", velocityMe); + Vec3.print("birdThrusts["+i+"]=", birds[i].thrust); + Vec3.print("birds["+i+"].newVelocity=", newVelocity); + } + + birds[i].particle = Particles.editParticle(birds[i].particle,{ velocity: newVelocity, color: color }); + + } + } +} + + +createBirds(); +// register the call back for simulation loop +Script.update.connect(updateBirds); +