// wand.js // part of bubblewand // // Script Type: Entity Script // Created by James B. Pollack @imgntn -- 09/03/2015 // Copyright 2015 High Fidelity, Inc. // // Makes bubbles when you wave the object around. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html (function() { Script.include("../libraries/utils.js"); var BUBBLE_MODEL = "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bubblewand/bubble.fbx"; var BUBBLE_INITIAL_DIMENSIONS = { x: 0.03, y: 0.03, z: 0.03 }; var BUBBLE_LIFETIME_MIN = 3; var BUBBLE_LIFETIME_MAX = 8; var BUBBLE_SIZE_MIN = 0.05; var BUBBLE_SIZE_MAX = 0.15; var BUBBLE_LINEAR_DAMPING = 0.2; var BUBBLE_GRAVITY_MIN = 0.1; var BUBBLE_GRAVITY_MAX = 0.3; var GROWTH_FACTOR = 0.015; var SHRINK_FACTOR = 0.001; var SHRINK_LOWER_LIMIT = 0.02; var WAND_TIP_OFFSET = 0.225; var VELOCITY_THRESHOLD = 0.5; //this helps us get the time passed since the last function call, for use in velocity calculations function interval() { var lastTime = new Date().getTime() / 1000; return function getInterval() { var newTime = new Date().getTime() / 1000; var delta = newTime - lastTime; lastTime = newTime; return delta; }; } var checkInterval = interval(); function BubbleWand() { return; } BubbleWand.prototype = { timePassed: null, currentBubble: null, leftActive: null, rightActive: null, popSound: null, popInjector: null, sloshLoop: null, sloshInjector: null, preload: function(entityID) { this.entityID = entityID; this.popSound = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/caitlyn/production/bubbles/245646__unfa__cartoon-pop-distorted.wav"); this.sloshLoop = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/caitlyn/production/bubbles/idleSlosh_r8b.wav"); Entities.editEntity(this.entityID, { animationFrameIndex : 1 }); }, getWandTipPosition: function(properties) { //the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position var upVector = Quat.getUp(properties.rotation); var upOffset = Vec3.multiply(upVector, WAND_TIP_OFFSET); var wandTipPosition = Vec3.sum(properties.position, upOffset); return wandTipPosition; }, addCollisionsToBubbleAfterCreation: function(bubble) { //if the bubble collide immediately, we get weird effects. so we add collisions after release Entities.editEntity(bubble, {dynamic: true}); }, randomizeBubbleGravity: function() { //change up the gravity a little bit for variation in floating effects var randomNumber = randFloat(BUBBLE_GRAVITY_MIN, BUBBLE_GRAVITY_MAX); var gravity = {x: 0, y: -randomNumber, z: 0}; return gravity; }, growBubbleWithWandVelocity: function(properties, deltaTime) { //get the wand and tip position for calculations var wandPosition = properties.position; this.getWandTipPosition(properties); // velocity = change in position / time var velocity = Vec3.multiply(Vec3.subtract(wandPosition, this.lastPosition), 1 / deltaTime); var velocityStrength = Vec3.length(velocity); //store the last position of the wand for velocity calculations this.lastPosition = wandPosition; //actually grow the bubble var dimensions = Entities.getEntityProperties(this.currentBubble, "dimensions").dimensions; if (velocityStrength > VELOCITY_THRESHOLD) { //add some variation in bubble sizes var bubbleSize = randFloat(BUBBLE_SIZE_MIN, BUBBLE_SIZE_MAX); //release the bubble if its dimensions are bigger than the bubble size if (dimensions.x > bubbleSize) { //bubbles pop after existing for a bit -- so set a random lifetime var lifetime = randInt(BUBBLE_LIFETIME_MIN, BUBBLE_LIFETIME_MAX); //edit the bubble properties at release Entities.editEntity(this.currentBubble, { velocity: velocity, lifetime: lifetime, gravity: this.randomizeBubbleGravity() }); //wait to make the bubbles collidable, so that they dont hit each other and the wand Script.setTimeout(this.addCollisionsToBubbleAfterCreation(this.currentBubble), lifetime / 2); //release the bubble -- when we create a new bubble, it will carry on and this update loop will // affect the new bubble this.createBubbleAtTipOfWand(); return; } else { //grow small bubbles dimensions.x += GROWTH_FACTOR * velocityStrength; dimensions.y += GROWTH_FACTOR * velocityStrength; dimensions.z += GROWTH_FACTOR * velocityStrength; } } else { // if the wand is not moving, make the current bubble smaller if (dimensions.x >= SHRINK_LOWER_LIMIT) { dimensions.x -= SHRINK_FACTOR; dimensions.y -= SHRINK_FACTOR; dimensions.z -= SHRINK_FACTOR; } } //adjust the bubble dimensions Entities.editEntity(this.currentBubble, { dimensions: dimensions }); }, createBubbleAtTipOfWand: function() { // CM - Randomly play a random bubbling sound? //create a new bubble at the tip of the wand var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); var wandPosition = properties.position; //store the position of the tip for use in velocity calculations this.lastPosition = wandPosition; //create a bubble at the wand tip this.currentBubble = Entities.addEntity({ name: 'Bubble', type: 'Model', modelURL: BUBBLE_MODEL, position: this.getWandTipPosition(properties), dimensions: BUBBLE_INITIAL_DIMENSIONS, dynamic: false, collisionless: true, damping: BUBBLE_LINEAR_DAMPING, shapeType: "sphere" }); }, startNearGrab: function(entityID, args) { //create a bubble to grow at the start of the grab print("BUBBLE start near grab "+args[0]+"!!!"); var posProperties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); var emitPoint = posProperties.position; if (this.currentBubble === null) this.createBubbleAtTipOfWand(); if (!this.leftActive && !this.rightActive) { var newProperties = { animationFrameIndex : 2 }; // Switch to frame 1, bottle top hidden, wand extended Entities.editEntity(this.entityID, newProperties); if (this.popSound.downloaded){ var options = { volume: 0.05, loop: false, position: emitPoint } this.popInjector = Audio.playSound(this.popSound, options); } if (this.sloshLoop.downloaded && !this.sloshInjector.isPlaying()){ var options = { volume: 1, loop: false, position: emitPoint } this.sloshInjector = Audio.playSound(this.sloshLoop, options); } } if (args[0]=="left") { leftActive = true; } if (args[0]=="right") { rightActive = true; } }, continueNearGrab: function(entityID, args) { //var newProperties = { animationFrameIndex : 2 }; // Switch to frame 1, bottle top hidden, wand extended Entities.editEntity(this.entityID, { animationFrameIndex : 2 }); var deltaTime = checkInterval(); var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); var wandTipPosition = this.getWandTipPosition(properties); Entities.editEntity(this.currentBubble, {position: wandTipPosition,}); this.growBubbleWithWandVelocity(properties, deltaTime); }, releaseGrab: function(entityID, args) { print("BUBBLE release grab "+args[0]+"!!!"); //this.currentBubble = null; //if (args[0]==null) return; if (args[0]=="left") this.leftActive = false; if (args[0]=="right") this.rightActive = false; if (!this.leftActive && !this.rightActive){ // var newProperties = { animationFrameIndex : 1 }; // Switch to frame 1, bottle top on, wand retracted Entities.editEntity(this.entityID, { animationFrameIndex : 1 }); Entities.deleteEntity(this.currentBubble); this.currentBubble = null; //if (this.sloshInjector.isPlaying()) this.sloshInjector.stop(); } }, }; return new BubbleWand(); });