From 472dd076252b32b86a6385a250439f815b7bfdd9 Mon Sep 17 00:00:00 2001 From: PhilipRosedale Date: Tue, 22 Mar 2016 22:39:22 -0700 Subject: [PATCH] airship from hack project --- examples/airship/airship.js | 303 ++++++++++++++++++++++++++++++++ examples/airship/makeAirship.js | 60 +++++++ 2 files changed, 363 insertions(+) create mode 100644 examples/airship/airship.js create mode 100644 examples/airship/makeAirship.js diff --git a/examples/airship/airship.js b/examples/airship/airship.js new file mode 100644 index 0000000000..9b6ae45093 --- /dev/null +++ b/examples/airship/airship.js @@ -0,0 +1,303 @@ +// +// airship.js +// +// Animates a pirate airship that chases people and shoots cannonballs at them +// +// Created by Philip Rosedale on March 7, 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + var entityID, + minimumDelay = 100, // milliseconds + distanceScale = 2.0, // distance at which 100% chance of hopping + timeScale = 300.0, // crash + hopStrength = 0.4, // meters / second + spotlight = null, + wantDebug = false, + timeoutID = undefined, + bullet = null, + particles = null, + nearbyAvatars = 0, + nearbyAvatarsInRange = 0, + averageAvatarLocation = { x: 0, y: 0, z: 0 }, + properties, + lightTimer = 0, + lightTimeoutID = undefined, + audioInjector = null + + var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + var cannonSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/cannonShot.wav"); + var explosionSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/explosion.wav"); + + var NO_SHOOT_COLOR = { red: 100, green: 100, blue: 100 }; + + var MAX_TARGET_RANGE = 200; + + function printDebug(message) { + if (wantDebug) { + print(message); + } + } + + var LIGHT_UPDATE_INTERVAL = 50; + var LIGHT_LIFETIME = 700; + + function randomVector(size) { + return { x: (Math.random() - 0.5) * size, + y: (Math.random() - 0.5) * size, + z: (Math.random() - 0.5) * size }; + } + + function makeLight(parent, position, colorDivisor) { + // Create a flickering light somewhere for a while + if (spotlight !== null) { + // light still exists, do nothing + printDebug("light still there"); + return; + } + printDebug("making light"); + + var colorIndex = 180 + Math.random() * 50; + + spotlight = Entities.addEntity({ + type: "Light", + name: "Test Light", + intensity: 50.0, + falloffRadius: 20.0, + dimensions: { + x: 150, + y: 150, + z: 150 + }, + position: position, + parentID: parent, + color: { + red: colorIndex, + green: colorIndex / colorDivisor, + blue: 0 + }, + lifetime: LIGHT_LIFETIME * 2 + }); + + lightTimer = 0; + lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL); + + }; + + function updateLight() { + lightTimer += LIGHT_UPDATE_INTERVAL; + if ((spotlight !== null) && (lightTimer > LIGHT_LIFETIME)) { + printDebug("deleting light!"); + Entities.deleteEntity(spotlight); + spotlight = null; + } else { + Entities.editEntity(spotlight, { + intensity: 5 + Math.random() * 50, + falloffRadius: 5 + Math.random() * 10 + }); + lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL); + } + } + + function move() { + + var HOVER_DISTANCE = 30.0; + var RUN_TOWARD_STRENGTH = 0.002; + var VELOCITY_FOLLOW_RATE = 0.01; + + var range = Vec3.distance(properties.position, averageAvatarLocation); + var impulse = { x: 0, y: 0, z: 0 }; + + // move in the XZ plane + var away = Vec3.subtract(properties.position, averageAvatarLocation); + away.y = 0.0; + + if (range > HOVER_DISTANCE) { + impulse = Vec3.multiply(-RUN_TOWARD_STRENGTH, Vec3.normalize(away)); + } + + var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, properties.velocity); + Entities.editEntity(entityID, {velocity: Vec3.sum(properties.velocity, impulse), rotation: Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE)}); + } + + + function countAvatars() { + var workQueue = AvatarList.getAvatarIdentifiers(); + var averageLocation = {x: 0, y: 0, z: 0}; + var summed = 0; + var inRange = 0; + for (var i = 0; i < workQueue.length; i++) { + var avatar = AvatarList.getAvatar(workQueue[i]), avatarPosition = avatar && avatar.position; + if (avatarPosition) { + averageLocation = Vec3.sum(averageLocation, avatarPosition); + summed++; + if (Vec3.distance(avatarPosition, properties.position) < MAX_TARGET_RANGE) { + inRange++; + } + } + } + if (summed > 0) { + averageLocation = Vec3.multiply(1 / summed, averageLocation); + } + nearbyAvatars = summed; + nearbyAvatarsInRange = inRange; + averageAvatarLocation = averageLocation; + + //printDebug(" Avatars: " + summed + "in range: " + nearbyAvatarsInRange); + //Vec3.print(" location: ", averageAvatarLocation); + + return; + } + + function shoot() { + if (bullet !== null) { + return; + } + if (Vec3.distance(MyAvatar.position, properties.position) > MAX_TARGET_RANGE) { + return; + } + + var SPEED = 7.5; + var GRAVITY = -2.0; + var DIAMETER = 0.5; + var direction = Vec3.subtract(MyAvatar.position, properties.position); + var range = Vec3.distance(MyAvatar.position, properties.position); + var timeOfFlight = range / SPEED; + + var fall = 0.5 * -GRAVITY * (timeOfFlight * timeOfFlight); + + var velocity = Vec3.multiply(SPEED, Vec3.normalize(direction)); + velocity.y += 0.5 * fall / timeOfFlight * 2.0; + + var DISTANCE_TO_DECK = 3; + var bulletStart = properties.position; + bulletStart.y -= DISTANCE_TO_DECK; + + makeLight(entityID, Vec3.sum(properties.position, randomVector(10.0)), 2); + + bullet = Entities.addEntity({ + type: "Sphere", + name: "cannonball", + position: properties.position, + dimensions: { x: DIAMETER, y: DIAMETER, z: DIAMETER }, + color: { red: 10, green: 10, blue: 10 }, + velocity: velocity, + damping: 0.01, + dynamic: true, + ignoreForCollisions: true, + gravity: { x: 0, y: GRAVITY, z: 0 }, + lifetime: timeOfFlight * 2 + }); + + Audio.playSound(cannonSound, { + position: Vec3.sum(MyAvatar.position, velocity), + volume: 1.0 + }); + makeParticles(properties.position, bullet, timeOfFlight * 2); + Script.setTimeout(explode, timeOfFlight * 1000); + + + } + + function explode() { + var properties = Entities.getEntityProperties(bullet); + var direction = Vec3.normalize(Vec3.subtract(MyAvatar.position, properties.position)); + makeLight(null, properties.position, 10); + Audio.playSound(explosionSound, { position: Vec3.sum(MyAvatar.position, direction), volume: 1.0 }); + bullet = null; + } + + + function maybe() { // every user checks their distance and tries to claim if close enough. + var PROBABILITY_OF_SHOOT = 0.015; + properties = Entities.getEntityProperties(entityID); + countAvatars(); + + if (nearbyAvatars && (Math.random() < 1 / nearbyAvatars)) { + move(); + } + + if (nearbyAvatarsInRange && (Math.random() < PROBABILITY_OF_SHOOT / nearbyAvatarsInRange)) { + shoot(); + } + + var timeToNextCheck = 33; + timeoutID = Script.setTimeout(maybe, timeToNextCheck); + } + + this.preload = function (givenEntityID) { + printDebug("preload airship v1..."); + + entityID = givenEntityID; + properties = Entities.getEntityProperties(entityID); + timeoutID = Script.setTimeout(maybe, minimumDelay); + }; + + this.unload = function () { + printDebug("unload airship..."); + if (timeoutID !== undefined) { + Script.clearTimeout(timeoutID); + } + if (lightTimeoutID !== undefined) { + Script.clearTimeout(lightTimeoutID); + } + if (spotlight !== null) { + Entities.deleteEntity(spotlight); + } + }; + + function makeParticles(position, parent, lifespan) { + particles = Entities.addEntity({ + type: 'ParticleEffect', + position: position, + parentID: parent, + color: { + red: 70, + green: 70, + blue: 70 + }, + isEmitting: 1, + maxParticles: 1000, + lifetime: lifespan, + lifespan: lifespan / 3, + emitRate: 80, + emitSpeed: 0, + speedSpread: 1.0, + emitRadiusStart: 1, + polarStart: -Math.PI/8, + polarFinish: Math.PI/8, + azimuthStart: -Math.PI/4, + azimuthFinish: Math.PI/4, + emitAcceleration: { x: 0, y: 0, z: 0 }, + particleRadius: 0.25, + radiusSpread: 0.1, + radiusStart: 0.3, + radiusFinish: 0.15, + colorSpread: { + red: 100, + green: 100, + blue: 0 + }, + colorStart: { + red: 125, + green: 125, + blue: 125 + }, + colorFinish: { + red: 10, + green: 10, + blue: 10 + }, + alpha: 0.5, + alphaSpread: 0, + alphaStart: 1, + alphaFinish: 0, + emitterShouldTrail: true, + textures: 'https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png' + }); + } +}) diff --git a/examples/airship/makeAirship.js b/examples/airship/makeAirship.js new file mode 100644 index 0000000000..92fff090ee --- /dev/null +++ b/examples/airship/makeAirship.js @@ -0,0 +1,60 @@ +// makeAirship.js +// +// // Created by Philip Rosedale on March 7, 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// A firefly which is animated by passerbys. It's physical, no gravity, periodic forces applied. +// If a firefly is found to + +var SIZE = 0.2; +var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere" + +var MODEL_URL = "https://s3.amazonaws.com/hifi-public/philip/airship_compact.fbx" + +var MODEL_DIMENSION = { x: 19.257, y: 24.094, z: 40.3122 }; +//var ENTITY_URL = "https://s3.amazonaws.com/hifi-public/scripts/airship/airship.js?"+Math.random(); +var ENTITY_URL = "file:///c:/users/dev/philip/examples/airship/airship.js?"+Math.random(); + +var LIFETIME = 3600 * 48; + +var GRAVITY = { x: 0, y: 0, z: 0 }; +var DAMPING = 0.05; +var ANGULAR_DAMPING = 0.01; + +var collidable = true; +var gravity = true; + +var HOW_FAR_IN_FRONT_OF_ME = 30; +var HOW_FAR_ABOVE_ME = 15; + +var leaveBehind = true; + +var shipLocation = Vec3.sum(MyAvatar.position, Vec3.multiply(HOW_FAR_IN_FRONT_OF_ME, Quat.getFront(Camera.orientation))); +shipLocation.y += HOW_FAR_ABOVE_ME; + + +var airship = Entities.addEntity({ + type: TYPE, + modelURL: MODEL_URL, + name: "airship", + position: shipLocation, + dimensions: (TYPE == "Model") ? MODEL_DIMENSION : { x: SIZE, y: SIZE, z: SIZE }, + damping: DAMPING, + angularDamping: ANGULAR_DAMPING, + gravity: (gravity ? GRAVITY : { x: 0, y: 0, z: 0}), + dynamic: collidable, + lifetime: LIFETIME, + animation: {url: MODEL_URL, running: true, currentFrame: 0, loop: true}, + script: ENTITY_URL + }); + +function scriptEnding() { + if (!leaveBehind) { + Entities.deleteEntity(airship); + } +} + +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file