From 49858d3ec5c84f281976290fe4aa3d565e65b5c0 Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Fri, 7 Apr 2017 19:16:29 -0700 Subject: [PATCH] Equip stick instead of grab. String is less rigid. Fixed bug where the ball was not recognized when other clients equipped the stick. --- scripts/tutorials/createTetherballStick.js | 98 +++-- .../entity_scripts/tetherballStick.js | 407 ++++++------------ 2 files changed, 194 insertions(+), 311 deletions(-) diff --git a/scripts/tutorials/createTetherballStick.js b/scripts/tutorials/createTetherballStick.js index a975c3a247..d0c069d186 100644 --- a/scripts/tutorials/createTetherballStick.js +++ b/scripts/tutorials/createTetherballStick.js @@ -14,20 +14,61 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +var LIFETIME = 3600; +var BALL_SIZE = 0.175; +var BALL_DAMPING = 0.5; +var BALL_ANGULAR_DAMPING = 0.5; +var BALL_RESTITUTION = 0.4; +var BALL_DENSITY = 1000; var STICK_SCRIPT_URL = Script.resolvePath("./entity_scripts/tetherballStick.js?v=" + Date.now()); var STICK_MODEL_URL = "http://hifi-content.s3.amazonaws.com/caitlyn/production/raveStick/newRaveStick2.fbx"; +var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; var avatarOrientation = MyAvatar.orientation; avatarOrientation = Quat.safeEulerAngles(avatarOrientation); avatarOrientation.x = 0; avatarOrientation = Quat.fromVec3Degrees(avatarOrientation); -var startPosition = Vec3.sum(MyAvatar.getRightPalmPosition(), Vec3.multiply(1, Quat.getFront(avatarOrientation))); +var front = Quat.getFront(avatarOrientation); +var stickStartPosition = Vec3.sum(MyAvatar.getRightPalmPosition(), front); +var ballStartPosition = Vec3.sum(stickStartPosition, Vec3.multiply(0.36, front)); + +var ballID = Entities.addEntity({ + type: "Model", + modelURL: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Star.fbx", + name: "TetherballStick Ball", + shapeType: "Sphere", + position: ballStartPosition, + lifetime: LIFETIME, + collisionSoundURL: COLLISION_SOUND_URL, + dimensions: { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE + }, + gravity: { + x: 0.0, + y: -9.8, + z: 0.0 + }, + damping: BALL_DAMPING, + angularDamping: BALL_ANGULAR_DAMPING, + density: BALL_DENSITY, + restitution: BALL_RESTITUTION, + dynamic: true, + collidesWith: "static,dynamic,otherAvatar,", + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) +}); var STICK_PROPERTIES = { type: 'Model', name: "TetherballStick Stick", modelURL: STICK_MODEL_URL, - position: startPosition, + position: stickStartPosition, rotation: MyAvatar.orientation, dimensions: { x: 0.0651, @@ -41,31 +82,40 @@ var STICK_PROPERTIES = { blue: 20 }, shapeType: 'box', - lifetime: 3600, + lifetime: LIFETIME, userData: JSON.stringify({ grabbableKey: { - grabbable: true, - spatialKey: { - rightRelativePosition: { - x: 0.05, - y: 0, - z: 0 - }, - leftRelativePosition: { - x: -0.05, - y: 0, - z: 0 - }, - relativeRotation: { - x: 0.4999999701976776, - y: 0.4999999701976776, - z: -0.4999999701976776, - w: 0.4999999701976776 - } - }, - invertSolidWhileHeld: true + invertSolidWhileHeld: true, + ignoreIK: false }, - ownerID: MyAvatar.sessionUUID + wearable: { + joints: { + RightHand: [{ + x: 0.15539926290512085, + y: 0.14493153989315033, + z: 0.023641478270292282 + }, { + x: 0.5481458902359009, + y: -0.4470711946487427, + z: -0.3148134648799896, + w: 0.6328644752502441 + }], + LeftHand: [{ + x: -0.14998853206634521, + y: 0.17033983767032623, + z: 0.023199155926704407 + }, + { + x: 0.6623835563659668, + y: -0.1671387255191803, + z: 0.7071226835250854, + w: 0.1823924481868744 + }] + } + }, + ownerID: MyAvatar.sessionUUID, + ballID: ballID, + lifetime: LIFETIME }) }; diff --git a/scripts/tutorials/entity_scripts/tetherballStick.js b/scripts/tutorials/entity_scripts/tetherballStick.js index 6fb249dc28..35e704b709 100644 --- a/scripts/tutorials/entity_scripts/tetherballStick.js +++ b/scripts/tutorials/entity_scripts/tetherballStick.js @@ -18,178 +18,107 @@ var _this; var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; - var ENTITY_CHECK_INTERVAL = 5; // time in sec var LINE_WIDTH = 0.02; - var BALL_SIZE = 0.175; - var BALL_DAMPING = 0.5; - var BALL_ANGULAR_DAMPING = 0.5; - var BALL_RESTITUTION = 0.4; - var BALL_DENSITY = 1000; - var MAX_DISTANCE_MULTIPLIER = 2; - var ACTION_DISTANCE = 0.25; - var ACTION_TIMESCALE = 0.025; - var ACTION_TAG = "TetherballStick Action"; - var BALL_NAME = "TetherballStick Ball"; - var LINE_NAME = "TetherballStick Line"; + var MAX_DISTANCE_MULTIPLIER = 4; + var ACTION_DISTANCE = 0.35; + var ACTION_TIMESCALE = 0.035; var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; - var INTERACT_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/color_busters/powerup.wav"; - var INTERACT_SOUND_VOLUME = 0.2; - var USE_INTERACT_SOUND = false; - var AVATAR_CHECK_RANGE = 5; // in meters var TIP_OFFSET = 0.26; var LIFETIME = 3600; tetherballStick = function() { _this = this; - return; }; tetherballStick.prototype = { - lastCheckForEntities: ENTITY_CHECK_INTERVAL, - originalDimensions: null, - ownerID: NULL_UUID, - userID: NULL_UUID, + entityID: NULL_UUID, ballID: NULL_UUID, lineID: NULL_UUID, - actionID: NULL_UUID, - INTERACT_SOUND: undefined, + + getUserData: function(key) { + try { + var stickProps = Entities.getEntityProperties(this.entityID); + var userData = JSON.parse(stickProps.userData); + return userData[key]; + } catch (e) { + print("Error parsing Tetherball Stick UserData in file " + + e.fileName + " on line " + e.lineNumber); + } + }, preload: function(entityID) { this.entityID = entityID; - if (USE_INTERACT_SOUND) { - this.INTERACT_SOUND = SoundCache.getSound(INTERACT_SOUND_URL); - } - this.originalDimensions = Entities.getEntityProperties(this.entityID).dimensions; - Script.update.connect(this.update); - }, - - unload: function() { - Script.update.disconnect(this.update); + this.ballID = this.getUserData("ballID"); }, update: function(dt) { - // _this during update due to loss of scope - if (_this.lastCheckForEntities >= ENTITY_CHECK_INTERVAL) { - _this.lastCheckForEntities = 0; - // everyone will be running this update loop - // the only action is to increment the timer till ENTITY_CHECK_INTERVAL - // when that is reached the userdata ownerID is simply validated by everyone - // if it's invalid call setNewOwner, to set a new valid user - if (!_this.checkForOwner()) { - return; - } + _this.drawLine(); + }, - // only one person should ever be running this far - if (_this.ownerID == MyAvatar.sessionUUID) { - _this.checkForEntities(); - } + startEquip: function(id, params) { + this.removeActions(); + this.createLine(); + + Script.update.connect(this.update); + }, + + continueEquip: function(id, params) { + var stickProps = Entities.getEntityProperties(this.entityID); + var ballProps = Entities.getEntityProperties(this.ballID); + var tipPosition = this.getTipPosition(); + var distance = Vec3.distance(tipPosition, ballProps.position); + var maxDistance = ACTION_DISTANCE; + + var dVel = Vec3.subtract(ballProps.velocity, stickProps.velocity); + var dPos = Vec3.subtract(ballProps.position, stickProps.position); + var ballWithinMaxDistance = distance <= maxDistance; + var ballMovingCloserToStick = Vec3.dot(dVel, dPos) < 0; + var ballAboveStick = ballProps.position.y > tipPosition.y; + + if(this.hasAction()) { + if(ballWithinMaxDistance && (ballMovingCloserToStick || ballAboveStick)) { + this.removeActions(); } else { - _this.lastCheckForEntities += dt; + this.updateOffsetAction(); } + } else if(!ballWithinMaxDistance && !ballMovingCloserToStick){ + this.createOffsetAction(); + } - _this.drawLine(); + this.capBallDistance(); }, - getScale: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - return stickProps.dimensions.z / this.originalDimensions.z; - }, + releaseEquip: function(id, params) { + this.deleteLine(); + this.createSpringAction(); - checkForOwner: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var stickData = JSON.parse(stickProps.userData); - var owner = AvatarManager.getAvatar(stickData.ownerID); - if (owner.sessionUUID !== undefined) { - this.ownerID = owner.sessionUUID; - return true; - } else { - // UUID is invalid - this.setNewOwner(); - return false; - } - } catch (e) { - // all other errors - this.setNewOwner(); - return false; - } - }, - - setNewOwner: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - // I only want the closest client to be in charge of creating objects. - // the AvatarList also contains a null representing MyAvatar, - // a new array is created to start with containing the proper UUID - var avatarList = AvatarList.getAvatarIdentifiers() - .filter(Boolean) // remove the null - .concat(MyAvatar.sessionUUID); // add the ID - - var closestAvatarID = undefined; - var closestAvatarDistance = AVATAR_CHECK_RANGE; - avatarList.forEach(function(avatarID) { - var avatar = AvatarList.getAvatar(avatarID); - var distFrom = Vec3.distance(avatar.position, stickProps.position); - if (distFrom < closestAvatarDistance) { - closestAvatarDistance = distFrom; - closestAvatarID = avatarID; - } - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.ownerID = closestAvatarID; //NOTE: this will assign undefined if all avatars are further than AVATAR_CHECK_RANGE - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) { - } - }, - - checkForEntities: function() { - if (!this.hasBall()) { - this.createBall(); - } - if (!this.hasAction()) { - this.createAction(); - } - }, - - playInteractionSound: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var INTERACT_SOUND_OPTIONS = { - volume: INTERACT_SOUND_VOLUME, - loop: false, - position: stickProps.position - }; - Audio.playSound(this.INTERACT_SOUND, INTERACT_SOUND_OPTIONS); + Script.update.disconnect(this.update); }, getTipPosition: function() { - var stickProps = Entities.getEntityProperties(this.entityID); + var stickProps = Entities.getEntityProperties(this.entityID); + var stickFront = Quat.getFront(stickProps.rotation); + var frontOffset = Vec3.multiply(stickFront, TIP_OFFSET); + var tipPosition = Vec3.sum(stickProps.position, frontOffset); - var scale = this.getScale(); - var frontVec = Quat.getFront(stickProps.rotation); - var frontOffset = Vec3.multiply(frontVec, TIP_OFFSET * scale); - var tipPosition = Vec3.sum(stickProps.position, frontOffset); - - return tipPosition; + return tipPosition; }, getStickFrontPosition: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var stickFront = Quat.getFront(stickProps.rotation); - var tipPosition = this.getTipPosition(); - return Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET*.4, stickFront)); + var stickProps = Entities.getEntityProperties(this.entityID); + var stickFront = Quat.getFront(stickProps.rotation); + var tipPosition = this.getTipPosition(); + var frontPostion = Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET * 0.4, stickFront)); + + return frontPostion; }, capBallDistance: function() { var stickProps = Entities.getEntityProperties(this.entityID); var ballProps = Entities.getEntityProperties(this.ballID); var tipPosition = this.getTipPosition(); - var scale = this.getScale(); var distance = Vec3.distance(tipPosition, ballProps.position); - var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER * scale; + var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER; if(distance > maxDistance) { var direction = Vec3.normalize(Vec3.subtract(ballProps.position, tipPosition)); @@ -200,175 +129,39 @@ } }, - startNearGrab: function(id, params) { - var stickProps = Entities.getEntityProperties(this.entityID); - // set variables from data in case someone else created the components - try { - var stickData = JSON.parse(stickProps.userData); - this.userID = MyAvatar.sessionUUID; - this.ballID = stickData.ballID; - this.actionID = stickData.actionID; - if(!this.hasLine()) { - this.createLine(); - } - if (USE_INTERACT_SOUND) { - var hand = params[0]; - Controller.triggerShortHapticPulse(1, hand); - this.playInteractionSound(); - } - } catch (e) { } - }, - - releaseGrab: function(id, params) { - this.userID = NULL_UUID; - if (USE_INTERACT_SOUND) { - this.playInteractionSound(); - } - }, - - continueNearGrab: function(id, params) { - this.repositionAction(); - this.updateDimensions(); - this.capBallDistance(); - }, - createLine: function() { - var stickProps = Entities.getEntityProperties(this.entityID); + if(!this.hasLine()) { this.lineID = Entities.addEntity({ type: "PolyLine", - name: LINE_NAME, + name: "TetherballStick Line", color: { red: 0, green: 120, blue: 250 }, textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", - position: stickProps.position, + position: this.getTipPosition(), dimensions: { x: 10, y: 10, z: 10 }, - lifetime: LIFETIME + lifetime: this.getUserData("lifetime") }); + } }, deleteLine: function() { - Entities.deleteEntity(this.lineID); - this.lineID = NULL_UUID; + Entities.deleteEntity(this.lineID); + this.lineID = NULL_UUID; }, hasLine: function() { - var lineProps = Entities.getEntityProperties(this.lineID); - return lineProps.name == LINE_NAME; - }, - - createBall: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - - this.ballID = Entities.addEntity({ - type: "Model", - modelURL: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Star.fbx", - name: BALL_NAME, - shapeType: "Sphere", - position: this.getStickFrontPosition(), - lifetime: LIFETIME, - collisionSoundURL: COLLISION_SOUND_URL, - dimensions: { - x: BALL_SIZE, - y: BALL_SIZE, - z: BALL_SIZE - }, - gravity: { - x: 0.0, - y: -9.8, - z: 0.0 - }, - damping: BALL_DAMPING, - angularDamping: BALL_ANGULAR_DAMPING, - density: BALL_DENSITY, - restitution: BALL_RESTITUTION, - dynamic: true, - collidesWith: "static,dynamic,otherAvatar,", - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.ballID = this.ballID; - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) {} - }, - - hasBall: function() { - // validate the userData to handle unexpected item deletion - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var data = JSON.parse(stickProps.userData); - var ballProps = Entities.getEntityProperties(data.ballID); - return ballProps.name == BALL_NAME; - } catch (e) { - return false; - } - }, - - createAction: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - this.actionID = Entities.addAction("offset", this.ballID, { - pointToOffsetFrom: stickProps.position, - linearDistance: ACTION_DISTANCE, - tag: ACTION_TAG, - linearTimeScale: ACTION_TIMESCALE - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.actionID = this.actionID; - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) {} - }, - - hasAction: function() { - // validate the userData to handle unexpected item deletion - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var stickData = JSON.parse(stickProps.userData); - var actionProps = Entities.getActionArguments(stickData.ballID, stickData.actionID); - return actionProps.tag == ACTION_TAG; - } catch (e) { - return false; - } - }, - - hasRequiredComponents: function() { - return this.hasBall() && this.hasAction() && this.hasLine(); - }, - - updateDimensions: function() { - var scale = this.getScale(); - Entities.editEntity(this.ballID, { - dimensions: { - x: BALL_SIZE * scale, - y: BALL_SIZE * scale, - z: BALL_SIZE * scale - } - }); + return this.lineID != NULL_UUID; }, drawLine: function() { - if(!this.hasLine()) - return; - + if(this.hasLine()) { var stickProps = Entities.getEntityProperties(this.entityID); var tipPosition = this.getTipPosition(); var ballProps = Entities.getEntityProperties(this.ballID); @@ -390,19 +183,59 @@ strokeWidths: strokeWidths, position: tipPosition, }); + } }, - repositionAction: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var tipPosition = this.getTipPosition(); - var scale = this.getScale(); + createOffsetAction: function() { + this.removeActions(); - Entities.updateAction(this.ballID, this.actionID, { - pointToOffsetFrom: tipPosition, - linearDistance: ACTION_DISTANCE * scale, - tag: ACTION_TAG, - linearTimeScale: ACTION_TIMESCALE + Entities.addAction("offset", this.ballID, { + pointToOffsetFrom: this.getTipPosition(), + linearDistance: ACTION_DISTANCE, + linearTimeScale: ACTION_TIMESCALE + }); + }, + + createSpringAction: function() { + this.removeActions(); + + Entities.addAction("spring", this.ballID, { + targetPosition: this.getTipPosition(), + linearTimeScale: ACTION_TIMESCALE + }); + }, + + updateOffsetAction: function() { + var actionIDs = Entities.getActionIDs(this.ballID); + var actionID; + + // Sometimes two offset actions are applied to the ball simultaneously. + // Here we ensure that only the most recent action is updated + // and the rest are deleted. + while(actionIDs.length > 1) { + actionID = actionIDs.shift(); + Entities.deleteAction(this.ballID, actionID); + } + + actionID = actionIDs.shift(); + if(actionID) { + Entities.updateAction(this.ballID, actionID, { + pointToOffsetFrom: this.getTipPosition() }); + } + }, + + removeActions: function() { + // Ball should only ever have one action, but sometimes sneaky little actions attach themselves + // So we remove all possible actions in this call. + var actionIDs = Entities.getActionIDs(this.ballID); + for(var i = 0; i < actionIDs.length; i++) { + Entities.deleteAction(this.ballID, actionIDs[i]); + } + }, + + hasAction: function() { + return Entities.getActionIDs(this.ballID).length > 0; } };