// // bow.js // // This script attaches to a bow that you can pick up with a hand controller. Use your other hand and press the trigger to grab the line, and release the trigger to fire. // // Created by James B. Pollack @imgntn on 10/19/2015 // Copyright 2015 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 ZERO_VEC = { x: 0, y: 0, z: 0 }; var LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000, z: 1000 }; var ARROW_MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/arrow_good.fbx"; var ARROW_COLLISION_HULL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/arrow_good_collision_hull.obj"; var ARROW_SCRIPT_URL = Script.resolvePath('arrow.js'); var ARROW_OFFSET = 0.25; var ARROW_FORCE = 1.25; var ARROW_DIMENSIONS = { x: 0.02, y: 0.02, z: 0.16 }; var ARROW_GRAVITY = { x: 0, y: -9.8, z: 0 }; var TOP_NOTCH_OFFSET = 0.5; var BOTTOM_NOTCH_OFFSET = 0.5; var LINE_DIMENSIONS = { x: 5, y: 5, z: 5 }; var DRAW_STRING_THRESHOLD = 0.80; var TARGET_LINE_LENGTH = 1; var _this; function Bow() { _this = this; return; } // create bow // on pickup, wait for other hand to start trigger pull // then create strings that start at top and bottom of bow // extend them to the other hand position // on trigger release, // create arrow // shoot arrow with velocity relative to distance between hand position and bow // delete lines Bow.prototype = { isGrabbed: false, stringDrawn: false, hasArrow: false, stringData: { currentColor: { red: 0, green: 255, blue: 0 } }, preload: function(entityID) { this.entityID = entityID; }, setLeftHand: function() { if (this.isGrabbed === true) { return false; } this.hand = 'left'; }, setRightHand: function() { if (this.isGrabbed === true) { return false; } this.hand = 'right'; }, 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 }); Entities.editEntity(this.bottomString, { linePoints: [{ x: 0, y: 0, z: 0 }, lineVectors[1]], lineWidth: 5, color: this.stringData.currentColor }); }, createStrings: function() { this.createTopString(); this.createBottomString(); }, deleteStrings: function() { Entities.deleteEntity(this.topString); Entities.deleteEntity(this.bottomString); }, createTopString: function() { var stringProperties = { type: 'Line', position: Vec3.sum(this.bowProperties.position, TOP_NOTCH_OFFSET), dimensions: LINE_DIMENSIONS }; this.topString = Entities.addEntity(stringProperties); }, createBottomString: function() { var stringProperties = { type: 'Line', position: Vec3.sum(this.bowProperties.position, BOTTOM_NOTCH_OFFSET), dimensions: LINE_DIMENSIONS }; this.bottomString = Entities.addEntity(stringProperties); }, updateStringPositions: function() { 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)); var downOffset = Vec3.multiply(downVector, BOTTOM_NOTCH_OFFSET); this.topStringPosition = Vec3.sum(this.bowProperties.position, upOffset); this.bottomStringPosition = Vec3.sum(this.bowProperties.position, downOffset); Entities.editEntity(this.topString, { position: this.topStringPosition }); Entities.editEntity(this.bottomString, { position: this.bottomStringPosition }); }, startNearGrab: function() { if (this.isGrabbed === true) { return false; } this.isGrabbed = true; this.initialHand = this.hand; Entities.editEntity(this.entityID, { userData: JSON.stringify({ grabbableKey: { turnOffOtherHand: true, turnOffOppositeBeam: true, invertSolidWhileHeld: true, } }) }); }, continueNearGrab: function() { this.bowProperties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); this.checkStringHand(); }, releaseGrab: function() { if (this.isGrabbed === true && this.hand === this.initialHand) { this.isGrabbed = false; this.stringDrawn = false; this.deleteStrings(); this.hasArrow = false; Entities.editEntity(this.entityID, { userData: JSON.stringify({ grabbableKey: { turnOffOtherHand: false, turnOffOppositeBeam: true, invertSolidWhileHeld: true, } }) }); } }, 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 if (this.initialHand === 'left') { this.getStringHandPosition = MyAvatar.getRightPalmPosition; this.getStringHandRotation = MyAvatar.getRightPalmRotation; this.getGrabHandPosition = MyAvatar.getLeftPalmPosition; this.getGrabHandRotation = MyAvatar.getLeftPalmRotation; this.stringTriggerAction = Controller.findAction("RIGHT_HAND_CLICK"); } else if (this.initialHand === 'right') { this.getStringHandPosition = MyAvatar.getLeftPalmPosition; this.getStringHandRotation = MyAvatar.getLeftPalmRotation; this.getGrabHandPosition = MyAvatar.getRightPalmPosition; this.getGrabHandRotation = MyAvatar.getRightPalmRotation; this.stringTriggerAction = Controller.findAction("LEFT_HAND_CLICK"); } this.triggerValue = Controller.getActionValue(this.stringTriggerAction); if (this.triggerValue < DRAW_STRING_THRESHOLD && this.stringDrawn === true) { this.stringDrawn = false; this.deleteStrings(); this.hasArrow = false; this.releaseArrow(); } else if (this.triggerValue >= DRAW_STRING_THRESHOLD && this.stringDrawn === true) { this.stringData.handPosition = this.getStringHandPosition(); this.stringData.handRotation = this.getStringHandRotation(); this.stringData.grabHandPosition = this.getGrabHandPosition(); this.stringData.grabHandRotation = this.getGrabHandRotation(); this.drawStrings(); this.updateArrowPosition(); this.createTargetLine(); } else if (this.triggerValue >= DRAW_STRING_THRESHOLD && this.stringDrawn === false) { this.stringDrawn = true; this.createStrings(); this.stringData.handPosition = this.getStringHandPosition(); this.stringData.handRotation = this.getStringHandRotation(); this.stringData.grabHandPosition = this.getGrabHandPosition(); this.stringData.grabHandRotation = this.getGrabHandRotation(); if (this.hasArrow === false) { this.createArrow(); this.hasArrow = true; } this.drawStrings(); this.updateArrowPosition(); this.createTargetLine(); } }, getArrowPosition: function() { var arrowVector = Vec3.subtract(this.bowProperties.position, this.stringData.handPosition); arrowVector = Vec3.normalize(arrowVector); arrowVector = Vec3.multiply(arrowVector, ARROW_OFFSET); var arrowPosition = Vec3.sum(this.stringData.handPosition, arrowVector); return arrowPosition; }, orientationOf: function(vector) { var Y_AXIS = { x: 0, y: 1, z: 0 }; var X_AXIS = { x: 1, y: 0, z: 0 }; var theta = 0.0; var RAD_TO_DEG = 180.0 / Math.PI; var direction, yaw, pitch; direction = Vec3.normalize(vector); yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); return Quat.multiply(yaw, pitch); }, updateArrowPosition: function() { var arrowPosition = this.getArrowPosition(); var handToHand = Vec3.subtract(this.stringData.handPosition, this.stringData.grabHandPosition); var arrowRotation = this.orientationOf(handToHand); Entities.editEntity(this.arrow, { position: arrowPosition, rotation: arrowRotation }); }, createArrow: function() { // print('CREATING ARROW'); var arrowProperties = { name: 'Hifi-Arrow', type: 'Model', shapeType: 'box', modelURL: ARROW_MODEL_URL, dimensions: ARROW_DIMENSIONS, position: this.getArrowPosition(), rotation: this.bowProperties.rotation, collisionsWillMove: false, ignoreForCollisions: true, gravity: ARROW_GRAVITY, // script: ARROW_SCRIPT_URL, lifetime: 40, userData: JSON.stringify({ grabbableKey: { grabbable: false, } }) }; this.arrow = Entities.addEntity(arrowProperties); }, releaseArrow: function() { var handToHand = Vec3.subtract(this.stringData.grabHandPosition, this.stringData.handPosition); var arrowRotation = this.orientationOf(handToHand); // var forwardVec = Quat.getFront(this.arrow.rotation); // forwardVec = Vec3.normalize(forwardVec); var handDistanceAtRelease = Vec3.distance(this.stringData.grabHandPosition, this.stringData.handPosition); print('HAND DISTANCE:: ' + handDistanceAtRelease); var arrowForce = this.scaleArrowShotStrength(handDistanceAtRelease, 0, 2, 20, 50); print('ARROW FORCE::' + arrowForce); var forwardVec = Vec3.multiply(handToHand, arrowForce); var arrowProperties = { // rotation:handToHand, ignoreForCollisions: false, collisionsWillMove: true, gravity: ARROW_GRAVITY, velocity: forwardVec, lifetime: 20 }; Entities.editEntity(this.arrow, arrowProperties); Script.setTimeout(function() { Entities.editEntity(this.arrow, { ignoreForCollisions: false, collisionsWillMove: true, gravity: ARROW_GRAVITY, }); }, 100) }, createTargetLine: function() { var handToHand = Vec3.subtract(this.stringData.handPosition, this.stringData.grabHandPosition); var arrowRotation = this.orientationOf(handToHand); // Entities.addEntity({ // type: "Line", // name: "Debug Line", // dimensions: LINE_ENTITY_DIMENSIONS, // visible: true, // rotation: arrowRotation, // position: this.stringData.handPosition, // linePoints: [ZERO_VEC, Vec3.multiply(-TARGET_LINE_LENGTH, handToHand)], // color: { // red: 255, // green: 0, // blue: 255 // }, // lifetime: 0.1 // }); // Entities.addEntity({ // type: "Line", // name: "Debug Line", // dimensions: LINE_ENTITY_DIMENSIONS, // visible: true, // // rotation: bowRotation, // position: this.bowProperties.position, // linePoints: [ZERO_VEC, Vec3.multiply(TARGET_LINE_LENGTH, { // x: 1, // y: 0, // z: 0 // })], // color: { // red: 255, // green: 0, // blue: 0 // }, // lifetime: 0.1 // }); // Entities.addEntity({ // type: "Line", // name: "Debug Line", // dimensions: LINE_ENTITY_DIMENSIONS, // visible: true, // // rotation: bowRotation, // position: this.bowProperties.position, // linePoints: [ZERO_VEC, Vec3.multiply(TARGET_LINE_LENGTH, { // x: 0, // y: 1, // z: 0 // })], // color: { // red: 0, // green: 255, // blue: 0 // }, // lifetime: 0.1 // }); // Entities.addEntity({ // type: "Line", // name: "Debug Line", // dimensions: LINE_ENTITY_DIMENSIONS, // visible: true, // // rotation:bowRotation, // position: this.bowProperties.position, // linePoints: [ZERO_VEC, Vec3.multiply(TARGET_LINE_LENGTH, { // x: 0, // y: 0, // z: 1 // })], // color: { // red: 0, // green: 0, // blue: 255 // }, // lifetime: 0.1 // }); }, getLocalLineVectors: function() { var topVector = Vec3.subtract(this.stringData.handPosition, this.topStringPosition); var bottomVector = Vec3.subtract(this.stringData.handPosition, this.bottomStringPosition); return [topVector, bottomVector]; }, scaleArrowShotStrength: function(value, min1, max1, min2, max2) { return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); } }; return new Bow(); });