diff --git a/examples/toys/bubblewand/bubble.js b/examples/toys/bubblewand/bubble.js new file mode 100644 index 0000000000..ae673b994e --- /dev/null +++ b/examples/toys/bubblewand/bubble.js @@ -0,0 +1,39 @@ +(function() { + + var popSound; + this.preload = function(entityID) { + // console.log('bubble preload') + this.entityID = entityID; + popSound = SoundCache.getSound("http://hifi-public.s3.amazonaws.com/james/bubblewand/sounds/pop.wav"); + + } + + this.collisionWithEntity = function(myID, otherID, collision) { + + Entities.deleteEntity(myID); + this.burstBubbleSound(collision.contactPoint) + + }; + + this.unload = function(entityID) { + // console.log('bubble unload') + // this.properties = Entities.getEntityProperties(entityID); + //var location = this.properties.position; + //this.burstBubbleSound(); + }; + + + + this.burstBubbleSound = function(location) { + + var audioOptions = { + volume: 0.5, + position: location + } + + //Audio.playSound(popSound, audioOptions); + + } + + +}) \ No newline at end of file diff --git a/examples/toys/bubblewand/wand.js b/examples/toys/bubblewand/wand.js new file mode 100644 index 0000000000..3e16fa5e08 --- /dev/null +++ b/examples/toys/bubblewand/wand.js @@ -0,0 +1,343 @@ +// wand.js +// part of bubblewand +// +// Created by James B. Pollack @imgntn -- 09/03/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Makes bubbles when you wave the object around, or hold it near your mouth and make noise into the microphone. +// +// For the example, it's attached to a wand -- but you can attach it to whatever entity you want. I dream of BubbleBees :) bzzzz...pop! +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +function randInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; +} + + +function convertRange(value, r1, r2) { + return (value - r1[0]) * (r2[1] - r2[0]) / (r1[1] - r1[0]) + r2[0]; +} + +// helpers -- @zappoman +// Computes the penetration between a point and a sphere (centered at the origin) +// if point is inside sphere: returns true and stores the result in 'penetration' +// (the vector that would move the point outside the sphere) +// otherwise returns false +function findSphereHit(point, sphereRadius) { + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var vectorLength = Vec3.length(point); + if (vectorLength < EPSILON) { + return true; + } + var distance = vectorLength - sphereRadius; + if (distance < 0.0) { + return true; + } + return false; +} + +function findSpherePointHit(sphereCenter, sphereRadius, point) { + return findSphereHit(Vec3.subtract(point, sphereCenter), sphereRadius); +} + +function findSphereSphereHit(firstCenter, firstRadius, secondCenter, secondRadius) { + return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter); +} + +(function() { + var console = {}; + console.log = function(p) { + if (arguments.length > 1) { + + for (var i = 1; i < arguments.length; i++) { + print(arguments[i]) + } + + } else { + print(p) + } + + } + + var bubbleModel = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/bubble/bubble.fbx"; + var bubbleScript = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/scripts/bubble.js?' + randInt(2, 5096); + var popSound = SoundCache.getSound("http://hifi-public.s3.amazonaws.com/james/bubblewand/sounds/pop.wav"); + var wandModel = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx"; + + + + var targetSize = 0.4; + var targetColor = { + red: 128, + green: 128, + blue: 128 + }; + var targetColorHit = { + red: 0, + green: 255, + blue: 0 + }; + var moveCycleColor = { + red: 255, + green: 255, + blue: 0 + }; + + var handSize = 0.25; + var leftCubePosition = MyAvatar.getLeftPalmPosition(); + var rightCubePosition = MyAvatar.getRightPalmPosition(); + + var RIGHT_FRONT = 512; + + var leftHand = Overlays.addOverlay("cube", { + position: leftCubePosition, + size: handSize, + color: { + red: 0, + green: 0, + blue: 255 + }, + alpha: 1, + solid: false + }); + + var rightHand = Overlays.addOverlay("cube", { + position: rightCubePosition, + size: handSize, + color: { + red: 255, + green: 0, + blue: 0 + }, + alpha: 1, + solid: false + }); + + var gustZoneOverlay = Overlays.addOverlay("cube", { + position: getGustDetectorPosition(), + size: targetSize, + color: targetColor, + alpha: 1, + solid: false + }); + + + function getGustDetectorPosition() { + var DISTANCE_IN_FRONT = 0.2; + var DISTANCE_UP = 0.5; + var DISTANCE_TO_SIDE = 0.0; + + var up = Quat.getUp(MyAvatar.orientation); + var front = Quat.getFront(MyAvatar.orientation); + var right = Quat.getRight(MyAvatar.orientation); + + var upOffset = Vec3.multiply(up, DISTANCE_UP); + var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE); + var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT); + + var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), upOffset); + var position = Vec3.sum(MyAvatar.position, offset); + return position; + } + + + var BUBBLE_GRAVITY = { + x: 0, + y: -0.05, + z: 0 + } + + + var thisEntity = this; + + this.preload = function(entityID) { + print('PRELOAD') + this.entityID = entityID; + this.properties = Entities.getEntityProperties(this.entityID); + BubbleWand.lastPosition = this.properties.position; + } + + this.unload = function(entityID) { + Overlays.deleteOverlay(leftHand); + Overlays.deleteOverlay(rightHand); + Overlays.deleteOverlay(rightFront) + Entities.editEntity(entityID, { + name: "" + }); + Script.update.disconnect(BubbleWand.update); + collectGarbage(); + }; + + + var BubbleWand = { + bubbles: [], + currentBubble: null, + update: function() { + BubbleWand.updateControllerState(); + }, + updateControllerState: function() { + var _t = this; + var properties = Entities.getEntityProperties(thisEntity.entityID); + var wandPosition = properties.position; + + var leftHandPos = MyAvatar.getLeftPalmPosition(); + var rightHandPos = MyAvatar.getRightPalmPosition(); + + Overlays.editOverlay(leftHand, { + position: leftHandPos + }); + Overlays.editOverlay(rightHand, { + position: rightHandPos + }); + + //not really a sphere... + var hitTargetWithWand = findSphereSphereHit(wandPosition, handSize / 2, getGustDetectorPosition(), targetSize / 2) + + var mouthMode; + if (hitTargetWithWand) { + Overlays.editOverlay(gustZoneOverlay, { + position: getGustDetectorPosition(), + color: targetColorHit + }) + mouthMode = true; + + } else { + Overlays.editOverlay(gustZoneOverlay, { + position: getGustDetectorPosition(), + color: targetColor + }) + mouthMode=false; + } + + var velocity = Vec3.subtract(wandPosition,BubbleWand.lastPosition) + + + + _t.lastPosition = wandPosition; + + //print('VELOCITY:::'+JSON.stringify(velocity)) + var velocityStrength = Vec3.length(velocity) *100; + print('velocityStrength::' + velocityStrength); + //todo: angular velocity without the controller + // var angularVelocity = Controller.getSpatialControlRawAngularVelocity(hands.leftHand.tip); + var dimensions = Entities.getEntityProperties(_t.currentBubble).dimensions; + + var volumeLevel = MyAvatar.audioAverageLoudness; + var convertedVolume = convertRange(volumeLevel, [0, 5000], [0, 10]); + // print('CONVERTED VOLUME:' + convertedVolume); + + var growthFactor = convertedVolume + velocityStrength; + // print('growthFactor::' + growthFactor); + if (velocityStrength > 1 || convertedVolume > 1) { + var bubbleSize = randInt(1, 5) + bubbleSize = bubbleSize / 10; + if (dimensions.x > bubbleSize) { + console.log('RELEASE BUBBLE') + var lifetime = randInt(3, 8); + //sound is somewhat unstable at the moment + // Script.setTimeout(function() { + // _t.burstBubbleSound(_t.currentBubble) + // }, lifetime * 1000) + //need to add some kind of forward velocity for bubble that you blow + Entities.editEntity(_t.currentBubble, { + velocity: Vec3.normalize(velocity), + // angularVelocity: Controller.getSpatialControlRawAngularVelocity(hands.leftHand.tip), + lifetime: lifetime + }); + + _t.spawnBubble(); + return + } else { + if (mouthMode) { + dimensions.x += 0.015 * convertedVolume; + dimensions.y += 0.015 * convertedVolume; + dimensions.z += 0.015 * convertedVolume; + + } else { + dimensions.x += 0.015 * velocityStrength; + dimensions.y += 0.015 * velocityStrength; + dimensions.z += 0.015 * velocityStrength; + } + + } + } else { + if (dimensions.x >= 0.02) { + dimensions.x -= 0.001; + dimensions.y -= 0.001; + dimensions.z -= 0.001; + } + + } + + Entities.editEntity(_t.currentBubble, { + position: wandPosition, + dimensions: dimensions + }); + + }, + burstBubbleSound: function(bubble) { + var position = Entities.getEntityProperties(bubble).position; + var orientation = Entities.getEntityProperties(bubble).orientation; + console.log('bubble position at pop: ' + JSON.stringify(position)); + var audioOptions = { + volume: 0.5, + position: position, + orientation: orientation + } + + //Audio.playSound(popSound, audioOptions); + + //remove this bubble from the array + var i = BubbleWand.bubbles.indexOf(bubble); + + if (i != -1) { + BubbleWand.bubbles.splice(i, 1); + } + + }, + spawnBubble: function() { + console.log('spawning bubble') + var _t = this; + var properties = Entities.getEntityProperties(thisEntity.entityID); + var wandPosition = properties.position; + + + _t.currentBubble = Entities.addEntity({ + type: 'Model', + modelURL: bubbleModel, + position: wandPosition, + dimensions: { + x: 0.01, + y: 0.01, + z: 0.01 + }, + ignoreForCollisions: true, + gravity: BUBBLE_GRAVITY, + // collisionSoundURL:popSound, + shapeType: "sphere", + script: bubbleScript, + }); + _t.bubbles.push(_t.currentBubble) + }, + init: function() { + var _t = this; + _t.spawnBubble(); + Script.update.connect(BubbleWand.update); + } + } + + function collectGarbage() { + console.log('COLLECTING GARBAGE!!!') + Entities.deleteEntity(BubbleWand.currentBubble); + while (BubbleWand.bubbles.length > 0) { + Entities.deleteEntity(BubbleWand.bubbles.pop()); + } + } + + BubbleWand.init(); + + + +}) \ No newline at end of file