diff --git a/examples/FlockOfbirds.js b/examples/FlockOfbirds.js new file mode 100644 index 0000000000..0e8c6d4731 --- /dev/null +++ b/examples/FlockOfbirds.js @@ -0,0 +1,265 @@ +// +// flockOfbirds.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined +// at the start of the script. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + +// The area over which the birds will fly +var lowerCorner = { x: 1, y: 1, z: 1 }; +var upperCorner = { x: 10, y: 10, z: 10 }; +var STARTING_FRACTION = 0.25; + +var NUM_BIRDS = 50; +var playSounds = true; +var SOUND_PROBABILITY = 0.001; +var numPlaying = 0; +var BIRD_SIZE = 0.08; +var BIRD_MASTER_VOLUME = 0.1; +var FLAP_PROBABILITY = 0.005; +var RANDOM_FLAP_VELOCITY = 1.0; +var FLAP_UP = 1.0; +var BIRD_GRAVITY = -0.5; +var LINEAR_DAMPING = 0.2; +var FLAP_FALLING_PROBABILITY = 0.025; +var MIN_ALIGNMENT_VELOCITY = 0.0; +var MAX_ALIGNMENT_VELOCITY = 1.0; +var VERTICAL_ALIGNMENT_COUPLING = 0.0; +var ALIGNMENT_FORCE = 1.5; +var COHESION_FORCE = 1.0; +var MAX_COHESION_VELOCITY = 0.5; + +var floor = false; +var MAKE_FLOOR = false; + +var averageVelocity = { x: 0, y: 0, z: 0 }; +var averagePosition = { x: 0, y: 0, z: 0 }; + +var birdsLoaded = false; + +var birds = []; +var playing = []; + +function randomVector(scale) { + return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; +} + +function updateBirds(deltaTime) { + if (!Entities.serversExist() || !Entities.canRez()) { + return; + } + if (!birdsLoaded) { + loadBirds(NUM_BIRDS); + birdsLoaded = true; + return; + } + var sumVelocity = { x: 0, y: 0, z: 0 }; + var sumPosition = { x: 0, y: 0, z: 0 }; + var birdPositionsCounted = 0; + var birdVelocitiesCounted = 0; + for (var i = 0; i < birds.length; i++) { + if (birds[i].entityId) { + var properties = Entities.getEntityProperties(birds[i].entityId); + // If Bird has been deleted, bail + if (properties.id != birds[i].entityId) { + birds[i].entityId = false; + return; + } + // Sum up average position and velocity + if (Vec3.length(properties.velocity) > MIN_ALIGNMENT_VELOCITY) { + sumVelocity = Vec3.sum(sumVelocity, properties.velocity); + birdVelocitiesCounted += 1; + } + sumPosition = Vec3.sum(sumPosition, properties.position); + birdPositionsCounted += 1; + + var downwardSpeed = (properties.velocity.y < 0) ? -properties.velocity.y : 0.0; + if ((properties.position.y < upperCorner.y) && (Math.random() < (FLAP_PROBABILITY + (downwardSpeed * FLAP_FALLING_PROBABILITY)))) { + // More likely to flap if falling + var randomVelocity = randomVector(RANDOM_FLAP_VELOCITY); + randomVelocity.y = FLAP_UP + Math.random() * FLAP_UP; + + // Alignment Velocity + var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); + var alignmentVelocity = Vec3.multiply(alignmentVelocityMagnitude, Vec3.normalize(averageVelocity)); + alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; + + // Cohesion + var distanceFromCenter = Vec3.length(Vec3.subtract(averagePosition, properties.position)); + var cohesionVelocitySize = Math.min(distanceFromCenter * COHESION_FORCE, MAX_COHESION_VELOCITY); + var cohesionVelocity = Vec3.multiply(cohesionVelocitySize, Vec3.normalize(Vec3.subtract(averagePosition, properties.position))); + + var newVelocity = Vec3.sum(randomVelocity, Vec3.sum(alignmentVelocity, cohesionVelocity)); + + Entities.editEntity(birds[i].entityId, { velocity: Vec3.sum(properties.velocity, newVelocity) }); + + } + + // Check whether to play a chirp + if (playSounds && (!birds[i].audioId || !birds[i].audioId.isPlaying) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { + var options = { + position: properties.position, + volume: BIRD_MASTER_VOLUME + }; + // Play chirp + if (birds[i].audioId) { + birds[i].audioId.setOptions(options); + birds[i].audioId.restart(); + } else { + birds[i].audioId = Audio.playSound(birds[i].sound, options); + } + numPlaying++; + // Change size + Entities.editEntity(birds[i].entityId, { dimensions: Vec3.multiply(1.5, properties.dimensions)}); + + } else if (birds[i].audioId) { + // If bird is playing a chirp + if (!birds[i].audioId.isPlaying) { + Entities.editEntity(birds[i].entityId, { dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }}); + numPlaying--; + } + } + + // Keep birds in their 'cage' + var bounce = false; + var newVelocity = properties.velocity; + var newPosition = properties.position; + if (properties.position.x < lowerCorner.x) { + newPosition.x = lowerCorner.x; + newVelocity.x *= -1.0; + bounce = true; + } else if (properties.position.x > upperCorner.x) { + newPosition.x = upperCorner.x; + newVelocity.x *= -1.0; + bounce = true; + } + if (properties.position.y < lowerCorner.y) { + newPosition.y = lowerCorner.y; + newVelocity.y *= -1.0; + bounce = true; + } else if (properties.position.y > upperCorner.y) { + newPosition.y = upperCorner.y; + newVelocity.y *= -1.0; + bounce = true; + } + if (properties.position.z < lowerCorner.z) { + newPosition.z = lowerCorner.z; + newVelocity.z *= -1.0; + bounce = true; + } else if (properties.position.z > upperCorner.z) { + newPosition.z = upperCorner.z; + newVelocity.z *= -1.0; + bounce = true; + } + if (bounce) { + Entities.editEntity(birds[i].entityId, { position: newPosition, velocity: newVelocity }); + } + } + } + // Update average velocity and position of flock + if (birdVelocitiesCounted > 0) { + averageVelocity = Vec3.multiply(1.0 / birdVelocitiesCounted, sumVelocity); + //print(Vec3.length(averageVelocity)); + } + if (birdPositionsCounted > 0) { + averagePosition = Vec3.multiply(1.0 / birdPositionsCounted, sumPosition); + } +} + +// Connect a call back that happens every frame +Script.update.connect(updateBirds); + +// Delete our little friends if script is stopped +Script.scriptEnding.connect(function() { + for (var i = 0; i < birds.length; i++) { + Entities.deleteEntity(birds[i].entityId); + } + if (floor) { + Entities.deleteEntity(floor); + } +}); + +function loadBirds(howMany) { + while (!Entities.serversExist() || !Entities.canRez()) { + } + var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"]; + /* Here are more sounds/species you can use + , "mexicanWhipoorwill.raw", + "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", + "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", + "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", + "housewren.wav","hummingbird.wav", "mountainchickadee.wav", "nightjar.wav", "piebilledgrieb.wav", "pygmynuthatch.wav", + "whistlingduck.wav", "woodpecker.wav"]; + */ + + var colors = [ + { red: 242, green: 207, blue: 013 }, + { red: 238, green: 94, blue: 11 }, + { red: 81, green: 30, blue: 7 }, + { red: 195, green: 176, blue: 81 }, + { red: 235, green: 190, blue: 152 }, + { red: 167, green: 99, blue: 52 }, + { red: 199, green: 122, blue: 108 }, + { red: 246, green: 220, blue: 189 }, + { red: 208, green: 145, blue: 65 }, + { red: 173, green: 120 , blue: 71 }, + { red: 132, green: 147, blue: 174 }, + { red: 164, green: 74, blue: 40 }, + { red: 131, green: 127, blue: 134 }, + { red: 209, green: 157, blue: 117 }, + { red: 205, green: 191, blue: 193 }, + { red: 193, green: 154, blue: 118 }, + { red: 205, green: 190, blue: 169 }, + { red: 199, green: 111, blue: 69 }, + { red: 221, green: 223, blue: 228 }, + { red: 115, green: 92, blue: 87 }, + { red: 214, green: 165, blue: 137 }, + { red: 160, green: 124, blue: 33 }, + { red: 117, green: 91, blue: 86 }, + { red: 113, green: 104, blue: 107 }, + { red: 216, green: 153, blue: 99 }, + { red: 242, green: 226, blue: 64 } + ]; + + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; + + for (var i = 0; i < howMany; i++) { + var whichBird = Math.floor(Math.random() * sound_filenames.length); + var position = { + x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, + y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, + z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION + }; + + birds.push({ + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), + entityId: Entities.addEntity({ + type: "Sphere", + position: position, + dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }, + gravity: { x: 0, y: BIRD_GRAVITY, z: 0 }, + velocity: { x: 0, y: -0.1, z: 0 }, + linearDamping: LINEAR_DAMPING, + collisionsWillMove: true, + color: colors[whichBird] + }), + audioId: false, + isPlaying: false + }); + } + if (MAKE_FLOOR) { + var FLOOR_THICKNESS = 0.05; + floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, + y: lowerCorner.y, + z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0 }, + dimensions: { x: (upperCorner.x - lowerCorner.x), y: FLOOR_THICKNESS, z: (upperCorner.z - lowerCorner.z)}, + color: {red: 100, green: 100, blue: 100} + }); + } +} \ No newline at end of file