From 53a8a0e45bf66e491dc3d9171e18f58f88e2d376 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 28 Nov 2016 16:49:43 -0800 Subject: [PATCH] Update bow.js --- bow/bow/bow.js | 448 ++++++++++++++++++++++++++++--------------------- 1 file changed, 254 insertions(+), 194 deletions(-) diff --git a/bow/bow/bow.js b/bow/bow/bow.js index 2978a7d85d..dfa7c7d8dd 100644 --- a/bow/bow/bow.js +++ b/bow/bow/bow.js @@ -9,23 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/*global Script, Controller, SoundCache, Entities, getEntityCustomData, setEntityCustomData, MyAvatar, Vec3, Quat, Messages */ + (function() { - Script.include("utils.js"); + Script.include("/~/system/libraries/utils.js"); + + var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; var NOTCH_ARROW_SOUND_URL = 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/notch.wav'; - var SHOOT_ARROW_SOUND_URL = 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/String_release2.L.wav'; + var SHOOT_ARROW_SOUND_URL = + 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/String_release2.L.wav'; var STRING_PULL_SOUND_URL = 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/Bow_draw.1.L.wav'; - var ARROW_HIT_SOUND_URL = 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/Arrow_impact1.L.wav' + var ARROW_HIT_SOUND_URL = 'http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/Arrow_impact1.L.wav'; - var ARROW_DIMENSIONS = { - x: 0.02, - y: 0.02, - z: 0.72 - }; - - var ARROW_OFFSET = -0.44; - var ARROW_TIP_OFFSET = 0.32; + var ARROW_TIP_OFFSET = 0.47; var ARROW_GRAVITY = { x: 0, y: -4.8, @@ -37,11 +35,13 @@ "http://mpassets.highfidelity.com/32fc6d32-27a2-428e-937e-869f3f05e8e1-v1/newarrow_collision_hull.obj"; var ARROW_DIMENSIONS = { - x: 0.02, - y: 0.02, - z: 0.64 + x: 0.03, + y: 0.03, + z: 0.96 }; + var ARROW_LIFETIME = 15; // seconds + var TOP_NOTCH_OFFSET = 0.6; var BOTTOM_NOTCH_OFFSET = 0.6; @@ -53,9 +53,10 @@ }; var DRAW_STRING_THRESHOLD = 0.80; - - var LEFT_TIP = 1; - var RIGHT_TIP = 3; + var DRAW_STRING_PULL_DELTA_HAPTIC_PULSE = 0.09; + var DRAW_STRING_MAX_DRAW = 0.7; + var NEAR_TO_RELAXED_KNOCK_DISTANCE = 0.5; // if the hand is this close, rez the arrow + var NEAR_TO_RELAXED_SCHMITT = 0.05; var NOTCH_OFFSET_FORWARD = 0.08; var NOTCH_OFFSET_UP = 0.035; @@ -65,7 +66,7 @@ max1: 0.6, min2: 1, max2: 15 - } + }; var USE_DEBOUNCE = false; @@ -95,11 +96,10 @@ } Bow.prototype = { - stringDrawn: false, + topString: null, aiming: false, arrowTipPosition: null, preNotchString: null, - hasArrowNotched: false, arrow: null, stringData: { currentColor: { @@ -108,6 +108,13 @@ blue: 255 } }, + + // 0 = start + // 1 = hand close to knock, arrow is drawn + // 2 = arrow is grabbed + // 3 = arrow is grabbed and pulled + + state: 0, sinceLastUpdate: 0, preload: function(entityID) { this.entityID = entityID; @@ -115,11 +122,15 @@ this.shootArrowSound = SoundCache.getSound(SHOOT_ARROW_SOUND_URL); this.arrowHitSound = SoundCache.getSound(ARROW_HIT_SOUND_URL); this.arrowNotchSound = SoundCache.getSound(NOTCH_ARROW_SOUND_URL); - var userData = Entities.getEntityProperties(this.entityID).userData; + var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData; this.userData = JSON.parse(userData); var children = Entities.getChildrenIDs(this.entityID); - this.preNotchString = children[0]; - + children.forEach(function(childID) { + var childName = Entities.getEntityProperties(childID, ["name"]).name; + if (childName == "Hifi-Bow-Pre-Notch-String") { + this.preNotchString = children[0]; + } + }); }, unload: function() { @@ -127,37 +138,37 @@ Entities.deleteEntity(this.arrow); }, - startNearGrab: function(entityID, args) { - _this.startEquip(entityID, args); - }, - - continueNearGrab: function(entityID, args) { - _this.continueEquip(entityID, args); - }, - - releaseGrab: function(entityID, args) { - _this.releaseEquip(entityID, args); - }, - startEquip: function(entityID, args) { this.hand = args[0]; - avatarID = args[1]; + // var avatarID = args[1]; - //disable the opposite hand in handControllerGrab.js by message - var handToDisable = this.hand === 'right' ? 'left' : 'right'; - Messages.sendMessage('Hifi-Hand-Disabler', handToDisable); + if (this.hand === 'left') { + this.getStringHandPosition = function() { return _this.getControllerLocation("right").position; }; + } else if (this.hand === 'right') { + this.getStringHandPosition = function() { return _this.getControllerLocation("left").position; }; + } var data = getEntityCustomData('grabbableKey', this.entityID, {}); data.grabbable = false; setEntityCustomData('grabbableKey', this.entityID, data); Entities.editEntity(_this.entityID, { collidesWith: "" - }) + }); + //make sure the string is ready + if (!this.preNotchString) { + this.createPreNotchString(); + } + var preNotchStringProps = Entities.getEntityProperties(this.preNotchString); + if (!preNotchStringProps || preNotchStringProps.name != "Hifi-Bow-Pre-Notch-String") { + this.createPreNotchString(); + } + Entities.editEntity(this.preNotchString, { + visible: true + }); }, continueEquip: function(entityID, args) { this.deltaTime = checkInterval(); - //debounce during debugging -- maybe we're updating too fast? if (USE_DEBOUNCE === true) { this.sinceLastUpdate = this.sinceLastUpdate + this.deltaTime; @@ -169,23 +180,10 @@ } } - this.bowProperties = Entities.getEntityProperties(this.entityID); - - if (this.aiming === true) { - Entities.editEntity(this.preNotchString, { - visible: false - }) - } else { - Entities.editEntity(this.preNotchString, { - visible: true - }) - } - this.checkStringHand(); - }, releaseEquip: function(entityID, args) { - Messages.sendMessage('Hifi-Hand-Disabler', "none") + Messages.sendMessage('Hifi-Hand-Disabler', "none"); this.stringDrawn = false; this.deleteStrings(); @@ -194,11 +192,9 @@ data.grabbable = true; setEntityCustomData('grabbableKey', this.entityID, data); Entities.deleteEntity(this.arrow); - this.aiming = false; - this.hasArrowNotched = false; Entities.editEntity(_this.entityID, { collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar" - }) + }); }, createArrow: function() { @@ -212,6 +208,7 @@ compoundShapeURL: ARROW_COLLISION_HULL_URL, dimensions: ARROW_DIMENSIONS, position: this.bowProperties.position, + parentID: this.entityID, dynamic: false, collisionless: true, collisionSoundURL: ARROW_HIT_SOUND_URL, @@ -226,12 +223,12 @@ var makeArrowStick = function(entityA, entityB, collision) { Entities.editEntity(entityA, { - angularVelocity: { + localAngularVelocity: { x: 0, y: 0, z: 0 }, - velocity: { + localVelocity: { x: 0, y: 0, z: 0 @@ -241,20 +238,40 @@ y: 0, z: 0 }, - position: collision.contactPoint, - dynamic: false - }) - Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick) - } + parentID: entityB, + dynamic: false, + collisionless: true, + collidesWith: "" + }); + Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick); + }; Script.addEventHandler(arrow, "collisionWithEntity", makeArrowStick); - return arrow + return arrow; + }, + + createPreNotchString: function() { + this.preNotchString = Entities.addEntity({ + "collisionless": 1, + "dimensions": { "x": 5, "y": 5, "z": 5 }, + "ignoreForCollisions": 1, + "linePoints": [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ], + "lineWidth": 5, + "name": "Hifi-Bow-Pre-Notch-String", + "parentID": this.entityID, + "localPosition": { "x": 0, "y": 0.6, "z": 0.1 }, + "localRotation": { "w": 1, "x": 0, "y": 0, "z": 0 }, + "type": "Line", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }); }, createStrings: function() { - this.createTopString(); - this.createBottomString(); + if (!this.topString) { + this.createTopString(); + Entities.editEntity(this.preNotchString, { visible: false }); + } }, createTopString: function() { @@ -265,6 +282,8 @@ dimensions: LINE_DIMENSIONS, dynamic: false, collisionless: true, + lineWidth: 5, + color: this.stringData.currentColor, userData: JSON.stringify({ grabbableKey: { grabbable: false @@ -275,30 +294,17 @@ this.topString = Entities.addEntity(stringProperties); }, - createBottomString: function() { - var stringProperties = { - name: 'Hifi-Bow-Bottom-String', - type: 'Line', - position: Vec3.sum(this.bowProperties.position, BOTTOM_NOTCH_OFFSET), - dimensions: LINE_DIMENSIONS, - dynamic: false, - collisionless: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }; - - this.bottomString = Entities.addEntity(stringProperties); - }, - deleteStrings: function() { - Entities.deleteEntity(this.topString); - Entities.deleteEntity(this.bottomString); + if (this.topString) { + Entities.deleteEntity(this.topString); + this.topString = null; + Entities.editEntity(this.preNotchString, { visible: true }); + } }, - updateStringPositions: function() { + drawStrings: function() { + this.createStrings(); + var upVector = Quat.getUp(this.bowProperties.rotation); var upOffset = Vec3.multiply(upVector, TOP_NOTCH_OFFSET); var downVector = Vec3.multiply(-1, Quat.getUp(this.bowProperties.rotation)); @@ -310,90 +316,94 @@ var bottomStringPosition = Vec3.sum(this.bowProperties.position, downOffset); this.bottomStringPosition = Vec3.sum(bottomStringPosition, backOffset); - Entities.editEntity(this.topString, { - position: this.topStringPosition - }); - - Entities.editEntity(this.bottomString, { - position: this.bottomStringPosition - }); - - }, - - drawStrings: function() { - - this.updateStringPositions(); var lineVectors = this.getLocalLineVectors(); Entities.editEntity(this.topString, { - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, lineVectors[0]], - lineWidth: 5, - color: this.stringData.currentColor + position: this.topStringPosition, + linePoints: [{ x: 0, y: 0, z: 0 }, lineVectors[0], lineVectors[1]] }); - - Entities.editEntity(this.bottomString, { - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, lineVectors[1]], - lineWidth: 5, - color: this.stringData.currentColor - }); - }, getLocalLineVectors: function() { var topVector = Vec3.subtract(this.arrowRearPosition, this.topStringPosition); - var bottomVector = Vec3.subtract(this.arrowRearPosition, this.bottomStringPosition); + var bottomVector = Vec3.subtract(this.bottomStringPosition, this.topStringPosition); return [topVector, bottomVector]; }, + getControllerLocation: function (controllerHand) { + var standardControllerValue = + (controllerHand === "right") ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + var orientation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var position = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + return {position: position, orientation: orientation}; + }, + checkStringHand: function() { //invert the hands because our string will be held with the opposite hand of the first one we pick up the bow with - var triggerLookup; - if (this.hand === 'left') { - triggerLookup = 1; - this.getStringHandPosition = MyAvatar.getRightPalmPosition; - } else if (this.hand === 'right') { - this.getStringHandPosition = MyAvatar.getLeftPalmPosition; - triggerLookup = 0; + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[(this.hand === 'left') ? 1 : 0]); + + this.bowProperties = Entities.getEntityProperties(this.entityID); + var notchPosition = this.getNotchPosition(this.bowProperties); + var stringHandPosition = this.getStringHandPosition(); + var handToNotch = Vec3.subtract(notchPosition, stringHandPosition); + var pullBackDistance = Vec3.length(handToNotch); + + if (this.state === 0) { + this.pullBackDistance = 0; + + this.deleteStrings(); + if (pullBackDistance < (NEAR_TO_RELAXED_KNOCK_DISTANCE - NEAR_TO_RELAXED_SCHMITT) && !this.backHandBusy) { + //the first time aiming the arrow + var handToDisable = (this.hand === 'right' ? 'left' : 'right'); + Messages.sendMessage('Hifi-Hand-Disabler', handToDisable); + this.arrow = this.createArrow(); + this.playStringPullSound(); + this.state = 1; + } } + if (this.state === 1) { - this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[triggerLookup]); - - - if (this.triggerValue < DRAW_STRING_THRESHOLD && this.stringDrawn === true) { - // firing the arrow - - this.drawStrings(); - this.hasArrowNotched = false; - this.aiming = false; - this.stringDrawn = false; - this.updateArrowPositionInNotch(true); - - - } else if (this.triggerValue > DRAW_STRING_THRESHOLD && this.stringDrawn === true) { - //continuing to aim the arrow - - this.aiming = true; - this.drawStrings(); - this.updateArrowPositionInNotch(); - - } else if (this.triggerValue > DRAW_STRING_THRESHOLD && this.stringDrawn === false) { - this.arrow = this.createArrow(); - this.playStringPullSound(); - - //the first time aiming the arrow - this.stringDrawn = true; - this.createStrings(); - this.drawStrings(); - this.updateArrowPositionInNotch(); - + if (pullBackDistance >= (NEAR_TO_RELAXED_KNOCK_DISTANCE + NEAR_TO_RELAXED_SCHMITT)) { + // delete the unpulled arrow + Messages.sendMessage('Hifi-Hand-Disabler', "none"); + Entities.deleteEntity(this.arrow); + this.arrow = null; + this.state = 0; + } else if (this.triggerValue >= DRAW_STRING_THRESHOLD) { + // they've grabbed the arrow + this.pullBackDistance = 0; + this.state = 2; + } else { + this.drawStrings(); + this.updateArrowPositionInNotch(false, false); + } + } + if (this.state === 2) { + if (this.triggerValue < DRAW_STRING_THRESHOLD) { + // they let go without pulling + this.state = 1; + } else if (pullBackDistance >= (NEAR_TO_RELAXED_KNOCK_DISTANCE + NEAR_TO_RELAXED_SCHMITT)) { + // they've grabbed the arrow and pulled it + this.state = 3; + } else { + this.drawStrings(); + this.updateArrowPositionInNotch(false, true); + } + } + if (this.state === 3) { + if (pullBackDistance < (NEAR_TO_RELAXED_KNOCK_DISTANCE + NEAR_TO_RELAXED_SCHMITT)) { + // they unpulled without firing + this.state = 2; + } else if (this.triggerValue < DRAW_STRING_THRESHOLD) { + // they've fired the arrow + Messages.sendMessage('Hifi-Hand-Disabler', "none"); + this.updateArrowPositionInNotch(true, true); + this.state = 0; + } else { + this.drawStrings(); + this.updateArrowPositionInNotch(false, true); + } } }, @@ -406,59 +416,71 @@ }, - updateArrowPositionInNotch: function(shouldReleaseArrow) { - var bowProperties = Entities.getEntityProperties(this.entityID); - //set the notch that the arrow should go through + getNotchPosition: function(bowProperties) { var frontVector = Quat.getFront(bowProperties.rotation); var notchVectorForward = Vec3.multiply(frontVector, NOTCH_OFFSET_FORWARD); var upVector = Quat.getUp(bowProperties.rotation); var notchVectorUp = Vec3.multiply(upVector, NOTCH_OFFSET_UP); - var notchPosition; - notchPosition = Vec3.sum(bowProperties.position, notchVectorForward); + var notchPosition = Vec3.sum(bowProperties.position, notchVectorForward); notchPosition = Vec3.sum(notchPosition, notchVectorUp); + return notchPosition; + }, + updateArrowPositionInNotch: function(shouldReleaseArrow, doHapticPulses) { + //set the notch that the arrow should go through + var notchPosition = this.getNotchPosition(this.bowProperties); //set the arrow rotation to be between the notch and other hand var stringHandPosition = this.getStringHandPosition(); var handToNotch = Vec3.subtract(notchPosition, stringHandPosition); var arrowRotation = Quat.rotationBetween(Vec3.FRONT, handToNotch); - - + var backHand = this.hand === 'left' ? 1 : 0; var pullBackDistance = Vec3.length(handToNotch); + // pulse as arrow is drawn + if (doHapticPulses && + Math.abs(pullBackDistance - this.pullBackDistance) > DRAW_STRING_PULL_DELTA_HAPTIC_PULSE) { + Controller.triggerHapticPulse(1, 20, backHand); + this.pullBackDistance = pullBackDistance; + } // this.changeStringPullSoundVolume(pullBackDistance); - if (pullBackDistance > 0.6) { - pullBackDistance = 0.6; + if (pullBackDistance > DRAW_STRING_MAX_DRAW) { + pullBackDistance = DRAW_STRING_MAX_DRAW; } // //pull the arrow back a bit - var pullBackOffset = Vec3.multiply(handToNotch, -pullBackDistance); - var arrowPosition = Vec3.sum(notchPosition, pullBackOffset); + // var pullBackOffset = Vec3.multiply(handToNotch, -pullBackDistance); + // var arrowPosition = Vec3.sum(notchPosition, pullBackOffset); - // // move it forward a bit - var pushForwardOffset = Vec3.multiply(handToNotch, -ARROW_OFFSET); - var finalArrowPosition = Vec3.sum(arrowPosition, pushForwardOffset); + // // // move it forward a bit + // var pushForwardOffset = Vec3.multiply(handToNotch, -ARROW_OFFSET); + // var finalArrowPosition = Vec3.sum(arrowPosition, pushForwardOffset); //we draw strings to the rear of the arrow - this.setArrowRearPosition(finalArrowPosition, arrowRotation); + // this.setArrowRearPosition(finalArrowPosition, arrowRotation); + + var halfArrowVec = Vec3.multiply(Vec3.normalize(handToNotch), ARROW_DIMENSIONS.z / 2.0); + var arrowPosition = Vec3.sum(stringHandPosition, halfArrowVec); + this.setArrowRearPosition(arrowPosition, arrowRotation); //if we're not shooting, we're updating the arrow's orientation if (shouldReleaseArrow !== true) { Entities.editEntity(this.arrow, { - position: finalArrowPosition, + position: arrowPosition, rotation: arrowRotation - }) + }); } //shoot the arrow - if (shouldReleaseArrow === true) { - var arrowProps = Entities.getEntityProperties(this.arrow); + if (shouldReleaseArrow === true) { // && pullBackDistance >= (NEAR_TO_RELAXED_KNOCK_DISTANCE + NEAR_TO_RELAXED_SCHMITT)) { + var arrowAge = Entities.getEntityProperties(this.arrow, ["age"]).age; - //scale the shot strength by the distance you've pulled the arrow back and set its release velocity to be in the direction of the v + //scale the shot strength by the distance you've pulled the arrow back and set its release velocity to be + // in the direction of the v var arrowForce = this.scaleArrowShotStrength(pullBackDistance); - var handToNotch = Vec3.normalize(handToNotch); + var handToNotchNorm = Vec3.normalize(handToNotch); - var releaseVelocity = Vec3.multiply(handToNotch, arrowForce); + var releaseVelocity = Vec3.multiply(handToNotchNorm, arrowForce); // var releaseVelocity2 = Vec3.multiply() //make the arrow physical, give it gravity, a lifetime, and set our velocity @@ -467,27 +489,37 @@ collisionless: false, collidesWith: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow velocity: releaseVelocity, + parentID: NULL_UUID, gravity: ARROW_GRAVITY, - lifetime: 10, - // position: arrowProps.position, - // rotation: arrowProps.rotation + lifetime: ARROW_LIFETIME + arrowAge, }; - //actually shoot the arrow and play its sound Entities.editEntity(this.arrow, arrowProperties); this.playShootArrowSound(); - var backHand = this.hand === 'left' ? 1 : 0; - var haptic = Controller.triggerShortHapticPulse(1, backHand); + Controller.triggerShortHapticPulse(1, backHand); - //clear the strings back to only the single straight one - this.deleteStrings(); - Entities.editEntity(this.preNotchString, { - visible: true + Entities.addAction("travel-oriented", this.arrow, { + forward: { x: 0, y: 0, z: -1 }, + angularTimeScale: 0.1, + tag: "arrow from hifi-bow", + ttl: ARROW_LIFETIME }); - } + + } // else if (shouldReleaseArrow === true) { + // they released without pulling back; just delete the arrow. + // Entities.deleteEntity(this.arrow); + // this.arrow = null; + // } + + // if (shouldReleaseArrow === true) { + // //clear the strings back to only the single straight one + // Entities.editEntity(this.preNotchString, { + // visible: true + // }); + // } }, @@ -527,7 +559,7 @@ var audioProperties = { volume: this.scaleSoundVolume(pullBackDistance), position: this.bowProperties.position - } + }; this.stringPullInjector.options = audioProperties; }, @@ -537,9 +569,37 @@ var min2 = 0; var max2 = 0.2; return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); - } + }, + handleMessages: function(channel, message, sender) { + if (sender !== MyAvatar.sessionUUID) { + return; + } + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + try { + var data = JSON.parse(message); + var action = data.action; + var hand = data.joint; + var isBackHand = ((_this.hand == "left" && hand == "RightHand") || + (_this.hand == "right" && hand == "LeftHand")); + if ((action == "equip" || action == "grab") && isBackHand) { + _this.backHandBusy = true; + } + if (action == "release" && isBackHand) { + _this.backHandBusy = false; + } + } catch (e) { + print("WARNING: bow.js -- error parsing Hifi-Object-Manipulation message: " + message); + } + } }; - return new Bow(); + var bow = new Bow(); + + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(bow.handleMessages); + + return bow; });