From 2fb318cc5366afe54e8794f33094e5bca6d84969 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 8 Dec 2015 17:52:23 -0800 Subject: [PATCH 01/24] work with clements branch --- examples/controllers/handControllerGrab.js | 161 +++++++++++++++++++-- 1 file changed, 149 insertions(+), 12 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 138240f5d6..20ca3811c0 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -251,9 +251,10 @@ function MyController(hand) { this.triggerValue = 0; // rolling average of trigger value this.rawTriggerValue = 0; this.rawBumperValue = 0; - + this.overlayLine = null; - + this.particleBeam = null; + this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; @@ -398,6 +399,119 @@ function MyController(hand) { } }; + + //test particles instead of overlays + + + this.handleParticleBeam = function(position, orientation) { + + var rotation = Quat.angleAxis(0, { + x: 1, + y: 0, + z: 0 + }); + + var finalRotation = Quat.multiply(orientation, rotation); + + + if (this.particleBeam === null) { + print('create beam') + this.createParticleBeam(position, finalRotation) + } else { + print('update beam') + this.updateParticleBeam(position, finalRotation) + } + } + + this.createParticleBeam = function(position, orientation) { + var particleBeamProperties = { + type: "ParticleEffect", + isEmitting: true, + position: position, + //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + "name": "Particle Beam", + "color": { + "red": 110, + "green": 118.52941176470593, + "blue": 255 + }, + "maxParticles": 1000, + "lifespan": 3, + "emitRate": 20, + "emitSpeed": 10, + "speedSpread": 0, + "emitOrientation": { + "x": -0.7000000000000001, + "y": 0, + "z": 0, + "w": 0.7071068286895752 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitRadiusStart": 0.5, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + x: 0, + y: 0, + z: 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.02, + "radiusSpread": 0, + "radiusStart": 0.01, + "radiusFinish": 0.01, + "colorSpread": { + "red": 0, + "green": 0, + "blue": 0 + }, + "colorStart": { + "red": 110, + "green": 118.52941176470593, + "blue": 255 + }, + "colorFinish": { + "red": 110, + "green": 118.52941176470593, + "blue": 255 + }, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 1, + "additiveBlending": 0, + "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" + } + + this.particleBeam = Entities.addEntity(particleBeamProperties); + } + + this.updateParticleBeam = function(position, orientation, acceleration) { + print('O IN UPDATE:::' + JSON.stringify(orientation)) + + // var beamProps = Entities.getEntityProperties(this.particleBeam); + + Entities.editEntity(this.particleBeam, { + //rotation:rotation, + rotation: orientation, + position: position, + + }) + + // var emitO = Entities.getEntityProperties(this.particleBeam, "emitOrientation").emitOrientation; + // print('EMIT o :::' + JSON.stringify(emitO)); + } + this.lineOff = function() { if (this.pointer !== null) { Entities.deleteEntity(this.pointer); @@ -412,6 +526,14 @@ function MyController(hand) { this.overlayLine = null; }; + this.particleBeamOff = function() { + if (this.particleBeam !== null) { + Entities.deleteEntity(this.particleBeam) + } + + this.particleBeam = null; + } + this.triggerPress = function(value) { _this.rawTriggerValue = value; }; @@ -662,6 +784,9 @@ function MyController(hand) { //this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation()); + + }; this.distanceHolding = function() { @@ -715,6 +840,7 @@ function MyController(hand) { this.currentAvatarOrientation = MyAvatar.orientation; this.overlayLineOff(); + this.particleBeamOff(); }; this.continueDistanceHolding = function() { @@ -808,8 +934,16 @@ function MyController(hand) { // mix in head motion if (MOVE_WITH_HEAD) { var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { x: 0.0, y: 0.0, z: objDistance }); - var after = Vec3.multiplyQbyV(Camera.orientation, { x: 0.0, y: 0.0, z: objDistance }); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); var change = Vec3.subtract(before, after); this.currentCameraOrientation = Camera.orientation; this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); @@ -837,6 +971,7 @@ function MyController(hand) { this.lineOff(); this.overlayLineOff(); + this.particleBeamOff(); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties); @@ -975,6 +1110,7 @@ function MyController(hand) { this.pullTowardEquipPosition = function() { this.lineOff(); this.overlayLineOff(); + this.particleBeamOff(); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); @@ -1164,6 +1300,7 @@ function MyController(hand) { this.lineOff(); this.overlayLineOff(); + this.particleBeamOff(); if (this.grabbedEntity !== null) { if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -1285,10 +1422,10 @@ Controller.enableMapping(MAPPING_NAME); var handToDisable = 'none'; function update() { - if (handToDisable !== LEFT_HAND && handToDisable!=='both') { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { leftController.update(); } - if (handToDisable !== RIGHT_HAND && handToDisable!=='both') { + if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { rightController.update(); } } @@ -1296,7 +1433,7 @@ function update() { Messages.subscribe('Hifi-Hand-Disabler'); handleHandDisablerMessages = function(channel, message, sender) { - + if (sender === MyAvatar.sessionUUID) { if (message === 'left') { handToDisable = LEFT_HAND; @@ -1304,11 +1441,11 @@ handleHandDisablerMessages = function(channel, message, sender) { if (message === 'right') { handToDisable = RIGHT_HAND; } - if(message==='both'){ - handToDisable='both'; + if (message === 'both') { + handToDisable = 'both'; } - if(message==='none'){ - handToDisable='none'; + if (message === 'none') { + handToDisable = 'none'; } } @@ -1323,4 +1460,4 @@ function cleanup() { } Script.scriptEnding.connect(cleanup); -Script.update.connect(update); +Script.update.connect(update); \ No newline at end of file From 1134d12ea0e9f92de38646e6d45c45e91f86bac3 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 9 Dec 2015 16:08:32 -0800 Subject: [PATCH 02/24] particles --- examples/controllers/handControllerGrab.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 20ca3811c0..b2af86c479 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -399,10 +399,7 @@ function MyController(hand) { } }; - //test particles instead of overlays - - this.handleParticleBeam = function(position, orientation) { var rotation = Quat.angleAxis(0, { @@ -435,11 +432,11 @@ function MyController(hand) { "green": 118.52941176470593, "blue": 255 }, - "maxParticles": 1000, + "maxParticles": 2000, "lifespan": 3, - "emitRate": 20, - "emitSpeed": 10, - "speedSpread": 0, + "emitRate": 50, + "emitSpeed": 2, + "speedSpread": 0.1, "emitOrientation": { "x": -0.7000000000000001, "y": 0, From 31526091d323a5ed8ad5038fa533697a6ac257bf Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 11:13:35 -0800 Subject: [PATCH 03/24] create beams at start --- examples/controllers/handControllerGrab.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index b2af86c479..4348722df0 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -425,6 +425,7 @@ function MyController(hand) { type: "ParticleEffect", isEmitting: true, position: position, + visible: false, //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": { @@ -436,12 +437,12 @@ function MyController(hand) { "lifespan": 3, "emitRate": 50, "emitSpeed": 2, - "speedSpread": 0.1, + "speedSpread": 0, "emitOrientation": { - "x": -0.7000000000000001, + "x": -1, "y": 0, "z": 0, - "w": 0.7071068286895752 + "w": 1 }, "emitDimensions": { "x": 0, @@ -486,7 +487,7 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 0, + "additiveBlending": 1, "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" } @@ -502,6 +503,7 @@ function MyController(hand) { //rotation:rotation, rotation: orientation, position: position, + visible: true }) @@ -525,10 +527,12 @@ function MyController(hand) { this.particleBeamOff = function() { if (this.particleBeam !== null) { - Entities.deleteEntity(this.particleBeam) + Entities.editEntity(this.particleBeam, { + visible: false + }) } - this.particleBeam = null; + //this.particleBeam = null; } this.triggerPress = function(value) { @@ -1314,6 +1318,7 @@ function MyController(hand) { this.cleanup = function() { this.release(); this.endHandGrasp(); + Entities.deleteEntity(this.particleBeam); }; this.activateEntity = function(entityID, grabbedProperties) { @@ -1404,6 +1409,8 @@ function MyController(hand) { var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); +rightController.createParticleBeam(); +leftController.createParticleBeam(); var MAPPING_NAME = "com.highfidelity.handControllerGrab"; From 1000b602808f4a487012be55df9ec685824a8f29 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 12:21:50 -0800 Subject: [PATCH 04/24] far beams --- examples/controllers/handControllerGrab.js | 65 ++++++++++++---------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 4348722df0..812eef07f8 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -400,7 +400,7 @@ function MyController(hand) { }; //test particles instead of overlays - this.handleParticleBeam = function(position, orientation) { + this.handleParticleBeam = function(position, orientation, color) { var rotation = Quat.angleAxis(0, { x: 1, @@ -409,18 +409,36 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); + // var finalRotation = orientation + + if (this.particleBeam === null) { + print('create beam') + this.createParticleBeam(position, finalRotation, color) + } else { + print('update beam') + this.updateParticleBeam(position, finalRotation, color) + } + } + + this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + + + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1,Vec3.UP), handToObject); + + if (this.particleBeam === null) { print('create beam') - this.createParticleBeam(position, finalRotation) + this.createParticleBeam(objectPosition, finalRotation, color) } else { print('update beam') - this.updateParticleBeam(position, finalRotation) + this.updateParticleBeam(objectPosition, finalRotation, color) } } - this.createParticleBeam = function(position, orientation) { + this.createParticleBeam = function(position, orientation, color) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, @@ -428,15 +446,11 @@ function MyController(hand) { visible: false, //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", - "color": { - "red": 110, - "green": 118.52941176470593, - "blue": 255 - }, + "color": color, "maxParticles": 2000, "lifespan": 3, "emitRate": 50, - "emitSpeed": 2, + "emitSpeed": 20, "speedSpread": 0, "emitOrientation": { "x": -1, @@ -473,16 +487,8 @@ function MyController(hand) { "green": 0, "blue": 0 }, - "colorStart": { - "red": 110, - "green": 118.52941176470593, - "blue": 255 - }, - "colorFinish": { - "red": 110, - "green": 118.52941176470593, - "blue": 255 - }, + "colorStart": color, + "colorFinish": color, "alpha": 1, "alphaSpread": 0, "alphaStart": 1, @@ -494,21 +500,19 @@ function MyController(hand) { this.particleBeam = Entities.addEntity(particleBeamProperties); } - this.updateParticleBeam = function(position, orientation, acceleration) { + this.updateParticleBeam = function(position, orientation, color) { print('O IN UPDATE:::' + JSON.stringify(orientation)) // var beamProps = Entities.getEntityProperties(this.particleBeam); Entities.editEntity(this.particleBeam, { - //rotation:rotation, rotation: orientation, position: position, - visible: true + visible: true, + color: color }) - // var emitO = Entities.getEntityProperties(this.particleBeam, "emitOrientation").emitOrientation; - // print('EMIT o :::' + JSON.stringify(emitO)); } this.lineOff = function() { @@ -784,8 +788,8 @@ function MyController(hand) { } //this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation()); + //this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); }; @@ -867,9 +871,8 @@ function MyController(hand) { return; } - this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - - // the action was set up on a previous call. update the targets. + // this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + // the action was set up on a previous call. update the targets. var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; if (radius < 1.0) { @@ -950,6 +953,8 @@ function MyController(hand) { this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } + this.handleDistantParticleBeam(handPosition, grabbedProperties.position,this.currentObjectRotation, INTERSECT_COLOR) + Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, From c3e9cde7fbc59ce8dafb0f8c7c443212bd08662c Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 13:44:53 -0800 Subject: [PATCH 05/24] beamz --- examples/controllers/handControllerGrab.js | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 812eef07f8..1106295cde 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -423,22 +423,25 @@ function MyController(hand) { this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { - var handToObject = Vec3.subtract(objectPosition, handPosition); - var finalRotation = Quat.rotationBetween(Vec3.multiply(-1,Vec3.UP), handToObject); - + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); + var distance = Vec3.distance(handPosition, objectPosition); + var speed = distance * 1; + var lifepsan = distance / speed; + var lifespan = 1; if (this.particleBeam === null) { print('create beam') - this.createParticleBeam(objectPosition, finalRotation, color) + this.createParticleBeam(objectPosition, finalRotation, color, speed) } else { print('update beam') - this.updateParticleBeam(objectPosition, finalRotation, color) + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan) } } - this.createParticleBeam = function(position, orientation, color) { + this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, @@ -448,9 +451,9 @@ function MyController(hand) { "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": 3, + "lifespan": 1, "emitRate": 50, - "emitSpeed": 20, + "emitSpeed": 1, "speedSpread": 0, "emitOrientation": { "x": -1, @@ -500,7 +503,7 @@ function MyController(hand) { this.particleBeam = Entities.addEntity(particleBeamProperties); } - this.updateParticleBeam = function(position, orientation, color) { + this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { print('O IN UPDATE:::' + JSON.stringify(orientation)) // var beamProps = Entities.getEntityProperties(this.particleBeam); @@ -509,7 +512,9 @@ function MyController(hand) { rotation: orientation, position: position, visible: true, - color: color + color: color, + emitSpeed: speed, + lifepsan: lifepsan }) @@ -871,8 +876,8 @@ function MyController(hand) { return; } - // this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - // the action was set up on a previous call. update the targets. + // this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + // the action was set up on a previous call. update the targets. var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; if (radius < 1.0) { @@ -953,7 +958,7 @@ function MyController(hand) { this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } - this.handleDistantParticleBeam(handPosition, grabbedProperties.position,this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, From efaa30a509148a6f0da8921d88c0c4c732aa04ca Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 16:18:40 -0800 Subject: [PATCH 06/24] rotatable light --- examples/controllers/handControllerGrab.js | 91 ++++++++++++++++++---- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index d92cb35b43..4e612799f7 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -116,6 +116,17 @@ var DEFAULT_GRABBABLE_DATA = { invertSolidWhileHeld: false }; +var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 +}; +var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 +}); + // states for the state machine var STATE_OFF = 0; @@ -137,6 +148,7 @@ var STATE_WAITING_FOR_BUMPER_RELEASE = 15; var STATE_EQUIP_SPRING = 16; + function stateToName(state) { switch (state) { case STATE_OFF: @@ -218,6 +230,7 @@ function getSpatialOffsetPosition(hand, spatialKey) { } var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); + function getSpatialOffsetRotation(hand, spatialKey) { var rotation = Quat.IDENTITY; @@ -264,7 +277,8 @@ function MyController(hand) { this.overlayLine = null; this.particleBeam = null; - + this.spotlight = null; + this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; @@ -420,7 +434,6 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - // var finalRotation = orientation if (this.particleBeam === null) { print('create beam') @@ -433,7 +446,6 @@ function MyController(hand) { this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { - var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); @@ -453,6 +465,7 @@ function MyController(hand) { } this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, @@ -463,7 +476,7 @@ function MyController(hand) { "color": color, "maxParticles": 2000, "lifespan": 1, - "emitRate": 50, + "emitRate": 15, "emitSpeed": 1, "speedSpread": 0, "emitOrientation": { @@ -494,21 +507,21 @@ function MyController(hand) { }, "particleRadius": 0.02, "radiusSpread": 0, - "radiusStart": 0.01, - "radiusFinish": 0.01, - "colorSpread": { - "red": 0, - "green": 0, - "blue": 0 - }, - "colorStart": color, - "colorFinish": color, + // "radiusStart": 0.01, + // "radiusFinish": 0.01, + // "colorSpread": { + // "red": 0, + // "green": 0, + // "blue": 0 + // }, + // "colorStart": color, + // "colorFinish": color, "alpha": 1, "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, "additiveBlending": 1, - "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" + "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); @@ -531,6 +544,49 @@ function MyController(hand) { } + + this.evalLightWorldTransform = function(modelPos, modelRot) { + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + } + + this.createSpotlight = function(parentID, position) { + var LIFETIME = 100; + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + //this light casts the beam + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + rotation: lightTransform.q, + }; + + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + + } + } + this.lineOff = function() { if (this.pointer !== null) { Entities.deleteEntity(this.pointer); @@ -555,6 +611,11 @@ function MyController(hand) { //this.particleBeam = null; } + this.spotlightOff = function() { + // Entities.deleteEntity(this.spotlight); + // this.spotlight = null; + } + this.triggerPress = function(value) { _this.rawTriggerValue = value; }; @@ -970,6 +1031,7 @@ function MyController(hand) { } this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.createSpotlight(this.grabbedEntity); Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, @@ -1331,6 +1393,7 @@ function MyController(hand) { this.lineOff(); this.overlayLineOff(); this.particleBeamOff(); + this.spotlightOff(); if (this.grabbedEntity !== null) { if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); From 16ab7e74de0c12813694e4f006c21f8c89e6f739 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 16:53:13 -0800 Subject: [PATCH 07/24] cleanup spotlights --- examples/controllers/handControllerGrab.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 4e612799f7..b787d8f0df 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -476,7 +476,7 @@ function MyController(hand) { "color": color, "maxParticles": 2000, "lifespan": 1, - "emitRate": 15, + "emitRate": 300, "emitSpeed": 1, "speedSpread": 0, "emitOrientation": { @@ -505,7 +505,7 @@ function MyController(hand) { "y": 0, "z": 0 }, - "particleRadius": 0.02, + "particleRadius": 0.01, "radiusSpread": 0, // "radiusStart": 0.01, // "radiusFinish": 0.01, @@ -583,7 +583,10 @@ function MyController(hand) { if (this.spotlight === null) { this.spotlight = Entities.addEntity(lightProperties); } else { - + // var rotationBetween = Quat.rotationBetween(Vec3.UP, ) + // Entities.editEntity(this.spotlight, { + // rotation: lightTransform.q + // }) } } @@ -612,8 +615,10 @@ function MyController(hand) { } this.spotlightOff = function() { - // Entities.deleteEntity(this.spotlight); - // this.spotlight = null; + if (this.spotlight !== null) { + Overlays.deleteOverlay(this.spotlight); + } + this.spotlight = null; } this.triggerPress = function(value) { From 2acc0df1f0bf29315d146b63d4e0f46342f63f37 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 10 Dec 2015 17:39:29 -0800 Subject: [PATCH 08/24] beams with spotlights --- examples/controllers/handControllerGrab.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index b787d8f0df..58eeb83b0e 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -559,7 +559,7 @@ function MyController(hand) { //this light casts the beam var lightProperties = { type: "Light", - isSpotlight: true, + isSpotlight: false, dimensions: { x: 2, y: 2, @@ -576,17 +576,18 @@ function MyController(hand) { cutoff: 20, lifetime: LIFETIME, position: lightTransform.p, - rotation: lightTransform.q, + // rotation: lightTransform.q, }; if (this.spotlight === null) { this.spotlight = Entities.addEntity(lightProperties); } else { - // var rotationBetween = Quat.rotationBetween(Vec3.UP, ) - // Entities.editEntity(this.spotlight, { - // rotation: lightTransform.q - // }) + Entities.editEntity(this.spotlight, { + parentID:parentID, + position:lightTransform.p, + visible:true + }) } } @@ -616,9 +617,13 @@ function MyController(hand) { this.spotlightOff = function() { if (this.spotlight !== null) { - Overlays.deleteOverlay(this.spotlight); + print('SHOULD DELETE SPOTLIGHT' + this.spotlight) + Entities.editEntity(this.spotlight,{ + visible:false + }) + //Entities.deleteEntity(this.spotlight); } - this.spotlight = null; + //this.spotlight = null; } this.triggerPress = function(value) { @@ -1416,6 +1421,7 @@ function MyController(hand) { this.release(); this.endHandGrasp(); Entities.deleteEntity(this.particleBeam); + Entities.deleteEntity(this.spotLight); }; this.activateEntity = function(entityID, grabbedProperties) { From 4e9d81f18737203e4bbdd60343aab98d65a210a1 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 11 Dec 2015 11:38:16 -0800 Subject: [PATCH 09/24] add support for various beam visualizations --- examples/controllers/handControllerGrab.js | 269 ++++++++++++++------- 1 file changed, 178 insertions(+), 91 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 58eeb83b0e..9e2f55b238 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -116,17 +116,14 @@ var DEFAULT_GRABBABLE_DATA = { invertSolidWhileHeld: false }; -var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 -}; -var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 -}); - +//we've created various ways of visualizing looking for and moving distant objects +var USE_ENTITY_LASERS_FOR_SEARCHING = false; +var USE_ENTITY_LASERS_FOR_MOVING = true; +var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_PARTICLE_BEAM_FOR_SEARCHING = false; +var USE_PARTICLE_BEAM_FOR_MOVING = false; +var USE_SPOTLIGHT = false; +var USE_POINTLIGHT = false; // states for the state machine var STATE_OFF = 0; @@ -275,9 +272,13 @@ function MyController(hand) { this.rawTriggerValue = 0; this.rawBumperValue = 0; + //for visualizations this.overlayLine = null; this.particleBeam = null; + + //for lights this.spotlight = null; + this.pointlight = null; this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; @@ -366,33 +367,6 @@ function MyController(hand) { }); } - this.overlayLineOn = function(closePoint, farPoint, color) { - if (this.overlayLine === null) { - var lineProperties = { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - visible: true, - alpha: 1 - }; - - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.overlayLine, { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - visible: true, - ignoreRayIntersection: true, // always ignore this - alpha: 1 - }); - } - } - this.lineOn = function(closePoint, farPoint, color) { // draw a line if (this.pointer === null) { @@ -424,7 +398,33 @@ function MyController(hand) { } }; - //test particles instead of overlays + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine === null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1 + }; + + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.overlayLine, { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1 + }); + } + } + this.handleParticleBeam = function(position, orientation, color) { var rotation = Quat.angleAxis(0, { @@ -437,10 +437,10 @@ function MyController(hand) { if (this.particleBeam === null) { print('create beam') - this.createParticleBeam(position, finalRotation, color) + this.createParticleBeam(position, finalRotation, color); } else { print('update beam') - this.updateParticleBeam(position, finalRotation, color) + this.updateParticleBeam(position, finalRotation, color); } } @@ -456,11 +456,9 @@ function MyController(hand) { var lifespan = 1; if (this.particleBeam === null) { - print('create beam') - this.createParticleBeam(objectPosition, finalRotation, color, speed) + this.createParticleBeam(objectPosition, finalRotation, color, speed); } else { - print('update beam') - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan) + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); } } @@ -475,10 +473,10 @@ function MyController(hand) { "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": 1, - "emitRate": 300, - "emitSpeed": 1, - "speedSpread": 0, + "lifespan": LINE_LENGTH / 10, + "emitRate": 50, + "emitSpeed": 5, + "speedSpread": 2, "emitOrientation": { "x": -1, "y": 0, @@ -528,9 +526,6 @@ function MyController(hand) { } this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - print('O IN UPDATE:::' + JSON.stringify(orientation)) - - // var beamProps = Entities.getEntityProperties(this.particleBeam); Entities.editEntity(this.particleBeam, { rotation: orientation, @@ -544,19 +539,72 @@ function MyController(hand) { } - this.evalLightWorldTransform = function(modelPos, modelRot) { + + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + return { p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) }; } - this.createSpotlight = function(parentID, position) { + this.handleSpotlight = function(parentID, position) { var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + Entities.editEntity(this.spotlight, { + parentID: parentID, + position: lightTransform.p, + //without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + visible: true + }) + } + } + + this.handlePointLight = function(parentID, position) { + var LIFETIME = 100; + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - //this light casts the beam + var lightProperties = { type: "Light", isSpotlight: false, @@ -576,17 +624,18 @@ function MyController(hand) { cutoff: 20, lifetime: LIFETIME, position: lightTransform.p, - // rotation: lightTransform.q, }; + if (this.pointlight === null) { - if (this.spotlight === null) { - this.spotlight = Entities.addEntity(lightProperties); + print('create pointlight') + this.pointlight = Entities.addEntity(lightProperties); } else { - Entities.editEntity(this.spotlight, { - parentID:parentID, - position:lightTransform.p, - visible:true + print('update pointlight') + Entities.editEntity(this.pointlight, { + parentID: parentID, + position: lightTransform.p, + visible: true }) } } @@ -611,19 +660,25 @@ function MyController(hand) { visible: false }) } - - //this.particleBeam = null; } - this.spotlightOff = function() { + this.turnLightsOff = function() { + //use visibility for now instead of creating and deleting since deleting seems to crash if (this.spotlight !== null) { - print('SHOULD DELETE SPOTLIGHT' + this.spotlight) - Entities.editEntity(this.spotlight,{ - visible:false - }) - //Entities.deleteEntity(this.spotlight); + Entities.editEntity(this.spotlight, { + visible: false + }) + //Entities.deleteEntity(this.spotlight); + //this.spotLight=null; + } + + if (this.pointlight !== null) { + Entities.editEntity(this.pointlight, { + visible: false + }) + //Entities.deleteEntity(this.pointlight); + //this.pointlight = null; } - //this.spotlight = null; } this.triggerPress = function(value) { @@ -634,7 +689,6 @@ function MyController(hand) { _this.rawBumperValue = value; }; - this.updateSmoothedTrigger = function() { var triggerValue = this.rawTriggerValue; // smooth out trigger value @@ -663,7 +717,6 @@ function MyController(hand) { return _this.rawBumperValue < BUMPER_ON_VALUE; } - this.off = function() { if (this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; @@ -874,10 +927,18 @@ function MyController(hand) { } } - //this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - //this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + //search line visualizations + if (USE_ENTITY_LASERS_FOR_SEARCHING === true) { + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + } }; @@ -931,10 +992,22 @@ function MyController(hand) { this.currentAvatarPosition = MyAvatar.position; this.currentAvatarOrientation = MyAvatar.orientation; - this.overlayLineOff(); - this.particleBeamOff(); + this.turnOffVisualizations(); }; + this.turnOffVisualizations = function() { + if (USE_ENTITY_LASERS_FOR_SEARCHING === true || USE_ENTITY_LASERS_FOR_MOVING === true) { + this.lineOff(); + } + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + } + this.continueDistanceHolding = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); @@ -958,7 +1031,7 @@ function MyController(hand) { return; } - // this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + // the action was set up on a previous call. update the targets. var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; @@ -1040,8 +1113,22 @@ function MyController(hand) { this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) - this.createSpotlight(this.grabbedEntity); + + //visualizations + if (USE_ENTITY_LASERS_FOR_MOVING === true) { + this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + } + + if (USE_POINTLIGHT === true) { + this.handlePointLight(this.grabbedEntity); + } + + if (USE_SPOTLIGHT === true) { + this.handleSpotlight(this.grabbedEntity); + } Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, @@ -1050,6 +1137,7 @@ function MyController(hand) { angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, ttl: ACTION_TTL }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); }; @@ -1063,9 +1151,7 @@ function MyController(hand) { return; } - this.lineOff(); - this.overlayLineOff(); - this.particleBeamOff(); + this.turnOffVisualizations(); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties); @@ -1207,9 +1293,8 @@ function MyController(hand) { }; this.pullTowardEquipPosition = function() { - this.lineOff(); - this.overlayLineOff(); - this.particleBeamOff(); + + this.turnOffVisualizations(); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); @@ -1400,10 +1485,9 @@ function MyController(hand) { this.release = function() { - this.lineOff(); - this.overlayLineOff(); - this.particleBeamOff(); - this.spotlightOff(); + this.turnLightsOff(); + this.turnOffVisualizations(); + if (this.grabbedEntity !== null) { if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -1422,6 +1506,7 @@ function MyController(hand) { this.endHandGrasp(); Entities.deleteEntity(this.particleBeam); Entities.deleteEntity(this.spotLight); + Entities.deleteEntity(this.pointLight); }; this.activateEntity = function(entityID, grabbedProperties) { @@ -1512,6 +1597,8 @@ function MyController(hand) { var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); + +//reload the particle beams rightController.createParticleBeam(); leftController.createParticleBeam(); From 11aaeb1ec00a76273f639648d47a00e4f691e0aa Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 11 Dec 2015 14:48:07 -0800 Subject: [PATCH 10/24] only create particle beams if set --- examples/controllers/handControllerGrab.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 9e2f55b238..217b5b6632 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -1599,8 +1599,10 @@ var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); //reload the particle beams -rightController.createParticleBeam(); -leftController.createParticleBeam(); +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + rightController.createParticleBeam(); + leftController.createParticleBeam(); +} var MAPPING_NAME = "com.highfidelity.handControllerGrab"; From 1b1edf6f7ab8a8e94e7d78df9eaf2b42d81c703b Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 11 Dec 2015 17:23:35 -0800 Subject: [PATCH 11/24] actually delete lights now that it doesnt crash --- examples/controllers/handControllerGrab.js | 30 +++++----------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 217b5b6632..ae95ab47fd 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -122,7 +122,7 @@ var USE_ENTITY_LASERS_FOR_MOVING = true; var USE_OVERLAY_LINES_FOR_SEARCHING = true; var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_PARTICLE_BEAM_FOR_MOVING = false; -var USE_SPOTLIGHT = false; +var USE_SPOTLIGHT = true; var USE_POINTLIGHT = false; // states for the state machine @@ -436,10 +436,8 @@ function MyController(hand) { var finalRotation = Quat.multiply(orientation, rotation); if (this.particleBeam === null) { - print('create beam') this.createParticleBeam(position, finalRotation, color); } else { - print('update beam') this.updateParticleBeam(position, finalRotation, color); } } @@ -590,11 +588,8 @@ function MyController(hand) { this.spotlight = Entities.addEntity(lightProperties); } else { Entities.editEntity(this.spotlight, { - parentID: parentID, - position: lightTransform.p, //without this, this light would maintain rotation with its parent rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - visible: true }) } } @@ -627,16 +622,9 @@ function MyController(hand) { }; if (this.pointlight === null) { - - print('create pointlight') this.pointlight = Entities.addEntity(lightProperties); } else { - print('update pointlight') - Entities.editEntity(this.pointlight, { - parentID: parentID, - position: lightTransform.p, - visible: true - }) + } } @@ -665,19 +653,13 @@ function MyController(hand) { this.turnLightsOff = function() { //use visibility for now instead of creating and deleting since deleting seems to crash if (this.spotlight !== null) { - Entities.editEntity(this.spotlight, { - visible: false - }) - //Entities.deleteEntity(this.spotlight); - //this.spotLight=null; + Entities.deleteEntity(this.spotlight); + this.spotlight = null; } if (this.pointlight !== null) { - Entities.editEntity(this.pointlight, { - visible: false - }) - //Entities.deleteEntity(this.pointlight); - //this.pointlight = null; + Entities.deleteEntity(this.pointlight); + this.pointlight = null; } } From 70a0e51cc9b5898dc49b1134f84a6f96d50b7aff Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Fri, 11 Dec 2015 17:59:03 -0800 Subject: [PATCH 12/24] back to default state --- examples/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index ae95ab47fd..fc217de03a 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -122,7 +122,7 @@ var USE_ENTITY_LASERS_FOR_MOVING = true; var USE_OVERLAY_LINES_FOR_SEARCHING = true; var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_PARTICLE_BEAM_FOR_MOVING = false; -var USE_SPOTLIGHT = true; +var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; // states for the state machine From fe05542440f2c74668cbec2859c2c8227eb91e42 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 12 Dec 2015 14:30:34 -0800 Subject: [PATCH 13/24] more vis --- examples/controllers/handControllerGrab.js | 80 ++++++++++++---------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index fc217de03a..afad62412e 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -1,5 +1,4 @@ // handControllerGrab.js -// examples // // Created by Eric Levin on 9/2/15 // Additions by James B. Pollack @imgntn on 9/24/2015 @@ -7,6 +6,7 @@ // Copyright 2015 High Fidelity, Inc. // // Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// Also supports touch and equipping objects. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -14,7 +14,6 @@ Script.include("../libraries/utils.js"); - // // add lines where the hand ray picking is happening // @@ -54,6 +53,7 @@ var LINE_ENTITY_DIMENSIONS = { y: 1000, z: 1000 }; + var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray @@ -120,6 +120,7 @@ var DEFAULT_GRABBABLE_DATA = { var USE_ENTITY_LASERS_FOR_SEARCHING = false; var USE_ENTITY_LASERS_FOR_MOVING = true; var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_PARTICLE_BEAM_FOR_MOVING = false; var USE_SPOTLIGHT = false; @@ -345,7 +346,7 @@ function MyController(hand) { print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); } this.state = newState; - } + }; this.debugLine = function(closePoint, farPoint, color) { Entities.addEntity({ @@ -365,7 +366,7 @@ function MyController(hand) { } }) }); - } + }; this.lineOn = function(closePoint, farPoint, color) { // draw a line @@ -423,7 +424,7 @@ function MyController(hand) { alpha: 1 }); } - } + }; this.handleParticleBeam = function(position, orientation, color) { @@ -440,7 +441,7 @@ function MyController(hand) { } else { this.updateParticleBeam(position, finalRotation, color); } - } + }; this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { @@ -458,7 +459,7 @@ function MyController(hand) { } else { this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); } - } + }; this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { @@ -521,7 +522,7 @@ function MyController(hand) { } this.particleBeam = Entities.addEntity(particleBeamProperties); - } + }; this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { @@ -535,7 +536,7 @@ function MyController(hand) { }) - } + }; this.evalLightWorldTransform = function(modelPos, modelRot) { @@ -555,7 +556,7 @@ function MyController(hand) { p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) }; - } + }; this.handleSpotlight = function(parentID, position) { var LIFETIME = 100; @@ -592,7 +593,7 @@ function MyController(hand) { rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), }) } - } + }; this.handlePointLight = function(parentID, position) { var LIFETIME = 100; @@ -626,7 +627,7 @@ function MyController(hand) { } else { } - } + }; this.lineOff = function() { if (this.pointer !== null) { @@ -651,7 +652,6 @@ function MyController(hand) { } this.turnLightsOff = function() { - //use visibility for now instead of creating and deleting since deleting seems to crash if (this.spotlight !== null) { Entities.deleteEntity(this.spotlight); this.spotlight = null; @@ -661,7 +661,22 @@ function MyController(hand) { Entities.deleteEntity(this.pointlight); this.pointlight = null; } - } + }; + + + this.turnOffVisualizations = function() { + if (USE_ENTITY_LASERS_FOR_SEARCHING === true || USE_ENTITY_LASERS_FOR_MOVING === true) { + this.lineOff(); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + }; this.triggerPress = function(value) { _this.rawTriggerValue = value; @@ -693,11 +708,11 @@ function MyController(hand) { this.bumperSqueezed = function() { return _this.rawBumperValue > BUMPER_ON_VALUE; - } + }; this.bumperReleased = function() { return _this.rawBumperValue < BUMPER_ON_VALUE; - } + }; this.off = function() { if (this.triggerSmoothedSqueezed()) { @@ -710,7 +725,7 @@ function MyController(hand) { this.setState(STATE_EQUIP_SEARCHING); return; } - } + }; this.search = function() { this.grabbedEntity = null; @@ -977,19 +992,6 @@ function MyController(hand) { this.turnOffVisualizations(); }; - this.turnOffVisualizations = function() { - if (USE_ENTITY_LASERS_FOR_SEARCHING === true || USE_ENTITY_LASERS_FOR_MOVING === true) { - this.lineOff(); - } - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOff(); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.particleBeamOff(); - } - } - this.continueDistanceHolding = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); @@ -1100,14 +1102,15 @@ function MyController(hand) { if (USE_ENTITY_LASERS_FOR_MOVING === true) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); } + if (USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) } - if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); } - if (USE_SPOTLIGHT === true) { this.handleSpotlight(this.grabbedEntity); } @@ -1399,6 +1402,7 @@ function MyController(hand) { }; _this.allTouchedIDs = {}; + this.touchTest = function() { var maxDistance = 0.05; var leftHandPosition = MyAvatar.getLeftPalmPosition(); @@ -1559,28 +1563,29 @@ function MyController(hand) { } //return an object with our updated settings return result; - } + }; this.graspHandler = null + this.startHandGrasp = function() { if (this.hand === RIGHT_HAND) { this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); } else if (this.hand === LEFT_HAND) { this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); } - } + }; this.endHandGrasp = function() { // Tell the animation system we don't need any more callbacks. MyAvatar.removeAnimationStateHandler(this.graspHandler); - } + }; -} +}; var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); -//reload the particle beams +//preload the particle beams so that they are full length when you start searching if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { rightController.createParticleBeam(); leftController.createParticleBeam(); @@ -1597,6 +1602,7 @@ mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); Controller.enableMapping(MAPPING_NAME); +//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items var handToDisable = 'none'; function update() { From 0ddc2ba76e22359f202cb2205efbf6da24ec08a1 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 12 Dec 2015 16:18:15 -0800 Subject: [PATCH 14/24] rename lasers to lines --- examples/controllers/handControllerGrab.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index afad62412e..f8fe1b821c 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -117,8 +117,8 @@ var DEFAULT_GRABBABLE_DATA = { }; //we've created various ways of visualizing looking for and moving distant objects -var USE_ENTITY_LASERS_FOR_SEARCHING = false; -var USE_ENTITY_LASERS_FOR_MOVING = true; +var USE_ENTITY_LINES_FOR_SEARCHING = false; +var USE_ENTITY_LINES_FOR_MOVING = true; var USE_OVERLAY_LINES_FOR_SEARCHING = true; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_SEARCHING = false; @@ -665,7 +665,7 @@ function MyController(hand) { this.turnOffVisualizations = function() { - if (USE_ENTITY_LASERS_FOR_SEARCHING === true || USE_ENTITY_LASERS_FOR_MOVING === true) { + if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); } @@ -925,7 +925,7 @@ function MyController(hand) { } //search line visualizations - if (USE_ENTITY_LASERS_FOR_SEARCHING === true) { + if (USE_ENTITY_LINES_FOR_SEARCHING === true) { this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } @@ -1099,7 +1099,7 @@ function MyController(hand) { //visualizations - if (USE_ENTITY_LASERS_FOR_MOVING === true) { + if (USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); } if (USE_OVERLAY_LINES_FOR_MOVING === true) { From a0b698c0283ce162720ad13465d04ccb8105439d Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 12 Dec 2015 17:33:54 -0800 Subject: [PATCH 15/24] reorg props --- examples/controllers/handControllerGrab.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index f8fe1b821c..382d5cac7a 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -118,11 +118,13 @@ var DEFAULT_GRABBABLE_DATA = { //we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_ENTITY_LINES_FOR_MOVING = true; var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_SEARCHING = false; + +var USE_ENTITY_LINES_FOR_MOVING = true; +var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = false; + var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; From e43b60f9f54c27b72e2377f32defe489647a415f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 14 Dec 2015 11:40:47 -0800 Subject: [PATCH 16/24] other variations --- examples/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 382d5cac7a..cd82db22b9 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -1105,7 +1105,7 @@ function MyController(hand) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); } if (USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) From 4d9cb6a6df3f3c07866ca4d6fa2170b939f7196b Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 14 Dec 2015 11:41:34 -0800 Subject: [PATCH 17/24] other variations --- .../handControllerGrab-all-overlays.js | 1649 +++++++++++++++++ .../handControllerGrab-particles.js | 1649 +++++++++++++++++ .../handControllerGrab-pointlight.js | 1649 +++++++++++++++++ .../handControllerGrab-spotlight.js | 1649 +++++++++++++++++ 4 files changed, 6596 insertions(+) create mode 100644 examples/controllers/handControllerGrab-all-overlays.js create mode 100644 examples/controllers/handControllerGrab-particles.js create mode 100644 examples/controllers/handControllerGrab-pointlight.js create mode 100644 examples/controllers/handControllerGrab-spotlight.js diff --git a/examples/controllers/handControllerGrab-all-overlays.js b/examples/controllers/handControllerGrab-all-overlays.js new file mode 100644 index 0000000000..acb47b2260 --- /dev/null +++ b/examples/controllers/handControllerGrab-all-overlays.js @@ -0,0 +1,1649 @@ +// handControllerGrab.js +// +// Created by Eric Levin on 9/2/15 +// Additions by James B. Pollack @imgntn on 9/24/2015 +// Additions By Seth Alves on 10/20/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// Also supports touch and equipping objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ + +Script.include("../libraries/utils.js"); + +// +// add lines where the hand ray picking is happening +// +var WANT_DEBUG = false; + +// +// these tune time-averaging and "on" value for analog trigger +// + +var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; + +var BUMPER_ON_VALUE = 0.5; + +// +// distant manipulation +// + +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position +var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects + +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; + +var LINE_LENGTH = 500; +var PICK_MAX_DISTANCE = 500; // max length of pick-ray + +// +// near grabbing +// + +var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected +var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position +var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. +var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected +var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things +var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object +var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed + +// +// equip +// + +var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; +var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position + +// +// other constants +// + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; + +var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; +var MSEC_PER_SEC = 1000.0; + +// these control how long an abandoned pointer line or action will hang around +var LIFETIME = 10; +var ACTION_TTL = 15; // seconds +var ACTION_TTL_REFRESH = 5; +var PICKS_PER_SECOND_PER_HAND = 5; +var MSECS_PER_SEC = 1000.0; +var GRABBABLE_PROPERTIES = [ + "position", + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; + +var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js +var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js + +var DEFAULT_GRABBABLE_DATA = { + grabbable: true, + invertSolidWhileHeld: false +}; + +//we've created various ways of visualizing looking for and moving distant objects +var USE_ENTITY_LINES_FOR_SEARCHING = false; +var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_PARTICLE_BEAM_FOR_SEARCHING = false; + +var USE_ENTITY_LINES_FOR_MOVING = false; +var USE_OVERLAY_LINES_FOR_MOVING = true; +var USE_PARTICLE_BEAM_FOR_MOVING = false; + +var USE_SPOTLIGHT = false; +var USE_POINTLIGHT = false; + +// states for the state machine +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_DISTANCE_HOLDING = 2; +var STATE_CONTINUE_DISTANCE_HOLDING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP_SEARCHING = 11; +var STATE_EQUIP = 12 +var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_BUMPER_RELEASE = 15; +var STATE_EQUIP_SPRING = 16; + + + +function stateToName(state) { + switch (state) { + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_DISTANCE_HOLDING: + return "distance_holding"; + case STATE_CONTINUE_DISTANCE_HOLDING: + return "continue_distance_holding"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_WAITING_FOR_BUMPER_RELEASE: + return "waiting_for_bumper_release"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; + } + + return "unknown"; +} + +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments["tag"]; + if (tag == getTag()) { + // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 5) == "grab-") { + // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +} + +function getSpatialOffsetPosition(hand, spatialKey) { + var position = Vec3.ZERO; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { + position = spatialKey.leftRelativePosition; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { + position = spatialKey.rightRelativePosition; + } + if (spatialKey.relativePosition) { + position = spatialKey.relativePosition; + } + + return position; +} + +var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); + +function getSpatialOffsetRotation(hand, spatialKey) { + var rotation = Quat.IDENTITY; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { + rotation = spatialKey.leftRelativeRotation; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { + rotation = spatialKey.rightRelativeRotation; + } + if (spatialKey.relativeRotation) { + rotation = spatialKey.relativeRotation; + } + + // Flip left hand + if (hand !== RIGHT_HAND) { + rotation = Quat.multiply(yFlip, rotation); + } + + return rotation; +} + +function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + this.actionID = null; // action this script created... + this.grabbedEntity = null; // on this entity. + this.state = STATE_OFF; + this.pointer = null; // entity-id of line object + this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; + this.rawBumperValue = 0; + + //for visualizations + this.overlayLine = null; + this.particleBeam = null; + + //for lights + this.spotlight = null; + this.pointlight = null; + + this.ignoreIK = false; + this.offsetPosition = Vec3.ZERO; + this.offsetRotation = Quat.IDENTITY; + + var _this = this; + + this.update = function() { + + this.updateSmoothedTrigger(); + + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; + case STATE_SEARCHING: + this.search(); + break; + case STATE_EQUIP_SEARCHING: + this.search(); + break; + case STATE_DISTANCE_HOLDING: + this.distanceHolding(); + break; + case STATE_CONTINUE_DISTANCE_HOLDING: + this.continueDistanceHolding(); + break; + case STATE_NEAR_GRABBING: + case STATE_EQUIP: + this.nearGrabbing(); + break; + case STATE_WAITING_FOR_BUMPER_RELEASE: + this.waitingForBumperRelease(); + break; + case STATE_EQUIP_SPRING: + this.pullTowardEquipPosition() + break; + case STATE_CONTINUE_NEAR_GRABBING: + case STATE_CONTINUE_EQUIP_BD: + case STATE_CONTINUE_EQUIP: + this.continueNearGrabbing(); + break; + case STATE_NEAR_TRIGGER: + this.nearTrigger(); + break; + case STATE_CONTINUE_NEAR_TRIGGER: + this.continueNearTrigger(); + break; + case STATE_FAR_TRIGGER: + this.farTrigger(); + break; + case STATE_CONTINUE_FAR_TRIGGER: + this.continueFarTrigger(); + break; + case STATE_RELEASE: + this.release(); + break; + } + }; + + this.setState = function(newState) { + if (WANT_DEBUG) { + print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + } + this.state = newState; + }; + + this.debugLine = function(closePoint, farPoint, color) { + Entities.addEntity({ + type: "Line", + name: "Grab Debug Entity", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + }; + + this.lineOn = function(closePoint, farPoint, color) { + // draw a line + if (this.pointer === null) { + this.pointer = Entities.addEntity({ + type: "Line", + name: "grab pointer", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: LIFETIME, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } else { + var age = Entities.getEntityProperties(this.pointer, "age").age; + this.pointer = Entities.editEntity(this.pointer, { + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: age + LIFETIME + }); + } + }; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine === null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1 + }; + + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.overlayLine, { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1 + }); + } + }; + + this.handleParticleBeam = function(position, orientation, color) { + + var rotation = Quat.angleAxis(0, { + x: 1, + y: 0, + z: 0 + }); + + var finalRotation = Quat.multiply(orientation, rotation); + + if (this.particleBeam === null) { + this.createParticleBeam(position, finalRotation, color); + } else { + this.updateParticleBeam(position, finalRotation, color); + } + }; + + this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); + + var distance = Vec3.distance(handPosition, objectPosition); + var speed = distance * 1; + + var lifepsan = distance / speed; + var lifespan = 1; + + if (this.particleBeam === null) { + this.createParticleBeam(objectPosition, finalRotation, color, speed); + } else { + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + } + }; + + this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + + var particleBeamProperties = { + type: "ParticleEffect", + isEmitting: true, + position: position, + visible: false, + //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + "name": "Particle Beam", + "color": color, + "maxParticles": 2000, + "lifespan": LINE_LENGTH / 10, + "emitRate": 50, + "emitSpeed": 5, + "speedSpread": 2, + "emitOrientation": { + "x": -1, + "y": 0, + "z": 0, + "w": 1 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitRadiusStart": 0.5, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + x: 0, + y: 0, + z: 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.01, + "radiusSpread": 0, + // "radiusStart": 0.01, + // "radiusFinish": 0.01, + // "colorSpread": { + // "red": 0, + // "green": 0, + // "blue": 0 + // }, + // "colorStart": color, + // "colorFinish": color, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 1, + "additiveBlending": 1, + "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" + } + + this.particleBeam = Entities.addEntity(particleBeamProperties); + }; + + this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { + + Entities.editEntity(this.particleBeam, { + rotation: orientation, + position: position, + visible: true, + color: color, + emitSpeed: speed, + lifepsan: lifepsan + + }) + + }; + + this.evalLightWorldTransform = function(modelPos, modelRot) { + + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + }; + + this.handleSpotlight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + Entities.editEntity(this.spotlight, { + //without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + }) + } + }; + + this.handlePointLight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + + var lightProperties = { + type: "Light", + isSpotlight: false, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.pointlight === null) { + this.pointlight = Entities.addEntity(lightProperties); + } else { + + } + }; + + this.lineOff = function() { + if (this.pointer !== null) { + Entities.deleteEntity(this.pointer); + } + this.pointer = null; + }; + + this.overlayLineOff = function() { + if (this.overlayLine !== null) { + Overlays.deleteOverlay(this.overlayLine); + } + this.overlayLine = null; + }; + + this.particleBeamOff = function() { + if (this.particleBeam !== null) { + Entities.editEntity(this.particleBeam, { + visible: false + }) + } + } + + this.turnLightsOff = function() { + if (this.spotlight !== null) { + Entities.deleteEntity(this.spotlight); + this.spotlight = null; + } + + if (this.pointlight !== null) { + Entities.deleteEntity(this.pointlight); + this.pointlight = null; + } + }; + + + this.turnOffVisualizations = function() { + if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOff(); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + }; + + this.triggerPress = function(value) { + _this.rawTriggerValue = value; + }; + + this.bumperPress = function(value) { + _this.rawBumperValue = value; + }; + + this.updateSmoothedTrigger = function() { + var triggerValue = this.rawTriggerValue; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; + return triggerValue > TRIGGER_ON_VALUE; + }; + + this.bumperSqueezed = function() { + return _this.rawBumperValue > BUMPER_ON_VALUE; + }; + + this.bumperReleased = function() { + return _this.rawBumperValue < BUMPER_ON_VALUE; + }; + + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_SEARCHING); + return; + } + if (this.bumperSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_EQUIP_SEARCHING); + return; + } + }; + + this.search = function() { + this.grabbedEntity = null; + + if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { + this.setState(STATE_RELEASE); + return; + } + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var distantPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), + length: PICK_MAX_DISTANCE + }; + + // don't pick 60x per second. + var pickRays = []; + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + pickRays = [distantPickRay]; + this.lastPickTime = now; + } + + for (var index = 0; index < pickRays.length; ++index) { + var pickRay = pickRays[index]; + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + if (WANT_DEBUG) { + this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { + red: 0, + green: 255, + blue: 0 + }) + } + + var intersection = Entities.findRayIntersection(pickRayBacked, true); + + if (intersection.intersects) { + // the ray is intersecting something we can move. + var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + + if (intersection.properties.name == "Grab Debug Entity") { + continue; + } + + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { + continue; + } + if (intersectionDistance > pickRay.length) { + // too far away for this ray. + continue; + } + if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + // the hand is very close to the intersected object. go into close-grabbing mode. + if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!intersection.properties.locked) { + this.grabbedEntity = intersection.entityID; + if (this.state == STATE_SEARCHING) { + this.setState(STATE_NEAR_GRABBING); + } else { // equipping + if (typeof grabbableData.spatialKey !== 'undefined') { + // TODO + // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch + // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + } else { + this.setState(STATE_EQUIP); + } + } + return; + } + } else if (!entityIsGrabbedByOther(intersection.entityID)) { + // don't allow two people to distance grab the same object + if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { + // the hand is far from the intersected object. go into distance-holding mode + this.grabbedEntity = intersection.entityID; + if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { + // if a distance pick in equip mode hits something with a spatialKey, equip it + // TODO use STATE_EQUIP_SPRING here once it works right. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + return; + } else if (this.state == STATE_SEARCHING) { + this.setState(STATE_DISTANCE_HOLDING); + return; + } + } else if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_FAR_TRIGGER); + return; + } + } + } + } + + // forward ray test failed, try sphere test. + if (WANT_DEBUG) { + Entities.addEntity({ + type: "Sphere", + name: "Grab Debug Entity", + dimensions: { + x: GRAB_RADIUS, + y: GRAB_RADIUS, + z: GRAB_RADIUS + }, + visible: true, + position: handPosition, + color: { + red: 0, + green: 255, + blue: 0 + }, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var minDistance = PICK_MAX_DISTANCE; + var i, props, distance, grabbableData; + this.grabbedEntity = null; + for (i = 0; i < nearbyEntities.length; i++) { + var grabbableDataForCandidate = + getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); + if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { + continue; + } + var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); + + if (propsForCandidate.type == 'Unknown') { + continue; + } + + if (propsForCandidate.type == 'Light') { + continue; + } + + if (propsForCandidate.type == 'ParticleEffect') { + continue; + } + + if (propsForCandidate.type == 'PolyLine') { + continue; + } + + if (propsForCandidate.type == 'Zone') { + continue; + } + + if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { + continue; + } + + if (propsForCandidate.name == "Grab Debug Entity") { + continue; + } + + if (propsForCandidate.name == "grab pointer") { + continue; + } + + distance = Vec3.distance(propsForCandidate.position, handPosition); + if (distance < minDistance) { + this.grabbedEntity = nearbyEntities[i]; + minDistance = distance; + props = propsForCandidate; + grabbableData = grabbableDataForCandidate; + } + } + if (this.grabbedEntity !== null) { + if (grabbableData.wantsTrigger) { + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!props.locked && props.collisionsWillMove) { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + return; + } + } + + //search line visualizations + if (USE_ENTITY_LINES_FOR_SEARCHING === true) { + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + } + + }; + + this.distanceHolding = function() { + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); + this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; + + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + tag: getTag(), + ttl: ACTION_TTL + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + + if (this.actionID !== null) { + this.setState(STATE_CONTINUE_DISTANCE_HOLDING); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); + } + + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + + this.turnOffVisualizations(); + }; + + this.continueDistanceHolding = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + var handPosition = this.getHandPosition(); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && + typeof grabbableData.spatialKey !== 'undefined') { + var saveGrabbedID = this.grabbedEntity; + this.release(); + this.setState(STATE_EQUIP); + this.grabbedEntity = saveGrabbedID; + return; + } + + + // the action was set up on a previous call. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? + var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); + this.handRelativePreviousPosition = handToAvatar; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); + var superHandMoved = Vec3.multiply(handMoved, radius); + + // Move the object by the magnified amount and then by amount from avatar movement & rotation + var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters + var now = Date.now(); + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentObjectPosition = newObjectPosition; + this.currentObjectTime = now; + + // this doubles hand rotation + var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, + handRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.handPreviousRotation)); + this.handPreviousRotation = handRotation; + this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + + Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + + + //visualizations + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } + if (USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); + } + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + } + if (USE_POINTLIGHT === true) { + this.handlePointLight(this.grabbedEntity); + } + if (USE_SPOTLIGHT === true) { + this.handleSpotlight(this.grabbedEntity); + } + + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + ttl: ACTION_TTL + }); + + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + }; + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + } else { + this.ignoreIK = false; + + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } else { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + } + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; + + this.continueNearGrabbing = function() { + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { + this.setState(STATE_CONTINUE_EQUIP); + return; + } + if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { + this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); + return; + } + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { + this.setState(STATE_CONTINUE_EQUIP_BD); + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + return; + } + + // Keep track of the fingertip velocity to impart when we release the object. + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var now = Date.now(); + + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentHandControllerTipPosition = handControllerPosition; + this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + Entities.updateAction(this.grabbedEntity, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } + }; + + this.waitingForBumperRelease = function() { + if (this.bumperReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + Entities.callEntityMethod(this.grabbedEntity, "unequip"); + this.endHandGrasp(); + + } + }; + + this.pullTowardEquipPosition = function() { + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + // use a spring to pull the object to where it will be when equipped + var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + var targetRotation = Quat.multiply(handRotation, relativeRotation); + var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); + var targetPosition = Vec3.sum(handPosition, offset); + + if (typeof this.equipSpringID === 'undefined' || + this.equipSpringID === null || + this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + if (this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = null; + this.setState(STATE_OFF); + return; + } + } else { + Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + } + + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipSpringID); + this.equipSpringID = null; + this.setState(STATE_EQUIP); + } + }; + + this.nearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.setState(STATE_CONTINUE_NEAR_TRIGGER); + }; + + this.farTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.setState(STATE_CONTINUE_FAR_TRIGGER); + }; + + this.continueNearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + }; + + this.continueFarTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + var intersection = Entities.findRayIntersection(pickRay, true); + this.lastPickTime = now; + if (intersection.entityID != this.grabbedEntity) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + } + + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + }; + + _this.allTouchedIDs = {}; + + this.touchTest = function() { + var maxDistance = 0.05; + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); + var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); + var ids = []; + + if (leftEntities.length !== 0) { + leftEntities.forEach(function(entity) { + ids.push(entity); + }); + + } + + if (rightEntities.length !== 0) { + rightEntities.forEach(function(entity) { + ids.push(entity); + }); + } + + ids.forEach(function(id) { + + var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); + if (props.name === 'pointer') { + return; + } else { + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); + var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); + + if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { + // we haven't been touched before, but either right or left is touching us now + _this.allTouchedIDs[id] = true; + _this.startTouch(id); + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { + // we have been touched before and are still being touched + // continue touch + _this.continueTouch(id); + } else if (_this.allTouchedIDs[id]) { + delete _this.allTouchedIDs[id]; + _this.stopTouch(id); + + } else { + //we are in another state + return; + } + } + + }); + + }; + + this.startTouch = function(entityID) { + Entities.callEntityMethod(entityID, "startTouch"); + }; + + this.continueTouch = function(entityID) { + Entities.callEntityMethod(entityID, "continueTouch"); + }; + + this.stopTouch = function(entityID) { + Entities.callEntityMethod(entityID, "stopTouch"); + }; + + this.release = function() { + + this.turnLightsOff(); + this.turnOffVisualizations(); + + if (this.grabbedEntity !== null) { + if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } + } + + this.deactivateEntity(this.grabbedEntity); + + this.grabbedEntity = null; + this.actionID = null; + this.setState(STATE_OFF); + }; + + this.cleanup = function() { + this.release(); + this.endHandGrasp(); + Entities.deleteEntity(this.particleBeam); + Entities.deleteEntity(this.spotLight); + Entities.deleteEntity(this.pointLight); + }; + + this.activateEntity = function(entityID, grabbedProperties) { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + data["activated"] = true; + data["avatarId"] = MyAvatar.sessionUUID; + data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done + if (data["refCount"] == 1) { + data["gravity"] = grabbedProperties.gravity; + data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; + data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + } + }; + if (invertSolidWhileHeld) { + whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; + } + Entities.editEntity(entityID, whileHeldProperties); + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + return data; + }; + + this.deactivateEntity = function(entityID) { + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + Entities.editEntity(entityID, { + gravity: data["gravity"], + ignoreForCollisions: data["ignoreForCollisions"], + collisionsWillMove: data["collisionsWillMove"] + }); + data = null; + } + } else { + data = null; + } + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + }; + + this.graspHandler = null + + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + }; + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + }; + +}; + +var rightController = new MyController(RIGHT_HAND); +var leftController = new MyController(LEFT_HAND); + +//preload the particle beams so that they are full length when you start searching +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + rightController.createParticleBeam(); + leftController.createParticleBeam(); +} + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); + +mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); +mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); + +Controller.enableMapping(MAPPING_NAME); + +//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items +var handToDisable = 'none'; + +function update() { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { + leftController.update(); + } + if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { + rightController.update(); + } +} + +Messages.subscribe('Hifi-Hand-Disabler'); + +handleHandDisablerMessages = function(channel, message, sender) { + + if (sender === MyAvatar.sessionUUID) { + if (message === 'left') { + handToDisable = LEFT_HAND; + } + if (message === 'right') { + handToDisable = RIGHT_HAND; + } + if (message === 'both') { + handToDisable = 'both'; + } + if (message === 'none') { + handToDisable = 'none'; + } + } + +} + +Messages.messageReceived.connect(handleHandDisablerMessages); + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-particles.js b/examples/controllers/handControllerGrab-particles.js new file mode 100644 index 0000000000..d9ecb18b01 --- /dev/null +++ b/examples/controllers/handControllerGrab-particles.js @@ -0,0 +1,1649 @@ +// handControllerGrab.js +// +// Created by Eric Levin on 9/2/15 +// Additions by James B. Pollack @imgntn on 9/24/2015 +// Additions By Seth Alves on 10/20/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// Also supports touch and equipping objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ + +Script.include("../libraries/utils.js"); + +// +// add lines where the hand ray picking is happening +// +var WANT_DEBUG = false; + +// +// these tune time-averaging and "on" value for analog trigger +// + +var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; + +var BUMPER_ON_VALUE = 0.5; + +// +// distant manipulation +// + +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position +var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects + +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; + +var LINE_LENGTH = 500; +var PICK_MAX_DISTANCE = 500; // max length of pick-ray + +// +// near grabbing +// + +var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected +var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position +var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. +var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected +var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things +var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object +var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed + +// +// equip +// + +var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; +var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position + +// +// other constants +// + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; + +var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; +var MSEC_PER_SEC = 1000.0; + +// these control how long an abandoned pointer line or action will hang around +var LIFETIME = 10; +var ACTION_TTL = 15; // seconds +var ACTION_TTL_REFRESH = 5; +var PICKS_PER_SECOND_PER_HAND = 5; +var MSECS_PER_SEC = 1000.0; +var GRABBABLE_PROPERTIES = [ + "position", + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; + +var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js +var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js + +var DEFAULT_GRABBABLE_DATA = { + grabbable: true, + invertSolidWhileHeld: false +}; + +//we've created various ways of visualizing looking for and moving distant objects +var USE_ENTITY_LINES_FOR_SEARCHING = false; +var USE_OVERLAY_LINES_FOR_SEARCHING = false; +var USE_PARTICLE_BEAM_FOR_SEARCHING = true; + +var USE_ENTITY_LINES_FOR_MOVING = false; +var USE_OVERLAY_LINES_FOR_MOVING = false; +var USE_PARTICLE_BEAM_FOR_MOVING = true; + +var USE_SPOTLIGHT = false; +var USE_POINTLIGHT = false; + +// states for the state machine +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_DISTANCE_HOLDING = 2; +var STATE_CONTINUE_DISTANCE_HOLDING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP_SEARCHING = 11; +var STATE_EQUIP = 12 +var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_BUMPER_RELEASE = 15; +var STATE_EQUIP_SPRING = 16; + + + +function stateToName(state) { + switch (state) { + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_DISTANCE_HOLDING: + return "distance_holding"; + case STATE_CONTINUE_DISTANCE_HOLDING: + return "continue_distance_holding"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_WAITING_FOR_BUMPER_RELEASE: + return "waiting_for_bumper_release"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; + } + + return "unknown"; +} + +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments["tag"]; + if (tag == getTag()) { + // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 5) == "grab-") { + // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +} + +function getSpatialOffsetPosition(hand, spatialKey) { + var position = Vec3.ZERO; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { + position = spatialKey.leftRelativePosition; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { + position = spatialKey.rightRelativePosition; + } + if (spatialKey.relativePosition) { + position = spatialKey.relativePosition; + } + + return position; +} + +var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); + +function getSpatialOffsetRotation(hand, spatialKey) { + var rotation = Quat.IDENTITY; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { + rotation = spatialKey.leftRelativeRotation; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { + rotation = spatialKey.rightRelativeRotation; + } + if (spatialKey.relativeRotation) { + rotation = spatialKey.relativeRotation; + } + + // Flip left hand + if (hand !== RIGHT_HAND) { + rotation = Quat.multiply(yFlip, rotation); + } + + return rotation; +} + +function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + this.actionID = null; // action this script created... + this.grabbedEntity = null; // on this entity. + this.state = STATE_OFF; + this.pointer = null; // entity-id of line object + this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; + this.rawBumperValue = 0; + + //for visualizations + this.overlayLine = null; + this.particleBeam = null; + + //for lights + this.spotlight = null; + this.pointlight = null; + + this.ignoreIK = false; + this.offsetPosition = Vec3.ZERO; + this.offsetRotation = Quat.IDENTITY; + + var _this = this; + + this.update = function() { + + this.updateSmoothedTrigger(); + + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; + case STATE_SEARCHING: + this.search(); + break; + case STATE_EQUIP_SEARCHING: + this.search(); + break; + case STATE_DISTANCE_HOLDING: + this.distanceHolding(); + break; + case STATE_CONTINUE_DISTANCE_HOLDING: + this.continueDistanceHolding(); + break; + case STATE_NEAR_GRABBING: + case STATE_EQUIP: + this.nearGrabbing(); + break; + case STATE_WAITING_FOR_BUMPER_RELEASE: + this.waitingForBumperRelease(); + break; + case STATE_EQUIP_SPRING: + this.pullTowardEquipPosition() + break; + case STATE_CONTINUE_NEAR_GRABBING: + case STATE_CONTINUE_EQUIP_BD: + case STATE_CONTINUE_EQUIP: + this.continueNearGrabbing(); + break; + case STATE_NEAR_TRIGGER: + this.nearTrigger(); + break; + case STATE_CONTINUE_NEAR_TRIGGER: + this.continueNearTrigger(); + break; + case STATE_FAR_TRIGGER: + this.farTrigger(); + break; + case STATE_CONTINUE_FAR_TRIGGER: + this.continueFarTrigger(); + break; + case STATE_RELEASE: + this.release(); + break; + } + }; + + this.setState = function(newState) { + if (WANT_DEBUG) { + print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + } + this.state = newState; + }; + + this.debugLine = function(closePoint, farPoint, color) { + Entities.addEntity({ + type: "Line", + name: "Grab Debug Entity", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + }; + + this.lineOn = function(closePoint, farPoint, color) { + // draw a line + if (this.pointer === null) { + this.pointer = Entities.addEntity({ + type: "Line", + name: "grab pointer", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: LIFETIME, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } else { + var age = Entities.getEntityProperties(this.pointer, "age").age; + this.pointer = Entities.editEntity(this.pointer, { + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: age + LIFETIME + }); + } + }; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine === null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1 + }; + + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.overlayLine, { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1 + }); + } + }; + + this.handleParticleBeam = function(position, orientation, color) { + + var rotation = Quat.angleAxis(0, { + x: 1, + y: 0, + z: 0 + }); + + var finalRotation = Quat.multiply(orientation, rotation); + + if (this.particleBeam === null) { + this.createParticleBeam(position, finalRotation, color); + } else { + this.updateParticleBeam(position, finalRotation, color); + } + }; + + this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); + + var distance = Vec3.distance(handPosition, objectPosition); + var speed = distance * 1; + + var lifepsan = distance / speed; + var lifespan = 1; + + if (this.particleBeam === null) { + this.createParticleBeam(objectPosition, finalRotation, color, speed); + } else { + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + } + }; + + this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + + var particleBeamProperties = { + type: "ParticleEffect", + isEmitting: true, + position: position, + visible: false, + //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + "name": "Particle Beam", + "color": color, + "maxParticles": 2000, + "lifespan": LINE_LENGTH / 10, + "emitRate": 50, + "emitSpeed": 5, + "speedSpread": 2, + "emitOrientation": { + "x": -1, + "y": 0, + "z": 0, + "w": 1 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitRadiusStart": 0.5, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + x: 0, + y: 0, + z: 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.01, + "radiusSpread": 0, + // "radiusStart": 0.01, + // "radiusFinish": 0.01, + // "colorSpread": { + // "red": 0, + // "green": 0, + // "blue": 0 + // }, + // "colorStart": color, + // "colorFinish": color, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 1, + "additiveBlending": 1, + "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" + } + + this.particleBeam = Entities.addEntity(particleBeamProperties); + }; + + this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { + + Entities.editEntity(this.particleBeam, { + rotation: orientation, + position: position, + visible: true, + color: color, + emitSpeed: speed, + lifepsan: lifepsan + + }) + + }; + + this.evalLightWorldTransform = function(modelPos, modelRot) { + + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + }; + + this.handleSpotlight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + Entities.editEntity(this.spotlight, { + //without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + }) + } + }; + + this.handlePointLight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + + var lightProperties = { + type: "Light", + isSpotlight: false, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.pointlight === null) { + this.pointlight = Entities.addEntity(lightProperties); + } else { + + } + }; + + this.lineOff = function() { + if (this.pointer !== null) { + Entities.deleteEntity(this.pointer); + } + this.pointer = null; + }; + + this.overlayLineOff = function() { + if (this.overlayLine !== null) { + Overlays.deleteOverlay(this.overlayLine); + } + this.overlayLine = null; + }; + + this.particleBeamOff = function() { + if (this.particleBeam !== null) { + Entities.editEntity(this.particleBeam, { + visible: false + }) + } + } + + this.turnLightsOff = function() { + if (this.spotlight !== null) { + Entities.deleteEntity(this.spotlight); + this.spotlight = null; + } + + if (this.pointlight !== null) { + Entities.deleteEntity(this.pointlight); + this.pointlight = null; + } + }; + + + this.turnOffVisualizations = function() { + if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOff(); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + }; + + this.triggerPress = function(value) { + _this.rawTriggerValue = value; + }; + + this.bumperPress = function(value) { + _this.rawBumperValue = value; + }; + + this.updateSmoothedTrigger = function() { + var triggerValue = this.rawTriggerValue; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; + return triggerValue > TRIGGER_ON_VALUE; + }; + + this.bumperSqueezed = function() { + return _this.rawBumperValue > BUMPER_ON_VALUE; + }; + + this.bumperReleased = function() { + return _this.rawBumperValue < BUMPER_ON_VALUE; + }; + + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_SEARCHING); + return; + } + if (this.bumperSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_EQUIP_SEARCHING); + return; + } + }; + + this.search = function() { + this.grabbedEntity = null; + + if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { + this.setState(STATE_RELEASE); + return; + } + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var distantPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), + length: PICK_MAX_DISTANCE + }; + + // don't pick 60x per second. + var pickRays = []; + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + pickRays = [distantPickRay]; + this.lastPickTime = now; + } + + for (var index = 0; index < pickRays.length; ++index) { + var pickRay = pickRays[index]; + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + if (WANT_DEBUG) { + this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { + red: 0, + green: 255, + blue: 0 + }) + } + + var intersection = Entities.findRayIntersection(pickRayBacked, true); + + if (intersection.intersects) { + // the ray is intersecting something we can move. + var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + + if (intersection.properties.name == "Grab Debug Entity") { + continue; + } + + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { + continue; + } + if (intersectionDistance > pickRay.length) { + // too far away for this ray. + continue; + } + if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + // the hand is very close to the intersected object. go into close-grabbing mode. + if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!intersection.properties.locked) { + this.grabbedEntity = intersection.entityID; + if (this.state == STATE_SEARCHING) { + this.setState(STATE_NEAR_GRABBING); + } else { // equipping + if (typeof grabbableData.spatialKey !== 'undefined') { + // TODO + // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch + // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + } else { + this.setState(STATE_EQUIP); + } + } + return; + } + } else if (!entityIsGrabbedByOther(intersection.entityID)) { + // don't allow two people to distance grab the same object + if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { + // the hand is far from the intersected object. go into distance-holding mode + this.grabbedEntity = intersection.entityID; + if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { + // if a distance pick in equip mode hits something with a spatialKey, equip it + // TODO use STATE_EQUIP_SPRING here once it works right. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + return; + } else if (this.state == STATE_SEARCHING) { + this.setState(STATE_DISTANCE_HOLDING); + return; + } + } else if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_FAR_TRIGGER); + return; + } + } + } + } + + // forward ray test failed, try sphere test. + if (WANT_DEBUG) { + Entities.addEntity({ + type: "Sphere", + name: "Grab Debug Entity", + dimensions: { + x: GRAB_RADIUS, + y: GRAB_RADIUS, + z: GRAB_RADIUS + }, + visible: true, + position: handPosition, + color: { + red: 0, + green: 255, + blue: 0 + }, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var minDistance = PICK_MAX_DISTANCE; + var i, props, distance, grabbableData; + this.grabbedEntity = null; + for (i = 0; i < nearbyEntities.length; i++) { + var grabbableDataForCandidate = + getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); + if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { + continue; + } + var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); + + if (propsForCandidate.type == 'Unknown') { + continue; + } + + if (propsForCandidate.type == 'Light') { + continue; + } + + if (propsForCandidate.type == 'ParticleEffect') { + continue; + } + + if (propsForCandidate.type == 'PolyLine') { + continue; + } + + if (propsForCandidate.type == 'Zone') { + continue; + } + + if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { + continue; + } + + if (propsForCandidate.name == "Grab Debug Entity") { + continue; + } + + if (propsForCandidate.name == "grab pointer") { + continue; + } + + distance = Vec3.distance(propsForCandidate.position, handPosition); + if (distance < minDistance) { + this.grabbedEntity = nearbyEntities[i]; + minDistance = distance; + props = propsForCandidate; + grabbableData = grabbableDataForCandidate; + } + } + if (this.grabbedEntity !== null) { + if (grabbableData.wantsTrigger) { + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!props.locked && props.collisionsWillMove) { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + return; + } + } + + //search line visualizations + if (USE_ENTITY_LINES_FOR_SEARCHING === true) { + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + } + + }; + + this.distanceHolding = function() { + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); + this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; + + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + tag: getTag(), + ttl: ACTION_TTL + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + + if (this.actionID !== null) { + this.setState(STATE_CONTINUE_DISTANCE_HOLDING); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); + } + + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + + this.turnOffVisualizations(); + }; + + this.continueDistanceHolding = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + var handPosition = this.getHandPosition(); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && + typeof grabbableData.spatialKey !== 'undefined') { + var saveGrabbedID = this.grabbedEntity; + this.release(); + this.setState(STATE_EQUIP); + this.grabbedEntity = saveGrabbedID; + return; + } + + + // the action was set up on a previous call. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? + var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); + this.handRelativePreviousPosition = handToAvatar; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); + var superHandMoved = Vec3.multiply(handMoved, radius); + + // Move the object by the magnified amount and then by amount from avatar movement & rotation + var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters + var now = Date.now(); + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentObjectPosition = newObjectPosition; + this.currentObjectTime = now; + + // this doubles hand rotation + var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, + handRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.handPreviousRotation)); + this.handPreviousRotation = handRotation; + this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + + Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + + + //visualizations + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } + if (USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); + } + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + } + if (USE_POINTLIGHT === true) { + this.handlePointLight(this.grabbedEntity); + } + if (USE_SPOTLIGHT === true) { + this.handleSpotlight(this.grabbedEntity); + } + + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + ttl: ACTION_TTL + }); + + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + }; + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + } else { + this.ignoreIK = false; + + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } else { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + } + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; + + this.continueNearGrabbing = function() { + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { + this.setState(STATE_CONTINUE_EQUIP); + return; + } + if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { + this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); + return; + } + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { + this.setState(STATE_CONTINUE_EQUIP_BD); + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + return; + } + + // Keep track of the fingertip velocity to impart when we release the object. + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var now = Date.now(); + + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentHandControllerTipPosition = handControllerPosition; + this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + Entities.updateAction(this.grabbedEntity, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } + }; + + this.waitingForBumperRelease = function() { + if (this.bumperReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + Entities.callEntityMethod(this.grabbedEntity, "unequip"); + this.endHandGrasp(); + + } + }; + + this.pullTowardEquipPosition = function() { + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + // use a spring to pull the object to where it will be when equipped + var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + var targetRotation = Quat.multiply(handRotation, relativeRotation); + var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); + var targetPosition = Vec3.sum(handPosition, offset); + + if (typeof this.equipSpringID === 'undefined' || + this.equipSpringID === null || + this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + if (this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = null; + this.setState(STATE_OFF); + return; + } + } else { + Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + } + + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipSpringID); + this.equipSpringID = null; + this.setState(STATE_EQUIP); + } + }; + + this.nearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.setState(STATE_CONTINUE_NEAR_TRIGGER); + }; + + this.farTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.setState(STATE_CONTINUE_FAR_TRIGGER); + }; + + this.continueNearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + }; + + this.continueFarTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + var intersection = Entities.findRayIntersection(pickRay, true); + this.lastPickTime = now; + if (intersection.entityID != this.grabbedEntity) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + } + + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + }; + + _this.allTouchedIDs = {}; + + this.touchTest = function() { + var maxDistance = 0.05; + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); + var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); + var ids = []; + + if (leftEntities.length !== 0) { + leftEntities.forEach(function(entity) { + ids.push(entity); + }); + + } + + if (rightEntities.length !== 0) { + rightEntities.forEach(function(entity) { + ids.push(entity); + }); + } + + ids.forEach(function(id) { + + var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); + if (props.name === 'pointer') { + return; + } else { + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); + var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); + + if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { + // we haven't been touched before, but either right or left is touching us now + _this.allTouchedIDs[id] = true; + _this.startTouch(id); + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { + // we have been touched before and are still being touched + // continue touch + _this.continueTouch(id); + } else if (_this.allTouchedIDs[id]) { + delete _this.allTouchedIDs[id]; + _this.stopTouch(id); + + } else { + //we are in another state + return; + } + } + + }); + + }; + + this.startTouch = function(entityID) { + Entities.callEntityMethod(entityID, "startTouch"); + }; + + this.continueTouch = function(entityID) { + Entities.callEntityMethod(entityID, "continueTouch"); + }; + + this.stopTouch = function(entityID) { + Entities.callEntityMethod(entityID, "stopTouch"); + }; + + this.release = function() { + + this.turnLightsOff(); + this.turnOffVisualizations(); + + if (this.grabbedEntity !== null) { + if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } + } + + this.deactivateEntity(this.grabbedEntity); + + this.grabbedEntity = null; + this.actionID = null; + this.setState(STATE_OFF); + }; + + this.cleanup = function() { + this.release(); + this.endHandGrasp(); + Entities.deleteEntity(this.particleBeam); + Entities.deleteEntity(this.spotLight); + Entities.deleteEntity(this.pointLight); + }; + + this.activateEntity = function(entityID, grabbedProperties) { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + data["activated"] = true; + data["avatarId"] = MyAvatar.sessionUUID; + data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done + if (data["refCount"] == 1) { + data["gravity"] = grabbedProperties.gravity; + data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; + data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + } + }; + if (invertSolidWhileHeld) { + whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; + } + Entities.editEntity(entityID, whileHeldProperties); + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + return data; + }; + + this.deactivateEntity = function(entityID) { + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + Entities.editEntity(entityID, { + gravity: data["gravity"], + ignoreForCollisions: data["ignoreForCollisions"], + collisionsWillMove: data["collisionsWillMove"] + }); + data = null; + } + } else { + data = null; + } + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + }; + + this.graspHandler = null + + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + }; + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + }; + +}; + +var rightController = new MyController(RIGHT_HAND); +var leftController = new MyController(LEFT_HAND); + +//preload the particle beams so that they are full length when you start searching +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + rightController.createParticleBeam(); + leftController.createParticleBeam(); +} + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); + +mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); +mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); + +Controller.enableMapping(MAPPING_NAME); + +//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items +var handToDisable = 'none'; + +function update() { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { + leftController.update(); + } + if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { + rightController.update(); + } +} + +Messages.subscribe('Hifi-Hand-Disabler'); + +handleHandDisablerMessages = function(channel, message, sender) { + + if (sender === MyAvatar.sessionUUID) { + if (message === 'left') { + handToDisable = LEFT_HAND; + } + if (message === 'right') { + handToDisable = RIGHT_HAND; + } + if (message === 'both') { + handToDisable = 'both'; + } + if (message === 'none') { + handToDisable = 'none'; + } + } + +} + +Messages.messageReceived.connect(handleHandDisablerMessages); + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-pointlight.js b/examples/controllers/handControllerGrab-pointlight.js new file mode 100644 index 0000000000..fc14e6026e --- /dev/null +++ b/examples/controllers/handControllerGrab-pointlight.js @@ -0,0 +1,1649 @@ +// handControllerGrab.js +// +// Created by Eric Levin on 9/2/15 +// Additions by James B. Pollack @imgntn on 9/24/2015 +// Additions By Seth Alves on 10/20/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// Also supports touch and equipping objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ + +Script.include("../libraries/utils.js"); + +// +// add lines where the hand ray picking is happening +// +var WANT_DEBUG = false; + +// +// these tune time-averaging and "on" value for analog trigger +// + +var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; + +var BUMPER_ON_VALUE = 0.5; + +// +// distant manipulation +// + +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position +var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects + +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; + +var LINE_LENGTH = 500; +var PICK_MAX_DISTANCE = 500; // max length of pick-ray + +// +// near grabbing +// + +var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected +var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position +var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. +var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected +var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things +var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object +var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed + +// +// equip +// + +var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; +var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position + +// +// other constants +// + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; + +var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; +var MSEC_PER_SEC = 1000.0; + +// these control how long an abandoned pointer line or action will hang around +var LIFETIME = 10; +var ACTION_TTL = 15; // seconds +var ACTION_TTL_REFRESH = 5; +var PICKS_PER_SECOND_PER_HAND = 5; +var MSECS_PER_SEC = 1000.0; +var GRABBABLE_PROPERTIES = [ + "position", + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; + +var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js +var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js + +var DEFAULT_GRABBABLE_DATA = { + grabbable: true, + invertSolidWhileHeld: false +}; + +//we've created various ways of visualizing looking for and moving distant objects +var USE_ENTITY_LINES_FOR_SEARCHING = false; +var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_PARTICLE_BEAM_FOR_SEARCHING = false; + +var USE_ENTITY_LINES_FOR_MOVING = true; +var USE_OVERLAY_LINES_FOR_MOVING = false; +var USE_PARTICLE_BEAM_FOR_MOVING = false; + +var USE_SPOTLIGHT = false; +var USE_POINTLIGHT = true; + +// states for the state machine +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_DISTANCE_HOLDING = 2; +var STATE_CONTINUE_DISTANCE_HOLDING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP_SEARCHING = 11; +var STATE_EQUIP = 12 +var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_BUMPER_RELEASE = 15; +var STATE_EQUIP_SPRING = 16; + + + +function stateToName(state) { + switch (state) { + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_DISTANCE_HOLDING: + return "distance_holding"; + case STATE_CONTINUE_DISTANCE_HOLDING: + return "continue_distance_holding"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_WAITING_FOR_BUMPER_RELEASE: + return "waiting_for_bumper_release"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; + } + + return "unknown"; +} + +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments["tag"]; + if (tag == getTag()) { + // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 5) == "grab-") { + // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +} + +function getSpatialOffsetPosition(hand, spatialKey) { + var position = Vec3.ZERO; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { + position = spatialKey.leftRelativePosition; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { + position = spatialKey.rightRelativePosition; + } + if (spatialKey.relativePosition) { + position = spatialKey.relativePosition; + } + + return position; +} + +var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); + +function getSpatialOffsetRotation(hand, spatialKey) { + var rotation = Quat.IDENTITY; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { + rotation = spatialKey.leftRelativeRotation; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { + rotation = spatialKey.rightRelativeRotation; + } + if (spatialKey.relativeRotation) { + rotation = spatialKey.relativeRotation; + } + + // Flip left hand + if (hand !== RIGHT_HAND) { + rotation = Quat.multiply(yFlip, rotation); + } + + return rotation; +} + +function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + this.actionID = null; // action this script created... + this.grabbedEntity = null; // on this entity. + this.state = STATE_OFF; + this.pointer = null; // entity-id of line object + this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; + this.rawBumperValue = 0; + + //for visualizations + this.overlayLine = null; + this.particleBeam = null; + + //for lights + this.spotlight = null; + this.pointlight = null; + + this.ignoreIK = false; + this.offsetPosition = Vec3.ZERO; + this.offsetRotation = Quat.IDENTITY; + + var _this = this; + + this.update = function() { + + this.updateSmoothedTrigger(); + + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; + case STATE_SEARCHING: + this.search(); + break; + case STATE_EQUIP_SEARCHING: + this.search(); + break; + case STATE_DISTANCE_HOLDING: + this.distanceHolding(); + break; + case STATE_CONTINUE_DISTANCE_HOLDING: + this.continueDistanceHolding(); + break; + case STATE_NEAR_GRABBING: + case STATE_EQUIP: + this.nearGrabbing(); + break; + case STATE_WAITING_FOR_BUMPER_RELEASE: + this.waitingForBumperRelease(); + break; + case STATE_EQUIP_SPRING: + this.pullTowardEquipPosition() + break; + case STATE_CONTINUE_NEAR_GRABBING: + case STATE_CONTINUE_EQUIP_BD: + case STATE_CONTINUE_EQUIP: + this.continueNearGrabbing(); + break; + case STATE_NEAR_TRIGGER: + this.nearTrigger(); + break; + case STATE_CONTINUE_NEAR_TRIGGER: + this.continueNearTrigger(); + break; + case STATE_FAR_TRIGGER: + this.farTrigger(); + break; + case STATE_CONTINUE_FAR_TRIGGER: + this.continueFarTrigger(); + break; + case STATE_RELEASE: + this.release(); + break; + } + }; + + this.setState = function(newState) { + if (WANT_DEBUG) { + print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + } + this.state = newState; + }; + + this.debugLine = function(closePoint, farPoint, color) { + Entities.addEntity({ + type: "Line", + name: "Grab Debug Entity", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + }; + + this.lineOn = function(closePoint, farPoint, color) { + // draw a line + if (this.pointer === null) { + this.pointer = Entities.addEntity({ + type: "Line", + name: "grab pointer", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: LIFETIME, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } else { + var age = Entities.getEntityProperties(this.pointer, "age").age; + this.pointer = Entities.editEntity(this.pointer, { + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: age + LIFETIME + }); + } + }; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine === null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1 + }; + + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.overlayLine, { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1 + }); + } + }; + + this.handleParticleBeam = function(position, orientation, color) { + + var rotation = Quat.angleAxis(0, { + x: 1, + y: 0, + z: 0 + }); + + var finalRotation = Quat.multiply(orientation, rotation); + + if (this.particleBeam === null) { + this.createParticleBeam(position, finalRotation, color); + } else { + this.updateParticleBeam(position, finalRotation, color); + } + }; + + this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); + + var distance = Vec3.distance(handPosition, objectPosition); + var speed = distance * 1; + + var lifepsan = distance / speed; + var lifespan = 1; + + if (this.particleBeam === null) { + this.createParticleBeam(objectPosition, finalRotation, color, speed); + } else { + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + } + }; + + this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + + var particleBeamProperties = { + type: "ParticleEffect", + isEmitting: true, + position: position, + visible: false, + //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + "name": "Particle Beam", + "color": color, + "maxParticles": 2000, + "lifespan": LINE_LENGTH / 10, + "emitRate": 50, + "emitSpeed": 5, + "speedSpread": 2, + "emitOrientation": { + "x": -1, + "y": 0, + "z": 0, + "w": 1 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitRadiusStart": 0.5, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + x: 0, + y: 0, + z: 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.01, + "radiusSpread": 0, + // "radiusStart": 0.01, + // "radiusFinish": 0.01, + // "colorSpread": { + // "red": 0, + // "green": 0, + // "blue": 0 + // }, + // "colorStart": color, + // "colorFinish": color, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 1, + "additiveBlending": 1, + "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" + } + + this.particleBeam = Entities.addEntity(particleBeamProperties); + }; + + this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { + + Entities.editEntity(this.particleBeam, { + rotation: orientation, + position: position, + visible: true, + color: color, + emitSpeed: speed, + lifepsan: lifepsan + + }) + + }; + + this.evalLightWorldTransform = function(modelPos, modelRot) { + + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + }; + + this.handleSpotlight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + Entities.editEntity(this.spotlight, { + //without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + }) + } + }; + + this.handlePointLight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + + var lightProperties = { + type: "Light", + isSpotlight: false, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.pointlight === null) { + this.pointlight = Entities.addEntity(lightProperties); + } else { + + } + }; + + this.lineOff = function() { + if (this.pointer !== null) { + Entities.deleteEntity(this.pointer); + } + this.pointer = null; + }; + + this.overlayLineOff = function() { + if (this.overlayLine !== null) { + Overlays.deleteOverlay(this.overlayLine); + } + this.overlayLine = null; + }; + + this.particleBeamOff = function() { + if (this.particleBeam !== null) { + Entities.editEntity(this.particleBeam, { + visible: false + }) + } + } + + this.turnLightsOff = function() { + if (this.spotlight !== null) { + Entities.deleteEntity(this.spotlight); + this.spotlight = null; + } + + if (this.pointlight !== null) { + Entities.deleteEntity(this.pointlight); + this.pointlight = null; + } + }; + + + this.turnOffVisualizations = function() { + if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOff(); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + }; + + this.triggerPress = function(value) { + _this.rawTriggerValue = value; + }; + + this.bumperPress = function(value) { + _this.rawBumperValue = value; + }; + + this.updateSmoothedTrigger = function() { + var triggerValue = this.rawTriggerValue; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; + return triggerValue > TRIGGER_ON_VALUE; + }; + + this.bumperSqueezed = function() { + return _this.rawBumperValue > BUMPER_ON_VALUE; + }; + + this.bumperReleased = function() { + return _this.rawBumperValue < BUMPER_ON_VALUE; + }; + + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_SEARCHING); + return; + } + if (this.bumperSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_EQUIP_SEARCHING); + return; + } + }; + + this.search = function() { + this.grabbedEntity = null; + + if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { + this.setState(STATE_RELEASE); + return; + } + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var distantPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), + length: PICK_MAX_DISTANCE + }; + + // don't pick 60x per second. + var pickRays = []; + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + pickRays = [distantPickRay]; + this.lastPickTime = now; + } + + for (var index = 0; index < pickRays.length; ++index) { + var pickRay = pickRays[index]; + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + if (WANT_DEBUG) { + this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { + red: 0, + green: 255, + blue: 0 + }) + } + + var intersection = Entities.findRayIntersection(pickRayBacked, true); + + if (intersection.intersects) { + // the ray is intersecting something we can move. + var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + + if (intersection.properties.name == "Grab Debug Entity") { + continue; + } + + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { + continue; + } + if (intersectionDistance > pickRay.length) { + // too far away for this ray. + continue; + } + if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + // the hand is very close to the intersected object. go into close-grabbing mode. + if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!intersection.properties.locked) { + this.grabbedEntity = intersection.entityID; + if (this.state == STATE_SEARCHING) { + this.setState(STATE_NEAR_GRABBING); + } else { // equipping + if (typeof grabbableData.spatialKey !== 'undefined') { + // TODO + // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch + // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + } else { + this.setState(STATE_EQUIP); + } + } + return; + } + } else if (!entityIsGrabbedByOther(intersection.entityID)) { + // don't allow two people to distance grab the same object + if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { + // the hand is far from the intersected object. go into distance-holding mode + this.grabbedEntity = intersection.entityID; + if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { + // if a distance pick in equip mode hits something with a spatialKey, equip it + // TODO use STATE_EQUIP_SPRING here once it works right. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + return; + } else if (this.state == STATE_SEARCHING) { + this.setState(STATE_DISTANCE_HOLDING); + return; + } + } else if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_FAR_TRIGGER); + return; + } + } + } + } + + // forward ray test failed, try sphere test. + if (WANT_DEBUG) { + Entities.addEntity({ + type: "Sphere", + name: "Grab Debug Entity", + dimensions: { + x: GRAB_RADIUS, + y: GRAB_RADIUS, + z: GRAB_RADIUS + }, + visible: true, + position: handPosition, + color: { + red: 0, + green: 255, + blue: 0 + }, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var minDistance = PICK_MAX_DISTANCE; + var i, props, distance, grabbableData; + this.grabbedEntity = null; + for (i = 0; i < nearbyEntities.length; i++) { + var grabbableDataForCandidate = + getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); + if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { + continue; + } + var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); + + if (propsForCandidate.type == 'Unknown') { + continue; + } + + if (propsForCandidate.type == 'Light') { + continue; + } + + if (propsForCandidate.type == 'ParticleEffect') { + continue; + } + + if (propsForCandidate.type == 'PolyLine') { + continue; + } + + if (propsForCandidate.type == 'Zone') { + continue; + } + + if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { + continue; + } + + if (propsForCandidate.name == "Grab Debug Entity") { + continue; + } + + if (propsForCandidate.name == "grab pointer") { + continue; + } + + distance = Vec3.distance(propsForCandidate.position, handPosition); + if (distance < minDistance) { + this.grabbedEntity = nearbyEntities[i]; + minDistance = distance; + props = propsForCandidate; + grabbableData = grabbableDataForCandidate; + } + } + if (this.grabbedEntity !== null) { + if (grabbableData.wantsTrigger) { + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!props.locked && props.collisionsWillMove) { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + return; + } + } + + //search line visualizations + if (USE_ENTITY_LINES_FOR_SEARCHING === true) { + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + } + + }; + + this.distanceHolding = function() { + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); + this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; + + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + tag: getTag(), + ttl: ACTION_TTL + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + + if (this.actionID !== null) { + this.setState(STATE_CONTINUE_DISTANCE_HOLDING); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); + } + + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + + this.turnOffVisualizations(); + }; + + this.continueDistanceHolding = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + var handPosition = this.getHandPosition(); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && + typeof grabbableData.spatialKey !== 'undefined') { + var saveGrabbedID = this.grabbedEntity; + this.release(); + this.setState(STATE_EQUIP); + this.grabbedEntity = saveGrabbedID; + return; + } + + + // the action was set up on a previous call. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? + var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); + this.handRelativePreviousPosition = handToAvatar; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); + var superHandMoved = Vec3.multiply(handMoved, radius); + + // Move the object by the magnified amount and then by amount from avatar movement & rotation + var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters + var now = Date.now(); + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentObjectPosition = newObjectPosition; + this.currentObjectTime = now; + + // this doubles hand rotation + var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, + handRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.handPreviousRotation)); + this.handPreviousRotation = handRotation; + this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + + Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + + + //visualizations + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } + if (USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); + } + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + } + if (USE_POINTLIGHT === true) { + this.handlePointLight(this.grabbedEntity); + } + if (USE_SPOTLIGHT === true) { + this.handleSpotlight(this.grabbedEntity); + } + + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + ttl: ACTION_TTL + }); + + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + }; + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + } else { + this.ignoreIK = false; + + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } else { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + } + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; + + this.continueNearGrabbing = function() { + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { + this.setState(STATE_CONTINUE_EQUIP); + return; + } + if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { + this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); + return; + } + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { + this.setState(STATE_CONTINUE_EQUIP_BD); + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + return; + } + + // Keep track of the fingertip velocity to impart when we release the object. + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var now = Date.now(); + + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentHandControllerTipPosition = handControllerPosition; + this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + Entities.updateAction(this.grabbedEntity, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } + }; + + this.waitingForBumperRelease = function() { + if (this.bumperReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + Entities.callEntityMethod(this.grabbedEntity, "unequip"); + this.endHandGrasp(); + + } + }; + + this.pullTowardEquipPosition = function() { + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + // use a spring to pull the object to where it will be when equipped + var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + var targetRotation = Quat.multiply(handRotation, relativeRotation); + var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); + var targetPosition = Vec3.sum(handPosition, offset); + + if (typeof this.equipSpringID === 'undefined' || + this.equipSpringID === null || + this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + if (this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = null; + this.setState(STATE_OFF); + return; + } + } else { + Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + } + + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipSpringID); + this.equipSpringID = null; + this.setState(STATE_EQUIP); + } + }; + + this.nearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.setState(STATE_CONTINUE_NEAR_TRIGGER); + }; + + this.farTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.setState(STATE_CONTINUE_FAR_TRIGGER); + }; + + this.continueNearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + }; + + this.continueFarTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + var intersection = Entities.findRayIntersection(pickRay, true); + this.lastPickTime = now; + if (intersection.entityID != this.grabbedEntity) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + } + + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + }; + + _this.allTouchedIDs = {}; + + this.touchTest = function() { + var maxDistance = 0.05; + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); + var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); + var ids = []; + + if (leftEntities.length !== 0) { + leftEntities.forEach(function(entity) { + ids.push(entity); + }); + + } + + if (rightEntities.length !== 0) { + rightEntities.forEach(function(entity) { + ids.push(entity); + }); + } + + ids.forEach(function(id) { + + var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); + if (props.name === 'pointer') { + return; + } else { + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); + var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); + + if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { + // we haven't been touched before, but either right or left is touching us now + _this.allTouchedIDs[id] = true; + _this.startTouch(id); + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { + // we have been touched before and are still being touched + // continue touch + _this.continueTouch(id); + } else if (_this.allTouchedIDs[id]) { + delete _this.allTouchedIDs[id]; + _this.stopTouch(id); + + } else { + //we are in another state + return; + } + } + + }); + + }; + + this.startTouch = function(entityID) { + Entities.callEntityMethod(entityID, "startTouch"); + }; + + this.continueTouch = function(entityID) { + Entities.callEntityMethod(entityID, "continueTouch"); + }; + + this.stopTouch = function(entityID) { + Entities.callEntityMethod(entityID, "stopTouch"); + }; + + this.release = function() { + + this.turnLightsOff(); + this.turnOffVisualizations(); + + if (this.grabbedEntity !== null) { + if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } + } + + this.deactivateEntity(this.grabbedEntity); + + this.grabbedEntity = null; + this.actionID = null; + this.setState(STATE_OFF); + }; + + this.cleanup = function() { + this.release(); + this.endHandGrasp(); + Entities.deleteEntity(this.particleBeam); + Entities.deleteEntity(this.spotLight); + Entities.deleteEntity(this.pointLight); + }; + + this.activateEntity = function(entityID, grabbedProperties) { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + data["activated"] = true; + data["avatarId"] = MyAvatar.sessionUUID; + data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done + if (data["refCount"] == 1) { + data["gravity"] = grabbedProperties.gravity; + data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; + data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + } + }; + if (invertSolidWhileHeld) { + whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; + } + Entities.editEntity(entityID, whileHeldProperties); + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + return data; + }; + + this.deactivateEntity = function(entityID) { + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + Entities.editEntity(entityID, { + gravity: data["gravity"], + ignoreForCollisions: data["ignoreForCollisions"], + collisionsWillMove: data["collisionsWillMove"] + }); + data = null; + } + } else { + data = null; + } + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + }; + + this.graspHandler = null + + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + }; + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + }; + +}; + +var rightController = new MyController(RIGHT_HAND); +var leftController = new MyController(LEFT_HAND); + +//preload the particle beams so that they are full length when you start searching +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + rightController.createParticleBeam(); + leftController.createParticleBeam(); +} + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); + +mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); +mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); + +Controller.enableMapping(MAPPING_NAME); + +//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items +var handToDisable = 'none'; + +function update() { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { + leftController.update(); + } + if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { + rightController.update(); + } +} + +Messages.subscribe('Hifi-Hand-Disabler'); + +handleHandDisablerMessages = function(channel, message, sender) { + + if (sender === MyAvatar.sessionUUID) { + if (message === 'left') { + handToDisable = LEFT_HAND; + } + if (message === 'right') { + handToDisable = RIGHT_HAND; + } + if (message === 'both') { + handToDisable = 'both'; + } + if (message === 'none') { + handToDisable = 'none'; + } + } + +} + +Messages.messageReceived.connect(handleHandDisablerMessages); + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-spotlight.js b/examples/controllers/handControllerGrab-spotlight.js new file mode 100644 index 0000000000..ea05a44f78 --- /dev/null +++ b/examples/controllers/handControllerGrab-spotlight.js @@ -0,0 +1,1649 @@ +// handControllerGrab.js +// +// Created by Eric Levin on 9/2/15 +// Additions by James B. Pollack @imgntn on 9/24/2015 +// Additions By Seth Alves on 10/20/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// Also supports touch and equipping objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ + +Script.include("../libraries/utils.js"); + +// +// add lines where the hand ray picking is happening +// +var WANT_DEBUG = false; + +// +// these tune time-averaging and "on" value for analog trigger +// + +var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; + +var BUMPER_ON_VALUE = 0.5; + +// +// distant manipulation +// + +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position +var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects + +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; + +var LINE_LENGTH = 500; +var PICK_MAX_DISTANCE = 500; // max length of pick-ray + +// +// near grabbing +// + +var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected +var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position +var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. +var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected +var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things +var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object +var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed + +// +// equip +// + +var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; +var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position + +// +// other constants +// + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; + +var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; +var MSEC_PER_SEC = 1000.0; + +// these control how long an abandoned pointer line or action will hang around +var LIFETIME = 10; +var ACTION_TTL = 15; // seconds +var ACTION_TTL_REFRESH = 5; +var PICKS_PER_SECOND_PER_HAND = 5; +var MSECS_PER_SEC = 1000.0; +var GRABBABLE_PROPERTIES = [ + "position", + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; + +var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js +var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js + +var DEFAULT_GRABBABLE_DATA = { + grabbable: true, + invertSolidWhileHeld: false +}; + +//we've created various ways of visualizing looking for and moving distant objects +var USE_ENTITY_LINES_FOR_SEARCHING = false; +var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_PARTICLE_BEAM_FOR_SEARCHING = false; + +var USE_ENTITY_LINES_FOR_MOVING = true; +var USE_OVERLAY_LINES_FOR_MOVING = false; +var USE_PARTICLE_BEAM_FOR_MOVING = false; + +var USE_SPOTLIGHT = true; +var USE_POINTLIGHT = false; + +// states for the state machine +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_DISTANCE_HOLDING = 2; +var STATE_CONTINUE_DISTANCE_HOLDING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP_SEARCHING = 11; +var STATE_EQUIP = 12 +var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_BUMPER_RELEASE = 15; +var STATE_EQUIP_SPRING = 16; + + + +function stateToName(state) { + switch (state) { + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_DISTANCE_HOLDING: + return "distance_holding"; + case STATE_CONTINUE_DISTANCE_HOLDING: + return "continue_distance_holding"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_WAITING_FOR_BUMPER_RELEASE: + return "waiting_for_bumper_release"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; + } + + return "unknown"; +} + +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments["tag"]; + if (tag == getTag()) { + // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 5) == "grab-") { + // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +} + +function getSpatialOffsetPosition(hand, spatialKey) { + var position = Vec3.ZERO; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { + position = spatialKey.leftRelativePosition; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { + position = spatialKey.rightRelativePosition; + } + if (spatialKey.relativePosition) { + position = spatialKey.relativePosition; + } + + return position; +} + +var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); + +function getSpatialOffsetRotation(hand, spatialKey) { + var rotation = Quat.IDENTITY; + + if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { + rotation = spatialKey.leftRelativeRotation; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { + rotation = spatialKey.rightRelativeRotation; + } + if (spatialKey.relativeRotation) { + rotation = spatialKey.relativeRotation; + } + + // Flip left hand + if (hand !== RIGHT_HAND) { + rotation = Quat.multiply(yFlip, rotation); + } + + return rotation; +} + +function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + this.actionID = null; // action this script created... + this.grabbedEntity = null; // on this entity. + this.state = STATE_OFF; + this.pointer = null; // entity-id of line object + this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; + this.rawBumperValue = 0; + + //for visualizations + this.overlayLine = null; + this.particleBeam = null; + + //for lights + this.spotlight = null; + this.pointlight = null; + + this.ignoreIK = false; + this.offsetPosition = Vec3.ZERO; + this.offsetRotation = Quat.IDENTITY; + + var _this = this; + + this.update = function() { + + this.updateSmoothedTrigger(); + + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; + case STATE_SEARCHING: + this.search(); + break; + case STATE_EQUIP_SEARCHING: + this.search(); + break; + case STATE_DISTANCE_HOLDING: + this.distanceHolding(); + break; + case STATE_CONTINUE_DISTANCE_HOLDING: + this.continueDistanceHolding(); + break; + case STATE_NEAR_GRABBING: + case STATE_EQUIP: + this.nearGrabbing(); + break; + case STATE_WAITING_FOR_BUMPER_RELEASE: + this.waitingForBumperRelease(); + break; + case STATE_EQUIP_SPRING: + this.pullTowardEquipPosition() + break; + case STATE_CONTINUE_NEAR_GRABBING: + case STATE_CONTINUE_EQUIP_BD: + case STATE_CONTINUE_EQUIP: + this.continueNearGrabbing(); + break; + case STATE_NEAR_TRIGGER: + this.nearTrigger(); + break; + case STATE_CONTINUE_NEAR_TRIGGER: + this.continueNearTrigger(); + break; + case STATE_FAR_TRIGGER: + this.farTrigger(); + break; + case STATE_CONTINUE_FAR_TRIGGER: + this.continueFarTrigger(); + break; + case STATE_RELEASE: + this.release(); + break; + } + }; + + this.setState = function(newState) { + if (WANT_DEBUG) { + print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + } + this.state = newState; + }; + + this.debugLine = function(closePoint, farPoint, color) { + Entities.addEntity({ + type: "Line", + name: "Grab Debug Entity", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + }; + + this.lineOn = function(closePoint, farPoint, color) { + // draw a line + if (this.pointer === null) { + this.pointer = Entities.addEntity({ + type: "Line", + name: "grab pointer", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: LIFETIME, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } else { + var age = Entities.getEntityProperties(this.pointer, "age").age; + this.pointer = Entities.editEntity(this.pointer, { + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: age + LIFETIME + }); + } + }; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine === null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, // always ignore this + visible: true, + alpha: 1 + }; + + this.overlayLine = Overlays.addOverlay("line3d", lineProperties); + + } else { + var success = Overlays.editOverlay(this.overlayLine, { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + visible: true, + ignoreRayIntersection: true, // always ignore this + alpha: 1 + }); + } + }; + + this.handleParticleBeam = function(position, orientation, color) { + + var rotation = Quat.angleAxis(0, { + x: 1, + y: 0, + z: 0 + }); + + var finalRotation = Quat.multiply(orientation, rotation); + + if (this.particleBeam === null) { + this.createParticleBeam(position, finalRotation, color); + } else { + this.updateParticleBeam(position, finalRotation, color); + } + }; + + this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + + var handToObject = Vec3.subtract(objectPosition, handPosition); + var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); + + var distance = Vec3.distance(handPosition, objectPosition); + var speed = distance * 1; + + var lifepsan = distance / speed; + var lifespan = 1; + + if (this.particleBeam === null) { + this.createParticleBeam(objectPosition, finalRotation, color, speed); + } else { + this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + } + }; + + this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + + var particleBeamProperties = { + type: "ParticleEffect", + isEmitting: true, + position: position, + visible: false, + //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + "name": "Particle Beam", + "color": color, + "maxParticles": 2000, + "lifespan": LINE_LENGTH / 10, + "emitRate": 50, + "emitSpeed": 5, + "speedSpread": 2, + "emitOrientation": { + "x": -1, + "y": 0, + "z": 0, + "w": 1 + }, + "emitDimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitRadiusStart": 0.5, + "polarStart": 0, + "polarFinish": 0, + "azimuthStart": -3.1415927410125732, + "azimuthFinish": 3.1415927410125732, + "emitAcceleration": { + x: 0, + y: 0, + z: 0 + }, + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.01, + "radiusSpread": 0, + // "radiusStart": 0.01, + // "radiusFinish": 0.01, + // "colorSpread": { + // "red": 0, + // "green": 0, + // "blue": 0 + // }, + // "colorStart": color, + // "colorFinish": color, + "alpha": 1, + "alphaSpread": 0, + "alphaStart": 1, + "alphaFinish": 1, + "additiveBlending": 1, + "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" + } + + this.particleBeam = Entities.addEntity(particleBeamProperties); + }; + + this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { + + Entities.editEntity(this.particleBeam, { + rotation: orientation, + position: position, + visible: true, + color: color, + emitSpeed: speed, + lifepsan: lifepsan + + }) + + }; + + this.evalLightWorldTransform = function(modelPos, modelRot) { + + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; + }; + + this.handleSpotlight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + var lightProperties = { + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.spotlight === null) { + this.spotlight = Entities.addEntity(lightProperties); + } else { + Entities.editEntity(this.spotlight, { + //without this, this light would maintain rotation with its parent + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + }) + } + }; + + this.handlePointLight = function(parentID, position) { + var LIFETIME = 100; + + var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); + var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + + var lightProperties = { + type: "Light", + isSpotlight: false, + dimensions: { + x: 2, + y: 2, + z: 20 + }, + parentID: parentID, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 2, + exponent: 0.3, + cutoff: 20, + lifetime: LIFETIME, + position: lightTransform.p, + }; + + if (this.pointlight === null) { + this.pointlight = Entities.addEntity(lightProperties); + } else { + + } + }; + + this.lineOff = function() { + if (this.pointer !== null) { + Entities.deleteEntity(this.pointer); + } + this.pointer = null; + }; + + this.overlayLineOff = function() { + if (this.overlayLine !== null) { + Overlays.deleteOverlay(this.overlayLine); + } + this.overlayLine = null; + }; + + this.particleBeamOff = function() { + if (this.particleBeam !== null) { + Entities.editEntity(this.particleBeam, { + visible: false + }) + } + } + + this.turnLightsOff = function() { + if (this.spotlight !== null) { + Entities.deleteEntity(this.spotlight); + this.spotlight = null; + } + + if (this.pointlight !== null) { + Entities.deleteEntity(this.pointlight); + this.pointlight = null; + } + }; + + + this.turnOffVisualizations = function() { + if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOff(); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOff(); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.particleBeamOff(); + } + }; + + this.triggerPress = function(value) { + _this.rawTriggerValue = value; + }; + + this.bumperPress = function(value) { + _this.rawBumperValue = value; + }; + + this.updateSmoothedTrigger = function() { + var triggerValue = this.rawTriggerValue; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; + return triggerValue > TRIGGER_ON_VALUE; + }; + + this.bumperSqueezed = function() { + return _this.rawBumperValue > BUMPER_ON_VALUE; + }; + + this.bumperReleased = function() { + return _this.rawBumperValue < BUMPER_ON_VALUE; + }; + + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_SEARCHING); + return; + } + if (this.bumperSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_EQUIP_SEARCHING); + return; + } + }; + + this.search = function() { + this.grabbedEntity = null; + + if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { + this.setState(STATE_RELEASE); + return; + } + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var distantPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), + length: PICK_MAX_DISTANCE + }; + + // don't pick 60x per second. + var pickRays = []; + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + pickRays = [distantPickRay]; + this.lastPickTime = now; + } + + for (var index = 0; index < pickRays.length; ++index) { + var pickRay = pickRays[index]; + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + if (WANT_DEBUG) { + this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { + red: 0, + green: 255, + blue: 0 + }) + } + + var intersection = Entities.findRayIntersection(pickRayBacked, true); + + if (intersection.intersects) { + // the ray is intersecting something we can move. + var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + + if (intersection.properties.name == "Grab Debug Entity") { + continue; + } + + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { + continue; + } + if (intersectionDistance > pickRay.length) { + // too far away for this ray. + continue; + } + if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + // the hand is very close to the intersected object. go into close-grabbing mode. + if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!intersection.properties.locked) { + this.grabbedEntity = intersection.entityID; + if (this.state == STATE_SEARCHING) { + this.setState(STATE_NEAR_GRABBING); + } else { // equipping + if (typeof grabbableData.spatialKey !== 'undefined') { + // TODO + // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch + // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + } else { + this.setState(STATE_EQUIP); + } + } + return; + } + } else if (!entityIsGrabbedByOther(intersection.entityID)) { + // don't allow two people to distance grab the same object + if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { + // the hand is far from the intersected object. go into distance-holding mode + this.grabbedEntity = intersection.entityID; + if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { + // if a distance pick in equip mode hits something with a spatialKey, equip it + // TODO use STATE_EQUIP_SPRING here once it works right. + // this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP); + return; + } else if (this.state == STATE_SEARCHING) { + this.setState(STATE_DISTANCE_HOLDING); + return; + } + } else if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_FAR_TRIGGER); + return; + } + } + } + } + + // forward ray test failed, try sphere test. + if (WANT_DEBUG) { + Entities.addEntity({ + type: "Sphere", + name: "Grab Debug Entity", + dimensions: { + x: GRAB_RADIUS, + y: GRAB_RADIUS, + z: GRAB_RADIUS + }, + visible: true, + position: handPosition, + color: { + red: 0, + green: 255, + blue: 0 + }, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var minDistance = PICK_MAX_DISTANCE; + var i, props, distance, grabbableData; + this.grabbedEntity = null; + for (i = 0; i < nearbyEntities.length; i++) { + var grabbableDataForCandidate = + getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); + if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { + continue; + } + var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); + + if (propsForCandidate.type == 'Unknown') { + continue; + } + + if (propsForCandidate.type == 'Light') { + continue; + } + + if (propsForCandidate.type == 'ParticleEffect') { + continue; + } + + if (propsForCandidate.type == 'PolyLine') { + continue; + } + + if (propsForCandidate.type == 'Zone') { + continue; + } + + if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { + continue; + } + + if (propsForCandidate.name == "Grab Debug Entity") { + continue; + } + + if (propsForCandidate.name == "grab pointer") { + continue; + } + + distance = Vec3.distance(propsForCandidate.position, handPosition); + if (distance < minDistance) { + this.grabbedEntity = nearbyEntities[i]; + minDistance = distance; + props = propsForCandidate; + grabbableData = grabbableDataForCandidate; + } + } + if (this.grabbedEntity !== null) { + if (grabbableData.wantsTrigger) { + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!props.locked && props.collisionsWillMove) { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + return; + } + } + + //search line visualizations + if (USE_ENTITY_LINES_FOR_SEARCHING === true) { + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { + this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); + } + + }; + + this.distanceHolding = function() { + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); + this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; + + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + tag: getTag(), + ttl: ACTION_TTL + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + + if (this.actionID !== null) { + this.setState(STATE_CONTINUE_DISTANCE_HOLDING); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); + } + + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + + this.turnOffVisualizations(); + }; + + this.continueDistanceHolding = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + var handPosition = this.getHandPosition(); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && + typeof grabbableData.spatialKey !== 'undefined') { + var saveGrabbedID = this.grabbedEntity; + this.release(); + this.setState(STATE_EQUIP); + this.grabbedEntity = saveGrabbedID; + return; + } + + + // the action was set up on a previous call. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? + var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); + this.handRelativePreviousPosition = handToAvatar; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); + var superHandMoved = Vec3.multiply(handMoved, radius); + + // Move the object by the magnified amount and then by amount from avatar movement & rotation + var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters + var now = Date.now(); + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentObjectPosition = newObjectPosition; + this.currentObjectTime = now; + + // this doubles hand rotation + var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, + handRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.handPreviousRotation)); + this.handPreviousRotation = handRotation; + this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + + Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + + + //visualizations + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); + } + if (USE_OVERLAY_LINES_FOR_MOVING === true) { + this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); + } + if (USE_PARTICLE_BEAM_FOR_MOVING === true) { + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + } + if (USE_POINTLIGHT === true) { + this.handlePointLight(this.grabbedEntity); + } + if (USE_SPOTLIGHT === true) { + this.handleSpotlight(this.grabbedEntity); + } + + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + targetRotation: this.currentObjectRotation, + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + ttl: ACTION_TTL + }); + + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + }; + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + } else { + this.ignoreIK = false; + + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } else { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + } + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; + + this.continueNearGrabbing = function() { + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { + this.setState(STATE_CONTINUE_EQUIP); + return; + } + if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { + this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); + return; + } + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { + this.setState(STATE_CONTINUE_EQUIP_BD); + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + return; + } + + // Keep track of the fingertip velocity to impart when we release the object. + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var now = Date.now(); + + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentHandControllerTipPosition = handControllerPosition; + this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + Entities.updateAction(this.grabbedEntity, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } + }; + + this.waitingForBumperRelease = function() { + if (this.bumperReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + Entities.callEntityMethod(this.grabbedEntity, "unequip"); + this.endHandGrasp(); + + } + }; + + this.pullTowardEquipPosition = function() { + + this.turnOffVisualizations(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + // use a spring to pull the object to where it will be when equipped + var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + var targetRotation = Quat.multiply(handRotation, relativeRotation); + var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); + var targetPosition = Vec3.sum(handPosition, offset); + + if (typeof this.equipSpringID === 'undefined' || + this.equipSpringID === null || + this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + if (this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = null; + this.setState(STATE_OFF); + return; + } + } else { + Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL, + ignoreIK: ignoreIK + }); + } + + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipSpringID); + this.equipSpringID = null; + this.setState(STATE_EQUIP); + } + }; + + this.nearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.setState(STATE_CONTINUE_NEAR_TRIGGER); + }; + + this.farTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.setState(STATE_CONTINUE_FAR_TRIGGER); + }; + + this.continueNearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + }; + + this.continueFarTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + var intersection = Entities.findRayIntersection(pickRay, true); + this.lastPickTime = now; + if (intersection.entityID != this.grabbedEntity) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + } + + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + }; + + _this.allTouchedIDs = {}; + + this.touchTest = function() { + var maxDistance = 0.05; + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); + var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); + var ids = []; + + if (leftEntities.length !== 0) { + leftEntities.forEach(function(entity) { + ids.push(entity); + }); + + } + + if (rightEntities.length !== 0) { + rightEntities.forEach(function(entity) { + ids.push(entity); + }); + } + + ids.forEach(function(id) { + + var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); + if (props.name === 'pointer') { + return; + } else { + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); + var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); + + if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { + // we haven't been touched before, but either right or left is touching us now + _this.allTouchedIDs[id] = true; + _this.startTouch(id); + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { + // we have been touched before and are still being touched + // continue touch + _this.continueTouch(id); + } else if (_this.allTouchedIDs[id]) { + delete _this.allTouchedIDs[id]; + _this.stopTouch(id); + + } else { + //we are in another state + return; + } + } + + }); + + }; + + this.startTouch = function(entityID) { + Entities.callEntityMethod(entityID, "startTouch"); + }; + + this.continueTouch = function(entityID) { + Entities.callEntityMethod(entityID, "continueTouch"); + }; + + this.stopTouch = function(entityID) { + Entities.callEntityMethod(entityID, "stopTouch"); + }; + + this.release = function() { + + this.turnLightsOff(); + this.turnOffVisualizations(); + + if (this.grabbedEntity !== null) { + if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } + } + + this.deactivateEntity(this.grabbedEntity); + + this.grabbedEntity = null; + this.actionID = null; + this.setState(STATE_OFF); + }; + + this.cleanup = function() { + this.release(); + this.endHandGrasp(); + Entities.deleteEntity(this.particleBeam); + Entities.deleteEntity(this.spotLight); + Entities.deleteEntity(this.pointLight); + }; + + this.activateEntity = function(entityID, grabbedProperties) { + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + data["activated"] = true; + data["avatarId"] = MyAvatar.sessionUUID; + data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done + if (data["refCount"] == 1) { + data["gravity"] = grabbedProperties.gravity; + data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; + data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + } + }; + if (invertSolidWhileHeld) { + whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; + } + Entities.editEntity(entityID, whileHeldProperties); + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + return data; + }; + + this.deactivateEntity = function(entityID) { + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + Entities.editEntity(entityID, { + gravity: data["gravity"], + ignoreForCollisions: data["ignoreForCollisions"], + collisionsWillMove: data["collisionsWillMove"] + }); + data = null; + } + } else { + data = null; + } + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + }; + + this.graspHandler = null + + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + }; + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + }; + +}; + +var rightController = new MyController(RIGHT_HAND); +var leftController = new MyController(LEFT_HAND); + +//preload the particle beams so that they are full length when you start searching +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + rightController.createParticleBeam(); + leftController.createParticleBeam(); +} + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); + +mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); +mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); + +Controller.enableMapping(MAPPING_NAME); + +//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items +var handToDisable = 'none'; + +function update() { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { + leftController.update(); + } + if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { + rightController.update(); + } +} + +Messages.subscribe('Hifi-Hand-Disabler'); + +handleHandDisablerMessages = function(channel, message, sender) { + + if (sender === MyAvatar.sessionUUID) { + if (message === 'left') { + handToDisable = LEFT_HAND; + } + if (message === 'right') { + handToDisable = RIGHT_HAND; + } + if (message === 'both') { + handToDisable = 'both'; + } + if (message === 'none') { + handToDisable = 'none'; + } + } + +} + +Messages.messageReceived.connect(handleHandDisablerMessages); + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); \ No newline at end of file From 76487bca7ac69c8fdaa2ec9e8eaaa281871b378e Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 14 Dec 2015 13:01:03 -0800 Subject: [PATCH 18/24] updates --- .../handControllerGrab-particles.js | 47 +++++++++++-------- .../handControllerGrab-pointlight.js | 47 +++++++++++-------- .../handControllerGrab-spotlight.js | 47 +++++++++++-------- examples/controllers/handControllerGrab.js | 47 +++++++++++-------- 4 files changed, 108 insertions(+), 80 deletions(-) diff --git a/examples/controllers/handControllerGrab-particles.js b/examples/controllers/handControllerGrab-particles.js index d9ecb18b01..bfe51927d0 100644 --- a/examples/controllers/handControllerGrab-particles.js +++ b/examples/controllers/handControllerGrab-particles.js @@ -437,47 +437,49 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - + var lifespan = LINE_LENGTH / 10; + var speed = 5; + var spread = 2; if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color); + this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(position, finalRotation, color); + this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); var distance = Vec3.distance(handPosition, objectPosition); - var speed = distance * 1; + var speed = 5; + var spread = 0; + + var lifespan = distance / speed; - var lifepsan = distance / speed; - var lifespan = 1; if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed); + this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } }; - this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, position: position, visible: false, - //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": LINE_LENGTH / 10, + "lifespan": lifespan, "emitRate": 50, - "emitSpeed": 5, - "speedSpread": 2, + "emitSpeed": speed, + "speedSpread": spread, "emitOrientation": { "x": -1, "y": 0, @@ -519,22 +521,23 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 1, + "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); }; - this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - + this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { + print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, visible: true, color: color, emitSpeed: speed, - lifepsan: lifepsan + speedSpread:spread, + lifespan: lifespan }) @@ -1108,7 +1111,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1399,7 +1403,10 @@ function MyController(hand) { } } - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); }; diff --git a/examples/controllers/handControllerGrab-pointlight.js b/examples/controllers/handControllerGrab-pointlight.js index fc14e6026e..e7bb4e3e9f 100644 --- a/examples/controllers/handControllerGrab-pointlight.js +++ b/examples/controllers/handControllerGrab-pointlight.js @@ -437,47 +437,49 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - + var lifespan = LINE_LENGTH / 10; + var speed = 5; + var spread = 2; if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color); + this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(position, finalRotation, color); + this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); var distance = Vec3.distance(handPosition, objectPosition); - var speed = distance * 1; + var speed = 5; + var spread = 0; + + var lifespan = distance / speed; - var lifepsan = distance / speed; - var lifespan = 1; if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed); + this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } }; - this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, position: position, visible: false, - //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": LINE_LENGTH / 10, + "lifespan": lifespan, "emitRate": 50, - "emitSpeed": 5, - "speedSpread": 2, + "emitSpeed": speed, + "speedSpread": spread, "emitOrientation": { "x": -1, "y": 0, @@ -519,22 +521,23 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 1, + "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); }; - this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - + this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { + print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, visible: true, color: color, emitSpeed: speed, - lifepsan: lifepsan + speedSpread:spread, + lifespan: lifespan }) @@ -1108,7 +1111,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1399,7 +1403,10 @@ function MyController(hand) { } } - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); }; diff --git a/examples/controllers/handControllerGrab-spotlight.js b/examples/controllers/handControllerGrab-spotlight.js index ea05a44f78..ee6dcfa681 100644 --- a/examples/controllers/handControllerGrab-spotlight.js +++ b/examples/controllers/handControllerGrab-spotlight.js @@ -437,47 +437,49 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - + var lifespan = LINE_LENGTH / 10; + var speed = 5; + var spread = 2; if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color); + this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(position, finalRotation, color); + this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); var distance = Vec3.distance(handPosition, objectPosition); - var speed = distance * 1; + var speed = 5; + var spread = 0; + + var lifespan = distance / speed; - var lifepsan = distance / speed; - var lifespan = 1; if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed); + this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } }; - this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, position: position, visible: false, - //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": LINE_LENGTH / 10, + "lifespan": lifespan, "emitRate": 50, - "emitSpeed": 5, - "speedSpread": 2, + "emitSpeed": speed, + "speedSpread": spread, "emitOrientation": { "x": -1, "y": 0, @@ -519,22 +521,23 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 1, + "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); }; - this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - + this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { + print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, visible: true, color: color, emitSpeed: speed, - lifepsan: lifepsan + speedSpread:spread, + lifespan: lifespan }) @@ -1108,7 +1111,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1399,7 +1403,10 @@ function MyController(hand) { } } - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); }; diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index cd82db22b9..b6f1a82c3f 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -437,47 +437,49 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - + var lifespan = LINE_LENGTH / 10; + var speed = 5; + var spread = 2; if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color); + this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(position, finalRotation, color); + this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); var distance = Vec3.distance(handPosition, objectPosition); - var speed = distance * 1; + var speed = 5; + var spread = 0; + + var lifespan = distance / speed; - var lifepsan = distance / speed; - var lifespan = 1; if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed); + this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } }; - this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, position: position, visible: false, - //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": LINE_LENGTH / 10, + "lifespan": lifespan, "emitRate": 50, - "emitSpeed": 5, - "speedSpread": 2, + "emitSpeed": speed, + "speedSpread": spread, "emitOrientation": { "x": -1, "y": 0, @@ -519,22 +521,23 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 1, + "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); }; - this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - + this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { + print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, visible: true, color: color, emitSpeed: speed, - lifepsan: lifepsan + speedSpread:spread, + lifespan: lifespan }) @@ -1108,7 +1111,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1399,7 +1403,10 @@ function MyController(hand) { } } - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); }; From e3e1bc1dc611b0bbb00860807a04018aa04c7105 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 14 Dec 2015 13:01:44 -0800 Subject: [PATCH 19/24] updates --- .../handControllerGrab-all-overlays.js | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/examples/controllers/handControllerGrab-all-overlays.js b/examples/controllers/handControllerGrab-all-overlays.js index acb47b2260..67e2b1f0a7 100644 --- a/examples/controllers/handControllerGrab-all-overlays.js +++ b/examples/controllers/handControllerGrab-all-overlays.js @@ -437,47 +437,49 @@ function MyController(hand) { }); var finalRotation = Quat.multiply(orientation, rotation); - + var lifespan = LINE_LENGTH / 10; + var speed = 5; + var spread = 2; if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color); + this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(position, finalRotation, color); + this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, objectRotation, color) { + this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); var distance = Vec3.distance(handPosition, objectPosition); - var speed = distance * 1; + var speed = 5; + var spread = 0; + + var lifespan = distance / speed; - var lifepsan = distance / speed; - var lifespan = 1; if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed); + this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, lifepsan); + this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); } }; - this.createParticleBeam = function(position, orientation, color, speed, lifepsan) { + this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { var particleBeamProperties = { type: "ParticleEffect", isEmitting: true, position: position, visible: false, - //rotation:Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), "name": "Particle Beam", "color": color, "maxParticles": 2000, - "lifespan": LINE_LENGTH / 10, + "lifespan": lifespan, "emitRate": 50, - "emitSpeed": 5, - "speedSpread": 2, + "emitSpeed": speed, + "speedSpread": spread, "emitOrientation": { "x": -1, "y": 0, @@ -519,22 +521,23 @@ function MyController(hand) { "alphaSpread": 0, "alphaStart": 1, "alphaFinish": 1, - "additiveBlending": 1, + "additiveBlending": 0, "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" } this.particleBeam = Entities.addEntity(particleBeamProperties); }; - this.updateParticleBeam = function(position, orientation, color, speed, lifepsan) { - + this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { + print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, visible: true, color: color, emitSpeed: speed, - lifepsan: lifepsan + speedSpread:spread, + lifespan: lifespan }) @@ -1108,7 +1111,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition, grabbedProperties.position, this.currentObjectRotation, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity); @@ -1399,7 +1403,10 @@ function MyController(hand) { } } - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + if (USE_ENTITY_LINES_FOR_MOVING === true) { + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + } + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); }; From 7bc7bddc3aed5b4ab26abe1f1e8a9a2bb60112d2 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 22 Dec 2015 12:00:45 -0800 Subject: [PATCH 20/24] set defaults --- .../handControllerGrab-all-overlays.js | 1656 ----------------- .../handControllerGrab-particles.js | 1656 ----------------- .../handControllerGrab-pointlight.js | 1656 ----------------- .../handControllerGrab-spotlight.js | 1656 ----------------- examples/controllers/handControllerGrab.js | 8 +- 5 files changed, 4 insertions(+), 6628 deletions(-) delete mode 100644 examples/controllers/handControllerGrab-all-overlays.js delete mode 100644 examples/controllers/handControllerGrab-particles.js delete mode 100644 examples/controllers/handControllerGrab-pointlight.js delete mode 100644 examples/controllers/handControllerGrab-spotlight.js diff --git a/examples/controllers/handControllerGrab-all-overlays.js b/examples/controllers/handControllerGrab-all-overlays.js deleted file mode 100644 index 67e2b1f0a7..0000000000 --- a/examples/controllers/handControllerGrab-all-overlays.js +++ /dev/null @@ -1,1656 +0,0 @@ -// handControllerGrab.js -// -// Created by Eric Levin on 9/2/15 -// Additions by James B. Pollack @imgntn on 9/24/2015 -// Additions By Seth Alves on 10/20/2015 -// Copyright 2015 High Fidelity, Inc. -// -// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. -// Also supports touch and equipping objects. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ - -Script.include("../libraries/utils.js"); - -// -// add lines where the hand ray picking is happening -// -var WANT_DEBUG = false; - -// -// these tune time-averaging and "on" value for analog trigger -// - -var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.4; -var TRIGGER_OFF_VALUE = 0.15; - -var BUMPER_ON_VALUE = 0.5; - -// -// distant manipulation -// - -var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object -var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects - -var NO_INTERSECT_COLOR = { - red: 10, - green: 10, - blue: 255 -}; // line color when pick misses -var INTERSECT_COLOR = { - red: 250, - green: 10, - blue: 10 -}; // line color when pick hits -var LINE_ENTITY_DIMENSIONS = { - x: 1000, - y: 1000, - z: 1000 -}; - -var LINE_LENGTH = 500; -var PICK_MAX_DISTANCE = 500; // max length of pick-ray - -// -// near grabbing -// - -var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected -var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected -var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things -var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object -var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed - -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - -// -// other constants -// - -var RIGHT_HAND = 1; -var LEFT_HAND = 0; - -var ZERO_VEC = { - x: 0, - y: 0, - z: 0 -}; - -var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; -var MSEC_PER_SEC = 1000.0; - -// these control how long an abandoned pointer line or action will hang around -var LIFETIME = 10; -var ACTION_TTL = 15; // seconds -var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 5; -var MSECS_PER_SEC = 1000.0; -var GRABBABLE_PROPERTIES = [ - "position", - "rotation", - "gravity", - "ignoreForCollisions", - "collisionsWillMove", - "locked", - "name" -]; - -var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js -var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js - -var DEFAULT_GRABBABLE_DATA = { - grabbable: true, - invertSolidWhileHeld: false -}; - -//we've created various ways of visualizing looking for and moving distant objects -var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_PARTICLE_BEAM_FOR_SEARCHING = false; - -var USE_ENTITY_LINES_FOR_MOVING = false; -var USE_OVERLAY_LINES_FOR_MOVING = true; -var USE_PARTICLE_BEAM_FOR_MOVING = false; - -var USE_SPOTLIGHT = false; -var USE_POINTLIGHT = false; - -// states for the state machine -var STATE_OFF = 0; -var STATE_SEARCHING = 1; -var STATE_DISTANCE_HOLDING = 2; -var STATE_CONTINUE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; -var STATE_EQUIP_SEARCHING = 11; -var STATE_EQUIP = 12 -var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down -var STATE_CONTINUE_EQUIP = 14; -var STATE_WAITING_FOR_BUMPER_RELEASE = 15; -var STATE_EQUIP_SPRING = 16; - - - -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP_SEARCHING: - return "equip_searching"; - case STATE_EQUIP: - return "equip"; - case STATE_CONTINUE_EQUIP_BD: - return "continue_equip_bd"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_BUMPER_RELEASE: - return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; - } - - return "unknown"; -} - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; - if (tag == getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 5) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -} - -function getSpatialOffsetPosition(hand, spatialKey) { - var position = Vec3.ZERO; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { - position = spatialKey.leftRelativePosition; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { - position = spatialKey.rightRelativePosition; - } - if (spatialKey.relativePosition) { - position = spatialKey.relativePosition; - } - - return position; -} - -var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); - -function getSpatialOffsetRotation(hand, spatialKey) { - var rotation = Quat.IDENTITY; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { - rotation = spatialKey.leftRelativeRotation; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { - rotation = spatialKey.rightRelativeRotation; - } - if (spatialKey.relativeRotation) { - rotation = spatialKey.relativeRotation; - } - - // Flip left hand - if (hand !== RIGHT_HAND) { - rotation = Quat.multiply(yFlip, rotation); - } - - return rotation; -} - -function MyController(hand) { - this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - - var SPATIAL_CONTROLLERS_PER_PALM = 2; - var TIP_CONTROLLER_OFFSET = 1; - this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; - this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; - - this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. - this.state = STATE_OFF; - this.pointer = null; // entity-id of line object - this.triggerValue = 0; // rolling average of trigger value - this.rawTriggerValue = 0; - this.rawBumperValue = 0; - - //for visualizations - this.overlayLine = null; - this.particleBeam = null; - - //for lights - this.spotlight = null; - this.pointlight = null; - - this.ignoreIK = false; - this.offsetPosition = Vec3.ZERO; - this.offsetRotation = Quat.IDENTITY; - - var _this = this; - - this.update = function() { - - this.updateSmoothedTrigger(); - - switch (this.state) { - case STATE_OFF: - this.off(); - this.touchTest(); - break; - case STATE_SEARCHING: - this.search(); - break; - case STATE_EQUIP_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_BUMPER_RELEASE: - this.waitingForBumperRelease(); - break; - case STATE_EQUIP_SPRING: - this.pullTowardEquipPosition() - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_EQUIP_BD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; - } - }; - - this.setState = function(newState) { - if (WANT_DEBUG) { - print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); - } - this.state = newState; - }; - - this.debugLine = function(closePoint, farPoint, color) { - Entities.addEntity({ - type: "Line", - name: "Grab Debug Entity", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - }; - - this.lineOn = function(closePoint, farPoint, color) { - // draw a line - if (this.pointer === null) { - this.pointer = Entities.addEntity({ - type: "Line", - name: "grab pointer", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: LIFETIME, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } else { - var age = Entities.getEntityProperties(this.pointer, "age").age; - this.pointer = Entities.editEntity(this.pointer, { - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: age + LIFETIME - }); - } - }; - - this.overlayLineOn = function(closePoint, farPoint, color) { - if (this.overlayLine === null) { - var lineProperties = { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - visible: true, - alpha: 1 - }; - - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.overlayLine, { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - visible: true, - ignoreRayIntersection: true, // always ignore this - alpha: 1 - }); - } - }; - - this.handleParticleBeam = function(position, orientation, color) { - - var rotation = Quat.angleAxis(0, { - x: 1, - y: 0, - z: 0 - }); - - var finalRotation = Quat.multiply(orientation, rotation); - var lifespan = LINE_LENGTH / 10; - var speed = 5; - var spread = 2; - if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } - }; - - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { - - var handToObject = Vec3.subtract(objectPosition, handPosition); - var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); - - var distance = Vec3.distance(handPosition, objectPosition); - var speed = 5; - var spread = 0; - - var lifespan = distance / speed; - - - if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } - }; - - this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - - var particleBeamProperties = { - type: "ParticleEffect", - isEmitting: true, - position: position, - visible: false, - "name": "Particle Beam", - "color": color, - "maxParticles": 2000, - "lifespan": lifespan, - "emitRate": 50, - "emitSpeed": speed, - "speedSpread": spread, - "emitOrientation": { - "x": -1, - "y": 0, - "z": 0, - "w": 1 - }, - "emitDimensions": { - "x": 0, - "y": 0, - "z": 0 - }, - "emitRadiusStart": 0.5, - "polarStart": 0, - "polarFinish": 0, - "azimuthStart": -3.1415927410125732, - "azimuthFinish": 3.1415927410125732, - "emitAcceleration": { - x: 0, - y: 0, - z: 0 - }, - "accelerationSpread": { - "x": 0, - "y": 0, - "z": 0 - }, - "particleRadius": 0.01, - "radiusSpread": 0, - // "radiusStart": 0.01, - // "radiusFinish": 0.01, - // "colorSpread": { - // "red": 0, - // "green": 0, - // "blue": 0 - // }, - // "colorStart": color, - // "colorFinish": color, - "alpha": 1, - "alphaSpread": 0, - "alphaStart": 1, - "alphaFinish": 1, - "additiveBlending": 0, - "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" - } - - this.particleBeam = Entities.addEntity(particleBeamProperties); - }; - - this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - print('lifespan::' + lifespan); - Entities.editEntity(this.particleBeam, { - rotation: orientation, - position: position, - visible: true, - color: color, - emitSpeed: speed, - speedSpread:spread, - lifespan: lifespan - - }) - - }; - - this.evalLightWorldTransform = function(modelPos, modelRot) { - - var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 - }; - - var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - }); - - return { - p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) - }; - }; - - this.handleSpotlight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - var lightProperties = { - type: "Light", - isSpotlight: true, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.spotlight === null) { - this.spotlight = Entities.addEntity(lightProperties); - } else { - Entities.editEntity(this.spotlight, { - //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - }) - } - }; - - this.handlePointLight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - - var lightProperties = { - type: "Light", - isSpotlight: false, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.pointlight === null) { - this.pointlight = Entities.addEntity(lightProperties); - } else { - - } - }; - - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.particleBeamOff = function() { - if (this.particleBeam !== null) { - Entities.editEntity(this.particleBeam, { - visible: false - }) - } - } - - this.turnLightsOff = function() { - if (this.spotlight !== null) { - Entities.deleteEntity(this.spotlight); - this.spotlight = null; - } - - if (this.pointlight !== null) { - Entities.deleteEntity(this.pointlight); - this.pointlight = null; - } - }; - - - this.turnOffVisualizations = function() { - if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOff(); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOff(); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.particleBeamOff(); - } - }; - - this.triggerPress = function(value) { - _this.rawTriggerValue = value; - }; - - this.bumperPress = function(value) { - _this.rawBumperValue = value; - }; - - this.updateSmoothedTrigger = function() { - var triggerValue = this.rawTriggerValue; - // smooth out trigger value - this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + - (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); - }; - - this.triggerSmoothedSqueezed = function() { - return this.triggerValue > TRIGGER_ON_VALUE; - }; - - this.triggerSmoothedReleased = function() { - return this.triggerValue < TRIGGER_OFF_VALUE; - }; - - this.triggerSqueezed = function() { - var triggerValue = this.rawTriggerValue; - return triggerValue > TRIGGER_ON_VALUE; - }; - - this.bumperSqueezed = function() { - return _this.rawBumperValue > BUMPER_ON_VALUE; - }; - - this.bumperReleased = function() { - return _this.rawBumperValue < BUMPER_ON_VALUE; - }; - - this.off = function() { - if (this.triggerSmoothedSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_SEARCHING); - return; - } - if (this.bumperSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_EQUIP_SEARCHING); - return; - } - }; - - this.search = function() { - this.grabbedEntity = null; - - if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { - this.setState(STATE_RELEASE); - return; - } - - // the trigger is being pressed, do a ray test - var handPosition = this.getHandPosition(); - var distantPickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()), - length: PICK_MAX_DISTANCE - }; - - // don't pick 60x per second. - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - if (WANT_DEBUG) { - this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { - red: 0, - green: 255, - blue: 0 - }) - } - - var intersection = Entities.findRayIntersection(pickRayBacked, true); - - if (intersection.intersects) { - // the ray is intersecting something we can move. - var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); - - if (intersection.properties.name == "Grab Debug Entity") { - continue; - } - - if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { - continue; - } - if (intersectionDistance > pickRay.length) { - // too far away for this ray. - continue; - } - if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { - // the hand is very close to the intersected object. go into close-grabbing mode. - if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!intersection.properties.locked) { - this.grabbedEntity = intersection.entityID; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // equipping - if (typeof grabbableData.spatialKey !== 'undefined') { - // TODO - // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch - // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - } else { - this.setState(STATE_EQUIP); - } - } - return; - } - } else if (!entityIsGrabbedByOther(intersection.entityID)) { - // don't allow two people to distance grab the same object - if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { - // the hand is far from the intersected object. go into distance-holding mode - this.grabbedEntity = intersection.entityID; - if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { - // if a distance pick in equip mode hits something with a spatialKey, equip it - // TODO use STATE_EQUIP_SPRING here once it works right. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - return; - } else if (this.state == STATE_SEARCHING) { - this.setState(STATE_DISTANCE_HOLDING); - return; - } - } else if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_FAR_TRIGGER); - return; - } - } - } - } - - // forward ray test failed, try sphere test. - if (WANT_DEBUG) { - Entities.addEntity({ - type: "Sphere", - name: "Grab Debug Entity", - dimensions: { - x: GRAB_RADIUS, - y: GRAB_RADIUS, - z: GRAB_RADIUS - }, - visible: true, - position: handPosition, - color: { - red: 0, - green: 255, - blue: 0 - }, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } - - var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < nearbyEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); - if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { - continue; - } - var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); - - if (propsForCandidate.type == 'Unknown') { - continue; - } - - if (propsForCandidate.type == 'Light') { - continue; - } - - if (propsForCandidate.type == 'ParticleEffect') { - continue; - } - - if (propsForCandidate.type == 'PolyLine') { - continue; - } - - if (propsForCandidate.type == 'Zone') { - continue; - } - - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - continue; - } - - if (propsForCandidate.name == "Grab Debug Entity") { - continue; - } - - if (propsForCandidate.name == "grab pointer") { - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance < minDistance) { - this.grabbedEntity = nearbyEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if (this.grabbedEntity !== null) { - if (grabbableData.wantsTrigger) { - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!props.locked && props.collisionsWillMove) { - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) - return; - } - } - - //search line visualizations - if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); - } - - }; - - this.distanceHolding = function() { - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); - this.handPreviousRotation = handRotation; - this.currentCameraOrientation = Camera.orientation; - - // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object - this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - tag: getTag(), - ttl: ACTION_TTL - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - - if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); - } - - this.currentAvatarPosition = MyAvatar.position; - this.currentAvatarOrientation = MyAvatar.orientation; - - this.turnOffVisualizations(); - }; - - this.continueDistanceHolding = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - var handPosition = this.getHandPosition(); - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && - typeof grabbableData.spatialKey !== 'undefined') { - var saveGrabbedID = this.grabbedEntity; - this.release(); - this.setState(STATE_EQUIP); - this.grabbedEntity = saveGrabbedID; - return; - } - - - // the action was set up on a previous call. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - // how far did avatar move this timestep? - var currentPosition = MyAvatar.position; - var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); - this.currentAvatarPosition = currentPosition; - - // How far did the avatar turn this timestep? - // Note: The following code is too long because we need a Quat.quatBetween() function - // that returns the minimum quaternion between two quaternions. - var currentOrientation = MyAvatar.orientation; - if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { - var negativeCurrentOrientation = { - x: -currentOrientation.x, - y: -currentOrientation.y, - z: -currentOrientation.z, - w: -currentOrientation.w - }; - var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } else { - var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } - var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); - var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); - var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); - this.currentAvatarOrientation = currentOrientation; - - // how far did hand move this timestep? - var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); - this.handRelativePreviousPosition = handToAvatar; - - // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, handMovementFromTurning); - var superHandMoved = Vec3.multiply(handMoved, radius); - - // Move the object by the magnified amount and then by amount from avatar movement & rotation - var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); - newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); - newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); - - var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters - var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentObjectPosition = newObjectPosition; - this.currentObjectTime = now; - - // this doubles hand rotation - var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); - this.handPreviousRotation = handRotation; - this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); - - Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); - } - - - //visualizations - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - } - if (USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); - } - if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) - } - if (USE_POINTLIGHT === true) { - this.handlePointLight(this.grabbedEntity); - } - if (USE_SPOTLIGHT === true) { - this.handleSpotlight(this.grabbedEntity); - } - - Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - ttl: ACTION_TTL - }); - - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - }; - - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } - - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - } else { - this.ignoreIK = false; - - var objectRotation = grabbedProperties.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); - - this.setState(STATE_CONTINUE_EQUIP_BD); - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - - } - - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - - this.currentObjectTime = Date.now(); - }; - - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { - this.setState(STATE_CONTINUE_EQUIP); - return; - } - if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { - this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); - return; - } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { - this.setState(STATE_CONTINUE_EQUIP_BD); - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - return; - } - - // Keep track of the fingertip velocity to impart when we release the object. - // Note that the idea of using a constant 'tip' velocity regardless of the - // object's actual held offset is an idea intended to make it easier to throw things: - // Because we might catch something or transfer it between hands without a good idea - // of it's actual offset, let's try imparting a velocity which is at a fixed radius - // from the palm. - - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentHandControllerTipPosition = handControllerPosition; - this.currentObjectTime = now; - Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); - - if (this.state === STATE_CONTINUE_EQUIP_BD) { - Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); - } - - if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { - // if less than a 5 seconds left, refresh the actions ttl - Entities.updateAction(this.grabbedEntity, this.actionID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - } - }; - - this.waitingForBumperRelease = function() { - if (this.bumperReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - Entities.callEntityMethod(this.grabbedEntity, "unequip"); - this.endHandGrasp(); - - } - }; - - this.pullTowardEquipPosition = function() { - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - // use a spring to pull the object to where it will be when equipped - var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var targetRotation = Quat.multiply(handRotation, relativeRotation); - var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); - var targetPosition = Vec3.sum(handPosition, offset); - - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = null; - this.setState(STATE_OFF); - return; - } - } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - } - - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; - this.setState(STATE_EQUIP); - } - }; - - this.nearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); - }; - - this.farTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); - }; - - this.continueNearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); - }; - - this.continueFarTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - var handPosition = this.getHandPosition(); - var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - } - - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); - }; - - _this.allTouchedIDs = {}; - - this.touchTest = function() { - var maxDistance = 0.05; - var leftHandPosition = MyAvatar.getLeftPalmPosition(); - var rightHandPosition = MyAvatar.getRightPalmPosition(); - var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); - var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); - var ids = []; - - if (leftEntities.length !== 0) { - leftEntities.forEach(function(entity) { - ids.push(entity); - }); - - } - - if (rightEntities.length !== 0) { - rightEntities.forEach(function(entity) { - ids.push(entity); - }); - } - - ids.forEach(function(id) { - - var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); - if (props.name === 'pointer') { - return; - } else { - var entityMinPoint = props.boundingBox.brn; - var entityMaxPoint = props.boundingBox.tfl; - var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); - var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); - - if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { - // we haven't been touched before, but either right or left is touching us now - _this.allTouchedIDs[id] = true; - _this.startTouch(id); - } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { - // we have been touched before and are still being touched - // continue touch - _this.continueTouch(id); - } else if (_this.allTouchedIDs[id]) { - delete _this.allTouchedIDs[id]; - _this.stopTouch(id); - - } else { - //we are in another state - return; - } - } - - }); - - }; - - this.startTouch = function(entityID) { - Entities.callEntityMethod(entityID, "startTouch"); - }; - - this.continueTouch = function(entityID) { - Entities.callEntityMethod(entityID, "continueTouch"); - }; - - this.stopTouch = function(entityID) { - Entities.callEntityMethod(entityID, "stopTouch"); - }; - - this.release = function() { - - this.turnLightsOff(); - this.turnOffVisualizations(); - - if (this.grabbedEntity !== null) { - if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); - } - } - - this.deactivateEntity(this.grabbedEntity); - - this.grabbedEntity = null; - this.actionID = null; - this.setState(STATE_OFF); - }; - - this.cleanup = function() { - this.release(); - this.endHandGrasp(); - Entities.deleteEntity(this.particleBeam); - Entities.deleteEntity(this.spotLight); - Entities.deleteEntity(this.pointLight); - }; - - this.activateEntity = function(entityID, grabbedProperties) { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["activated"] = true; - data["avatarId"] = MyAvatar.sessionUUID; - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; - // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["gravity"] = grabbedProperties.gravity; - data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; - data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - } - }; - if (invertSolidWhileHeld) { - whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; - } - Entities.editEntity(entityID, whileHeldProperties); - } - - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - return data; - }; - - this.deactivateEntity = function(entityID) { - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { - Entities.editEntity(entityID, { - gravity: data["gravity"], - ignoreForCollisions: data["ignoreForCollisions"], - collisionsWillMove: data["collisionsWillMove"] - }); - data = null; - } - } else { - data = null; - } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - }; - - - //this is our handler, where we do the actual work of changing animation settings - this.graspHand = function(animationProperties) { - var result = {}; - //full alpha on overlay for this hand - //set grab to true - //set idle to false - //full alpha on the blend btw open and grab - if (_this.hand === RIGHT_HAND) { - result['rightHandOverlayAlpha'] = 1.0; - result['isRightHandGrab'] = true; - result['isRightHandIdle'] = false; - result['rightHandGrabBlend'] = 1.0; - } else if (_this.hand === LEFT_HAND) { - result['leftHandOverlayAlpha'] = 1.0; - result['isLeftHandGrab'] = true; - result['isLeftHandIdle'] = false; - result['leftHandGrabBlend'] = 1.0; - } - //return an object with our updated settings - return result; - }; - - this.graspHandler = null - - this.startHandGrasp = function() { - if (this.hand === RIGHT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); - } else if (this.hand === LEFT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); - } - }; - - this.endHandGrasp = function() { - // Tell the animation system we don't need any more callbacks. - MyAvatar.removeAnimationStateHandler(this.graspHandler); - }; - -}; - -var rightController = new MyController(RIGHT_HAND); -var leftController = new MyController(LEFT_HAND); - -//preload the particle beams so that they are full length when you start searching -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - rightController.createParticleBeam(); - leftController.createParticleBeam(); -} - -var MAPPING_NAME = "com.highfidelity.handControllerGrab"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); - -mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); - -Controller.enableMapping(MAPPING_NAME); - -//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items -var handToDisable = 'none'; - -function update() { - if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); - } - if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); - } -} - -Messages.subscribe('Hifi-Hand-Disabler'); - -handleHandDisablerMessages = function(channel, message, sender) { - - if (sender === MyAvatar.sessionUUID) { - if (message === 'left') { - handToDisable = LEFT_HAND; - } - if (message === 'right') { - handToDisable = RIGHT_HAND; - } - if (message === 'both') { - handToDisable = 'both'; - } - if (message === 'none') { - handToDisable = 'none'; - } - } - -} - -Messages.messageReceived.connect(handleHandDisablerMessages); - -function cleanup() { - rightController.cleanup(); - leftController.cleanup(); - Controller.disableMapping(MAPPING_NAME); -} - -Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-particles.js b/examples/controllers/handControllerGrab-particles.js deleted file mode 100644 index bfe51927d0..0000000000 --- a/examples/controllers/handControllerGrab-particles.js +++ /dev/null @@ -1,1656 +0,0 @@ -// handControllerGrab.js -// -// Created by Eric Levin on 9/2/15 -// Additions by James B. Pollack @imgntn on 9/24/2015 -// Additions By Seth Alves on 10/20/2015 -// Copyright 2015 High Fidelity, Inc. -// -// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. -// Also supports touch and equipping objects. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ - -Script.include("../libraries/utils.js"); - -// -// add lines where the hand ray picking is happening -// -var WANT_DEBUG = false; - -// -// these tune time-averaging and "on" value for analog trigger -// - -var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.4; -var TRIGGER_OFF_VALUE = 0.15; - -var BUMPER_ON_VALUE = 0.5; - -// -// distant manipulation -// - -var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object -var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects - -var NO_INTERSECT_COLOR = { - red: 10, - green: 10, - blue: 255 -}; // line color when pick misses -var INTERSECT_COLOR = { - red: 250, - green: 10, - blue: 10 -}; // line color when pick hits -var LINE_ENTITY_DIMENSIONS = { - x: 1000, - y: 1000, - z: 1000 -}; - -var LINE_LENGTH = 500; -var PICK_MAX_DISTANCE = 500; // max length of pick-ray - -// -// near grabbing -// - -var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected -var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected -var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things -var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object -var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed - -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - -// -// other constants -// - -var RIGHT_HAND = 1; -var LEFT_HAND = 0; - -var ZERO_VEC = { - x: 0, - y: 0, - z: 0 -}; - -var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; -var MSEC_PER_SEC = 1000.0; - -// these control how long an abandoned pointer line or action will hang around -var LIFETIME = 10; -var ACTION_TTL = 15; // seconds -var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 5; -var MSECS_PER_SEC = 1000.0; -var GRABBABLE_PROPERTIES = [ - "position", - "rotation", - "gravity", - "ignoreForCollisions", - "collisionsWillMove", - "locked", - "name" -]; - -var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js -var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js - -var DEFAULT_GRABBABLE_DATA = { - grabbable: true, - invertSolidWhileHeld: false -}; - -//we've created various ways of visualizing looking for and moving distant objects -var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = false; -var USE_PARTICLE_BEAM_FOR_SEARCHING = true; - -var USE_ENTITY_LINES_FOR_MOVING = false; -var USE_OVERLAY_LINES_FOR_MOVING = false; -var USE_PARTICLE_BEAM_FOR_MOVING = true; - -var USE_SPOTLIGHT = false; -var USE_POINTLIGHT = false; - -// states for the state machine -var STATE_OFF = 0; -var STATE_SEARCHING = 1; -var STATE_DISTANCE_HOLDING = 2; -var STATE_CONTINUE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; -var STATE_EQUIP_SEARCHING = 11; -var STATE_EQUIP = 12 -var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down -var STATE_CONTINUE_EQUIP = 14; -var STATE_WAITING_FOR_BUMPER_RELEASE = 15; -var STATE_EQUIP_SPRING = 16; - - - -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP_SEARCHING: - return "equip_searching"; - case STATE_EQUIP: - return "equip"; - case STATE_CONTINUE_EQUIP_BD: - return "continue_equip_bd"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_BUMPER_RELEASE: - return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; - } - - return "unknown"; -} - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; - if (tag == getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 5) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -} - -function getSpatialOffsetPosition(hand, spatialKey) { - var position = Vec3.ZERO; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { - position = spatialKey.leftRelativePosition; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { - position = spatialKey.rightRelativePosition; - } - if (spatialKey.relativePosition) { - position = spatialKey.relativePosition; - } - - return position; -} - -var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); - -function getSpatialOffsetRotation(hand, spatialKey) { - var rotation = Quat.IDENTITY; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { - rotation = spatialKey.leftRelativeRotation; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { - rotation = spatialKey.rightRelativeRotation; - } - if (spatialKey.relativeRotation) { - rotation = spatialKey.relativeRotation; - } - - // Flip left hand - if (hand !== RIGHT_HAND) { - rotation = Quat.multiply(yFlip, rotation); - } - - return rotation; -} - -function MyController(hand) { - this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - - var SPATIAL_CONTROLLERS_PER_PALM = 2; - var TIP_CONTROLLER_OFFSET = 1; - this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; - this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; - - this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. - this.state = STATE_OFF; - this.pointer = null; // entity-id of line object - this.triggerValue = 0; // rolling average of trigger value - this.rawTriggerValue = 0; - this.rawBumperValue = 0; - - //for visualizations - this.overlayLine = null; - this.particleBeam = null; - - //for lights - this.spotlight = null; - this.pointlight = null; - - this.ignoreIK = false; - this.offsetPosition = Vec3.ZERO; - this.offsetRotation = Quat.IDENTITY; - - var _this = this; - - this.update = function() { - - this.updateSmoothedTrigger(); - - switch (this.state) { - case STATE_OFF: - this.off(); - this.touchTest(); - break; - case STATE_SEARCHING: - this.search(); - break; - case STATE_EQUIP_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_BUMPER_RELEASE: - this.waitingForBumperRelease(); - break; - case STATE_EQUIP_SPRING: - this.pullTowardEquipPosition() - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_EQUIP_BD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; - } - }; - - this.setState = function(newState) { - if (WANT_DEBUG) { - print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); - } - this.state = newState; - }; - - this.debugLine = function(closePoint, farPoint, color) { - Entities.addEntity({ - type: "Line", - name: "Grab Debug Entity", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - }; - - this.lineOn = function(closePoint, farPoint, color) { - // draw a line - if (this.pointer === null) { - this.pointer = Entities.addEntity({ - type: "Line", - name: "grab pointer", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: LIFETIME, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } else { - var age = Entities.getEntityProperties(this.pointer, "age").age; - this.pointer = Entities.editEntity(this.pointer, { - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: age + LIFETIME - }); - } - }; - - this.overlayLineOn = function(closePoint, farPoint, color) { - if (this.overlayLine === null) { - var lineProperties = { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - visible: true, - alpha: 1 - }; - - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.overlayLine, { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - visible: true, - ignoreRayIntersection: true, // always ignore this - alpha: 1 - }); - } - }; - - this.handleParticleBeam = function(position, orientation, color) { - - var rotation = Quat.angleAxis(0, { - x: 1, - y: 0, - z: 0 - }); - - var finalRotation = Quat.multiply(orientation, rotation); - var lifespan = LINE_LENGTH / 10; - var speed = 5; - var spread = 2; - if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } - }; - - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { - - var handToObject = Vec3.subtract(objectPosition, handPosition); - var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); - - var distance = Vec3.distance(handPosition, objectPosition); - var speed = 5; - var spread = 0; - - var lifespan = distance / speed; - - - if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } - }; - - this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - - var particleBeamProperties = { - type: "ParticleEffect", - isEmitting: true, - position: position, - visible: false, - "name": "Particle Beam", - "color": color, - "maxParticles": 2000, - "lifespan": lifespan, - "emitRate": 50, - "emitSpeed": speed, - "speedSpread": spread, - "emitOrientation": { - "x": -1, - "y": 0, - "z": 0, - "w": 1 - }, - "emitDimensions": { - "x": 0, - "y": 0, - "z": 0 - }, - "emitRadiusStart": 0.5, - "polarStart": 0, - "polarFinish": 0, - "azimuthStart": -3.1415927410125732, - "azimuthFinish": 3.1415927410125732, - "emitAcceleration": { - x: 0, - y: 0, - z: 0 - }, - "accelerationSpread": { - "x": 0, - "y": 0, - "z": 0 - }, - "particleRadius": 0.01, - "radiusSpread": 0, - // "radiusStart": 0.01, - // "radiusFinish": 0.01, - // "colorSpread": { - // "red": 0, - // "green": 0, - // "blue": 0 - // }, - // "colorStart": color, - // "colorFinish": color, - "alpha": 1, - "alphaSpread": 0, - "alphaStart": 1, - "alphaFinish": 1, - "additiveBlending": 0, - "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" - } - - this.particleBeam = Entities.addEntity(particleBeamProperties); - }; - - this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - print('lifespan::' + lifespan); - Entities.editEntity(this.particleBeam, { - rotation: orientation, - position: position, - visible: true, - color: color, - emitSpeed: speed, - speedSpread:spread, - lifespan: lifespan - - }) - - }; - - this.evalLightWorldTransform = function(modelPos, modelRot) { - - var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 - }; - - var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - }); - - return { - p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) - }; - }; - - this.handleSpotlight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - var lightProperties = { - type: "Light", - isSpotlight: true, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.spotlight === null) { - this.spotlight = Entities.addEntity(lightProperties); - } else { - Entities.editEntity(this.spotlight, { - //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - }) - } - }; - - this.handlePointLight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - - var lightProperties = { - type: "Light", - isSpotlight: false, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.pointlight === null) { - this.pointlight = Entities.addEntity(lightProperties); - } else { - - } - }; - - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.particleBeamOff = function() { - if (this.particleBeam !== null) { - Entities.editEntity(this.particleBeam, { - visible: false - }) - } - } - - this.turnLightsOff = function() { - if (this.spotlight !== null) { - Entities.deleteEntity(this.spotlight); - this.spotlight = null; - } - - if (this.pointlight !== null) { - Entities.deleteEntity(this.pointlight); - this.pointlight = null; - } - }; - - - this.turnOffVisualizations = function() { - if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOff(); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOff(); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.particleBeamOff(); - } - }; - - this.triggerPress = function(value) { - _this.rawTriggerValue = value; - }; - - this.bumperPress = function(value) { - _this.rawBumperValue = value; - }; - - this.updateSmoothedTrigger = function() { - var triggerValue = this.rawTriggerValue; - // smooth out trigger value - this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + - (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); - }; - - this.triggerSmoothedSqueezed = function() { - return this.triggerValue > TRIGGER_ON_VALUE; - }; - - this.triggerSmoothedReleased = function() { - return this.triggerValue < TRIGGER_OFF_VALUE; - }; - - this.triggerSqueezed = function() { - var triggerValue = this.rawTriggerValue; - return triggerValue > TRIGGER_ON_VALUE; - }; - - this.bumperSqueezed = function() { - return _this.rawBumperValue > BUMPER_ON_VALUE; - }; - - this.bumperReleased = function() { - return _this.rawBumperValue < BUMPER_ON_VALUE; - }; - - this.off = function() { - if (this.triggerSmoothedSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_SEARCHING); - return; - } - if (this.bumperSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_EQUIP_SEARCHING); - return; - } - }; - - this.search = function() { - this.grabbedEntity = null; - - if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { - this.setState(STATE_RELEASE); - return; - } - - // the trigger is being pressed, do a ray test - var handPosition = this.getHandPosition(); - var distantPickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()), - length: PICK_MAX_DISTANCE - }; - - // don't pick 60x per second. - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - if (WANT_DEBUG) { - this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { - red: 0, - green: 255, - blue: 0 - }) - } - - var intersection = Entities.findRayIntersection(pickRayBacked, true); - - if (intersection.intersects) { - // the ray is intersecting something we can move. - var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); - - if (intersection.properties.name == "Grab Debug Entity") { - continue; - } - - if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { - continue; - } - if (intersectionDistance > pickRay.length) { - // too far away for this ray. - continue; - } - if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { - // the hand is very close to the intersected object. go into close-grabbing mode. - if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!intersection.properties.locked) { - this.grabbedEntity = intersection.entityID; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // equipping - if (typeof grabbableData.spatialKey !== 'undefined') { - // TODO - // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch - // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - } else { - this.setState(STATE_EQUIP); - } - } - return; - } - } else if (!entityIsGrabbedByOther(intersection.entityID)) { - // don't allow two people to distance grab the same object - if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { - // the hand is far from the intersected object. go into distance-holding mode - this.grabbedEntity = intersection.entityID; - if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { - // if a distance pick in equip mode hits something with a spatialKey, equip it - // TODO use STATE_EQUIP_SPRING here once it works right. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - return; - } else if (this.state == STATE_SEARCHING) { - this.setState(STATE_DISTANCE_HOLDING); - return; - } - } else if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_FAR_TRIGGER); - return; - } - } - } - } - - // forward ray test failed, try sphere test. - if (WANT_DEBUG) { - Entities.addEntity({ - type: "Sphere", - name: "Grab Debug Entity", - dimensions: { - x: GRAB_RADIUS, - y: GRAB_RADIUS, - z: GRAB_RADIUS - }, - visible: true, - position: handPosition, - color: { - red: 0, - green: 255, - blue: 0 - }, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } - - var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < nearbyEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); - if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { - continue; - } - var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); - - if (propsForCandidate.type == 'Unknown') { - continue; - } - - if (propsForCandidate.type == 'Light') { - continue; - } - - if (propsForCandidate.type == 'ParticleEffect') { - continue; - } - - if (propsForCandidate.type == 'PolyLine') { - continue; - } - - if (propsForCandidate.type == 'Zone') { - continue; - } - - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - continue; - } - - if (propsForCandidate.name == "Grab Debug Entity") { - continue; - } - - if (propsForCandidate.name == "grab pointer") { - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance < minDistance) { - this.grabbedEntity = nearbyEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if (this.grabbedEntity !== null) { - if (grabbableData.wantsTrigger) { - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!props.locked && props.collisionsWillMove) { - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) - return; - } - } - - //search line visualizations - if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); - } - - }; - - this.distanceHolding = function() { - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); - this.handPreviousRotation = handRotation; - this.currentCameraOrientation = Camera.orientation; - - // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object - this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - tag: getTag(), - ttl: ACTION_TTL - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - - if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); - } - - this.currentAvatarPosition = MyAvatar.position; - this.currentAvatarOrientation = MyAvatar.orientation; - - this.turnOffVisualizations(); - }; - - this.continueDistanceHolding = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - var handPosition = this.getHandPosition(); - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && - typeof grabbableData.spatialKey !== 'undefined') { - var saveGrabbedID = this.grabbedEntity; - this.release(); - this.setState(STATE_EQUIP); - this.grabbedEntity = saveGrabbedID; - return; - } - - - // the action was set up on a previous call. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - // how far did avatar move this timestep? - var currentPosition = MyAvatar.position; - var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); - this.currentAvatarPosition = currentPosition; - - // How far did the avatar turn this timestep? - // Note: The following code is too long because we need a Quat.quatBetween() function - // that returns the minimum quaternion between two quaternions. - var currentOrientation = MyAvatar.orientation; - if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { - var negativeCurrentOrientation = { - x: -currentOrientation.x, - y: -currentOrientation.y, - z: -currentOrientation.z, - w: -currentOrientation.w - }; - var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } else { - var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } - var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); - var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); - var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); - this.currentAvatarOrientation = currentOrientation; - - // how far did hand move this timestep? - var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); - this.handRelativePreviousPosition = handToAvatar; - - // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, handMovementFromTurning); - var superHandMoved = Vec3.multiply(handMoved, radius); - - // Move the object by the magnified amount and then by amount from avatar movement & rotation - var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); - newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); - newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); - - var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters - var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentObjectPosition = newObjectPosition; - this.currentObjectTime = now; - - // this doubles hand rotation - var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); - this.handPreviousRotation = handRotation; - this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); - - Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); - } - - - //visualizations - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - } - if (USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); - } - if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) - } - if (USE_POINTLIGHT === true) { - this.handlePointLight(this.grabbedEntity); - } - if (USE_SPOTLIGHT === true) { - this.handleSpotlight(this.grabbedEntity); - } - - Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - ttl: ACTION_TTL - }); - - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - }; - - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } - - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - } else { - this.ignoreIK = false; - - var objectRotation = grabbedProperties.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); - - this.setState(STATE_CONTINUE_EQUIP_BD); - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - - } - - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - - this.currentObjectTime = Date.now(); - }; - - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { - this.setState(STATE_CONTINUE_EQUIP); - return; - } - if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { - this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); - return; - } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { - this.setState(STATE_CONTINUE_EQUIP_BD); - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - return; - } - - // Keep track of the fingertip velocity to impart when we release the object. - // Note that the idea of using a constant 'tip' velocity regardless of the - // object's actual held offset is an idea intended to make it easier to throw things: - // Because we might catch something or transfer it between hands without a good idea - // of it's actual offset, let's try imparting a velocity which is at a fixed radius - // from the palm. - - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentHandControllerTipPosition = handControllerPosition; - this.currentObjectTime = now; - Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); - - if (this.state === STATE_CONTINUE_EQUIP_BD) { - Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); - } - - if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { - // if less than a 5 seconds left, refresh the actions ttl - Entities.updateAction(this.grabbedEntity, this.actionID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - } - }; - - this.waitingForBumperRelease = function() { - if (this.bumperReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - Entities.callEntityMethod(this.grabbedEntity, "unequip"); - this.endHandGrasp(); - - } - }; - - this.pullTowardEquipPosition = function() { - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - // use a spring to pull the object to where it will be when equipped - var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var targetRotation = Quat.multiply(handRotation, relativeRotation); - var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); - var targetPosition = Vec3.sum(handPosition, offset); - - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = null; - this.setState(STATE_OFF); - return; - } - } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - } - - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; - this.setState(STATE_EQUIP); - } - }; - - this.nearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); - }; - - this.farTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); - }; - - this.continueNearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); - }; - - this.continueFarTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - var handPosition = this.getHandPosition(); - var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - } - - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); - }; - - _this.allTouchedIDs = {}; - - this.touchTest = function() { - var maxDistance = 0.05; - var leftHandPosition = MyAvatar.getLeftPalmPosition(); - var rightHandPosition = MyAvatar.getRightPalmPosition(); - var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); - var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); - var ids = []; - - if (leftEntities.length !== 0) { - leftEntities.forEach(function(entity) { - ids.push(entity); - }); - - } - - if (rightEntities.length !== 0) { - rightEntities.forEach(function(entity) { - ids.push(entity); - }); - } - - ids.forEach(function(id) { - - var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); - if (props.name === 'pointer') { - return; - } else { - var entityMinPoint = props.boundingBox.brn; - var entityMaxPoint = props.boundingBox.tfl; - var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); - var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); - - if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { - // we haven't been touched before, but either right or left is touching us now - _this.allTouchedIDs[id] = true; - _this.startTouch(id); - } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { - // we have been touched before and are still being touched - // continue touch - _this.continueTouch(id); - } else if (_this.allTouchedIDs[id]) { - delete _this.allTouchedIDs[id]; - _this.stopTouch(id); - - } else { - //we are in another state - return; - } - } - - }); - - }; - - this.startTouch = function(entityID) { - Entities.callEntityMethod(entityID, "startTouch"); - }; - - this.continueTouch = function(entityID) { - Entities.callEntityMethod(entityID, "continueTouch"); - }; - - this.stopTouch = function(entityID) { - Entities.callEntityMethod(entityID, "stopTouch"); - }; - - this.release = function() { - - this.turnLightsOff(); - this.turnOffVisualizations(); - - if (this.grabbedEntity !== null) { - if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); - } - } - - this.deactivateEntity(this.grabbedEntity); - - this.grabbedEntity = null; - this.actionID = null; - this.setState(STATE_OFF); - }; - - this.cleanup = function() { - this.release(); - this.endHandGrasp(); - Entities.deleteEntity(this.particleBeam); - Entities.deleteEntity(this.spotLight); - Entities.deleteEntity(this.pointLight); - }; - - this.activateEntity = function(entityID, grabbedProperties) { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["activated"] = true; - data["avatarId"] = MyAvatar.sessionUUID; - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; - // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["gravity"] = grabbedProperties.gravity; - data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; - data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - } - }; - if (invertSolidWhileHeld) { - whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; - } - Entities.editEntity(entityID, whileHeldProperties); - } - - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - return data; - }; - - this.deactivateEntity = function(entityID) { - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { - Entities.editEntity(entityID, { - gravity: data["gravity"], - ignoreForCollisions: data["ignoreForCollisions"], - collisionsWillMove: data["collisionsWillMove"] - }); - data = null; - } - } else { - data = null; - } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - }; - - - //this is our handler, where we do the actual work of changing animation settings - this.graspHand = function(animationProperties) { - var result = {}; - //full alpha on overlay for this hand - //set grab to true - //set idle to false - //full alpha on the blend btw open and grab - if (_this.hand === RIGHT_HAND) { - result['rightHandOverlayAlpha'] = 1.0; - result['isRightHandGrab'] = true; - result['isRightHandIdle'] = false; - result['rightHandGrabBlend'] = 1.0; - } else if (_this.hand === LEFT_HAND) { - result['leftHandOverlayAlpha'] = 1.0; - result['isLeftHandGrab'] = true; - result['isLeftHandIdle'] = false; - result['leftHandGrabBlend'] = 1.0; - } - //return an object with our updated settings - return result; - }; - - this.graspHandler = null - - this.startHandGrasp = function() { - if (this.hand === RIGHT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); - } else if (this.hand === LEFT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); - } - }; - - this.endHandGrasp = function() { - // Tell the animation system we don't need any more callbacks. - MyAvatar.removeAnimationStateHandler(this.graspHandler); - }; - -}; - -var rightController = new MyController(RIGHT_HAND); -var leftController = new MyController(LEFT_HAND); - -//preload the particle beams so that they are full length when you start searching -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - rightController.createParticleBeam(); - leftController.createParticleBeam(); -} - -var MAPPING_NAME = "com.highfidelity.handControllerGrab"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); - -mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); - -Controller.enableMapping(MAPPING_NAME); - -//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items -var handToDisable = 'none'; - -function update() { - if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); - } - if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); - } -} - -Messages.subscribe('Hifi-Hand-Disabler'); - -handleHandDisablerMessages = function(channel, message, sender) { - - if (sender === MyAvatar.sessionUUID) { - if (message === 'left') { - handToDisable = LEFT_HAND; - } - if (message === 'right') { - handToDisable = RIGHT_HAND; - } - if (message === 'both') { - handToDisable = 'both'; - } - if (message === 'none') { - handToDisable = 'none'; - } - } - -} - -Messages.messageReceived.connect(handleHandDisablerMessages); - -function cleanup() { - rightController.cleanup(); - leftController.cleanup(); - Controller.disableMapping(MAPPING_NAME); -} - -Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-pointlight.js b/examples/controllers/handControllerGrab-pointlight.js deleted file mode 100644 index e7bb4e3e9f..0000000000 --- a/examples/controllers/handControllerGrab-pointlight.js +++ /dev/null @@ -1,1656 +0,0 @@ -// handControllerGrab.js -// -// Created by Eric Levin on 9/2/15 -// Additions by James B. Pollack @imgntn on 9/24/2015 -// Additions By Seth Alves on 10/20/2015 -// Copyright 2015 High Fidelity, Inc. -// -// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. -// Also supports touch and equipping objects. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ - -Script.include("../libraries/utils.js"); - -// -// add lines where the hand ray picking is happening -// -var WANT_DEBUG = false; - -// -// these tune time-averaging and "on" value for analog trigger -// - -var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.4; -var TRIGGER_OFF_VALUE = 0.15; - -var BUMPER_ON_VALUE = 0.5; - -// -// distant manipulation -// - -var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object -var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects - -var NO_INTERSECT_COLOR = { - red: 10, - green: 10, - blue: 255 -}; // line color when pick misses -var INTERSECT_COLOR = { - red: 250, - green: 10, - blue: 10 -}; // line color when pick hits -var LINE_ENTITY_DIMENSIONS = { - x: 1000, - y: 1000, - z: 1000 -}; - -var LINE_LENGTH = 500; -var PICK_MAX_DISTANCE = 500; // max length of pick-ray - -// -// near grabbing -// - -var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected -var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected -var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things -var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object -var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed - -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - -// -// other constants -// - -var RIGHT_HAND = 1; -var LEFT_HAND = 0; - -var ZERO_VEC = { - x: 0, - y: 0, - z: 0 -}; - -var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; -var MSEC_PER_SEC = 1000.0; - -// these control how long an abandoned pointer line or action will hang around -var LIFETIME = 10; -var ACTION_TTL = 15; // seconds -var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 5; -var MSECS_PER_SEC = 1000.0; -var GRABBABLE_PROPERTIES = [ - "position", - "rotation", - "gravity", - "ignoreForCollisions", - "collisionsWillMove", - "locked", - "name" -]; - -var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js -var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js - -var DEFAULT_GRABBABLE_DATA = { - grabbable: true, - invertSolidWhileHeld: false -}; - -//we've created various ways of visualizing looking for and moving distant objects -var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_PARTICLE_BEAM_FOR_SEARCHING = false; - -var USE_ENTITY_LINES_FOR_MOVING = true; -var USE_OVERLAY_LINES_FOR_MOVING = false; -var USE_PARTICLE_BEAM_FOR_MOVING = false; - -var USE_SPOTLIGHT = false; -var USE_POINTLIGHT = true; - -// states for the state machine -var STATE_OFF = 0; -var STATE_SEARCHING = 1; -var STATE_DISTANCE_HOLDING = 2; -var STATE_CONTINUE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; -var STATE_EQUIP_SEARCHING = 11; -var STATE_EQUIP = 12 -var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down -var STATE_CONTINUE_EQUIP = 14; -var STATE_WAITING_FOR_BUMPER_RELEASE = 15; -var STATE_EQUIP_SPRING = 16; - - - -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP_SEARCHING: - return "equip_searching"; - case STATE_EQUIP: - return "equip"; - case STATE_CONTINUE_EQUIP_BD: - return "continue_equip_bd"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_BUMPER_RELEASE: - return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; - } - - return "unknown"; -} - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; - if (tag == getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 5) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -} - -function getSpatialOffsetPosition(hand, spatialKey) { - var position = Vec3.ZERO; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { - position = spatialKey.leftRelativePosition; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { - position = spatialKey.rightRelativePosition; - } - if (spatialKey.relativePosition) { - position = spatialKey.relativePosition; - } - - return position; -} - -var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); - -function getSpatialOffsetRotation(hand, spatialKey) { - var rotation = Quat.IDENTITY; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { - rotation = spatialKey.leftRelativeRotation; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { - rotation = spatialKey.rightRelativeRotation; - } - if (spatialKey.relativeRotation) { - rotation = spatialKey.relativeRotation; - } - - // Flip left hand - if (hand !== RIGHT_HAND) { - rotation = Quat.multiply(yFlip, rotation); - } - - return rotation; -} - -function MyController(hand) { - this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - - var SPATIAL_CONTROLLERS_PER_PALM = 2; - var TIP_CONTROLLER_OFFSET = 1; - this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; - this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; - - this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. - this.state = STATE_OFF; - this.pointer = null; // entity-id of line object - this.triggerValue = 0; // rolling average of trigger value - this.rawTriggerValue = 0; - this.rawBumperValue = 0; - - //for visualizations - this.overlayLine = null; - this.particleBeam = null; - - //for lights - this.spotlight = null; - this.pointlight = null; - - this.ignoreIK = false; - this.offsetPosition = Vec3.ZERO; - this.offsetRotation = Quat.IDENTITY; - - var _this = this; - - this.update = function() { - - this.updateSmoothedTrigger(); - - switch (this.state) { - case STATE_OFF: - this.off(); - this.touchTest(); - break; - case STATE_SEARCHING: - this.search(); - break; - case STATE_EQUIP_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_BUMPER_RELEASE: - this.waitingForBumperRelease(); - break; - case STATE_EQUIP_SPRING: - this.pullTowardEquipPosition() - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_EQUIP_BD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; - } - }; - - this.setState = function(newState) { - if (WANT_DEBUG) { - print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); - } - this.state = newState; - }; - - this.debugLine = function(closePoint, farPoint, color) { - Entities.addEntity({ - type: "Line", - name: "Grab Debug Entity", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - }; - - this.lineOn = function(closePoint, farPoint, color) { - // draw a line - if (this.pointer === null) { - this.pointer = Entities.addEntity({ - type: "Line", - name: "grab pointer", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: LIFETIME, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } else { - var age = Entities.getEntityProperties(this.pointer, "age").age; - this.pointer = Entities.editEntity(this.pointer, { - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: age + LIFETIME - }); - } - }; - - this.overlayLineOn = function(closePoint, farPoint, color) { - if (this.overlayLine === null) { - var lineProperties = { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - visible: true, - alpha: 1 - }; - - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.overlayLine, { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - visible: true, - ignoreRayIntersection: true, // always ignore this - alpha: 1 - }); - } - }; - - this.handleParticleBeam = function(position, orientation, color) { - - var rotation = Quat.angleAxis(0, { - x: 1, - y: 0, - z: 0 - }); - - var finalRotation = Quat.multiply(orientation, rotation); - var lifespan = LINE_LENGTH / 10; - var speed = 5; - var spread = 2; - if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } - }; - - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { - - var handToObject = Vec3.subtract(objectPosition, handPosition); - var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); - - var distance = Vec3.distance(handPosition, objectPosition); - var speed = 5; - var spread = 0; - - var lifespan = distance / speed; - - - if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } - }; - - this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - - var particleBeamProperties = { - type: "ParticleEffect", - isEmitting: true, - position: position, - visible: false, - "name": "Particle Beam", - "color": color, - "maxParticles": 2000, - "lifespan": lifespan, - "emitRate": 50, - "emitSpeed": speed, - "speedSpread": spread, - "emitOrientation": { - "x": -1, - "y": 0, - "z": 0, - "w": 1 - }, - "emitDimensions": { - "x": 0, - "y": 0, - "z": 0 - }, - "emitRadiusStart": 0.5, - "polarStart": 0, - "polarFinish": 0, - "azimuthStart": -3.1415927410125732, - "azimuthFinish": 3.1415927410125732, - "emitAcceleration": { - x: 0, - y: 0, - z: 0 - }, - "accelerationSpread": { - "x": 0, - "y": 0, - "z": 0 - }, - "particleRadius": 0.01, - "radiusSpread": 0, - // "radiusStart": 0.01, - // "radiusFinish": 0.01, - // "colorSpread": { - // "red": 0, - // "green": 0, - // "blue": 0 - // }, - // "colorStart": color, - // "colorFinish": color, - "alpha": 1, - "alphaSpread": 0, - "alphaStart": 1, - "alphaFinish": 1, - "additiveBlending": 0, - "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" - } - - this.particleBeam = Entities.addEntity(particleBeamProperties); - }; - - this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - print('lifespan::' + lifespan); - Entities.editEntity(this.particleBeam, { - rotation: orientation, - position: position, - visible: true, - color: color, - emitSpeed: speed, - speedSpread:spread, - lifespan: lifespan - - }) - - }; - - this.evalLightWorldTransform = function(modelPos, modelRot) { - - var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 - }; - - var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - }); - - return { - p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) - }; - }; - - this.handleSpotlight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - var lightProperties = { - type: "Light", - isSpotlight: true, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.spotlight === null) { - this.spotlight = Entities.addEntity(lightProperties); - } else { - Entities.editEntity(this.spotlight, { - //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - }) - } - }; - - this.handlePointLight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - - var lightProperties = { - type: "Light", - isSpotlight: false, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.pointlight === null) { - this.pointlight = Entities.addEntity(lightProperties); - } else { - - } - }; - - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.particleBeamOff = function() { - if (this.particleBeam !== null) { - Entities.editEntity(this.particleBeam, { - visible: false - }) - } - } - - this.turnLightsOff = function() { - if (this.spotlight !== null) { - Entities.deleteEntity(this.spotlight); - this.spotlight = null; - } - - if (this.pointlight !== null) { - Entities.deleteEntity(this.pointlight); - this.pointlight = null; - } - }; - - - this.turnOffVisualizations = function() { - if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOff(); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOff(); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.particleBeamOff(); - } - }; - - this.triggerPress = function(value) { - _this.rawTriggerValue = value; - }; - - this.bumperPress = function(value) { - _this.rawBumperValue = value; - }; - - this.updateSmoothedTrigger = function() { - var triggerValue = this.rawTriggerValue; - // smooth out trigger value - this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + - (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); - }; - - this.triggerSmoothedSqueezed = function() { - return this.triggerValue > TRIGGER_ON_VALUE; - }; - - this.triggerSmoothedReleased = function() { - return this.triggerValue < TRIGGER_OFF_VALUE; - }; - - this.triggerSqueezed = function() { - var triggerValue = this.rawTriggerValue; - return triggerValue > TRIGGER_ON_VALUE; - }; - - this.bumperSqueezed = function() { - return _this.rawBumperValue > BUMPER_ON_VALUE; - }; - - this.bumperReleased = function() { - return _this.rawBumperValue < BUMPER_ON_VALUE; - }; - - this.off = function() { - if (this.triggerSmoothedSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_SEARCHING); - return; - } - if (this.bumperSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_EQUIP_SEARCHING); - return; - } - }; - - this.search = function() { - this.grabbedEntity = null; - - if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { - this.setState(STATE_RELEASE); - return; - } - - // the trigger is being pressed, do a ray test - var handPosition = this.getHandPosition(); - var distantPickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()), - length: PICK_MAX_DISTANCE - }; - - // don't pick 60x per second. - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - if (WANT_DEBUG) { - this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { - red: 0, - green: 255, - blue: 0 - }) - } - - var intersection = Entities.findRayIntersection(pickRayBacked, true); - - if (intersection.intersects) { - // the ray is intersecting something we can move. - var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); - - if (intersection.properties.name == "Grab Debug Entity") { - continue; - } - - if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { - continue; - } - if (intersectionDistance > pickRay.length) { - // too far away for this ray. - continue; - } - if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { - // the hand is very close to the intersected object. go into close-grabbing mode. - if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!intersection.properties.locked) { - this.grabbedEntity = intersection.entityID; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // equipping - if (typeof grabbableData.spatialKey !== 'undefined') { - // TODO - // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch - // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - } else { - this.setState(STATE_EQUIP); - } - } - return; - } - } else if (!entityIsGrabbedByOther(intersection.entityID)) { - // don't allow two people to distance grab the same object - if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { - // the hand is far from the intersected object. go into distance-holding mode - this.grabbedEntity = intersection.entityID; - if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { - // if a distance pick in equip mode hits something with a spatialKey, equip it - // TODO use STATE_EQUIP_SPRING here once it works right. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - return; - } else if (this.state == STATE_SEARCHING) { - this.setState(STATE_DISTANCE_HOLDING); - return; - } - } else if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_FAR_TRIGGER); - return; - } - } - } - } - - // forward ray test failed, try sphere test. - if (WANT_DEBUG) { - Entities.addEntity({ - type: "Sphere", - name: "Grab Debug Entity", - dimensions: { - x: GRAB_RADIUS, - y: GRAB_RADIUS, - z: GRAB_RADIUS - }, - visible: true, - position: handPosition, - color: { - red: 0, - green: 255, - blue: 0 - }, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } - - var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < nearbyEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); - if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { - continue; - } - var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); - - if (propsForCandidate.type == 'Unknown') { - continue; - } - - if (propsForCandidate.type == 'Light') { - continue; - } - - if (propsForCandidate.type == 'ParticleEffect') { - continue; - } - - if (propsForCandidate.type == 'PolyLine') { - continue; - } - - if (propsForCandidate.type == 'Zone') { - continue; - } - - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - continue; - } - - if (propsForCandidate.name == "Grab Debug Entity") { - continue; - } - - if (propsForCandidate.name == "grab pointer") { - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance < minDistance) { - this.grabbedEntity = nearbyEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if (this.grabbedEntity !== null) { - if (grabbableData.wantsTrigger) { - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!props.locked && props.collisionsWillMove) { - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) - return; - } - } - - //search line visualizations - if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); - } - - }; - - this.distanceHolding = function() { - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); - this.handPreviousRotation = handRotation; - this.currentCameraOrientation = Camera.orientation; - - // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object - this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - tag: getTag(), - ttl: ACTION_TTL - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - - if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); - } - - this.currentAvatarPosition = MyAvatar.position; - this.currentAvatarOrientation = MyAvatar.orientation; - - this.turnOffVisualizations(); - }; - - this.continueDistanceHolding = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - var handPosition = this.getHandPosition(); - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && - typeof grabbableData.spatialKey !== 'undefined') { - var saveGrabbedID = this.grabbedEntity; - this.release(); - this.setState(STATE_EQUIP); - this.grabbedEntity = saveGrabbedID; - return; - } - - - // the action was set up on a previous call. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - // how far did avatar move this timestep? - var currentPosition = MyAvatar.position; - var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); - this.currentAvatarPosition = currentPosition; - - // How far did the avatar turn this timestep? - // Note: The following code is too long because we need a Quat.quatBetween() function - // that returns the minimum quaternion between two quaternions. - var currentOrientation = MyAvatar.orientation; - if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { - var negativeCurrentOrientation = { - x: -currentOrientation.x, - y: -currentOrientation.y, - z: -currentOrientation.z, - w: -currentOrientation.w - }; - var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } else { - var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } - var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); - var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); - var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); - this.currentAvatarOrientation = currentOrientation; - - // how far did hand move this timestep? - var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); - this.handRelativePreviousPosition = handToAvatar; - - // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, handMovementFromTurning); - var superHandMoved = Vec3.multiply(handMoved, radius); - - // Move the object by the magnified amount and then by amount from avatar movement & rotation - var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); - newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); - newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); - - var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters - var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentObjectPosition = newObjectPosition; - this.currentObjectTime = now; - - // this doubles hand rotation - var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); - this.handPreviousRotation = handRotation; - this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); - - Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); - } - - - //visualizations - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - } - if (USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); - } - if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) - } - if (USE_POINTLIGHT === true) { - this.handlePointLight(this.grabbedEntity); - } - if (USE_SPOTLIGHT === true) { - this.handleSpotlight(this.grabbedEntity); - } - - Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - ttl: ACTION_TTL - }); - - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - }; - - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } - - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - } else { - this.ignoreIK = false; - - var objectRotation = grabbedProperties.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); - - this.setState(STATE_CONTINUE_EQUIP_BD); - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - - } - - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - - this.currentObjectTime = Date.now(); - }; - - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { - this.setState(STATE_CONTINUE_EQUIP); - return; - } - if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { - this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); - return; - } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { - this.setState(STATE_CONTINUE_EQUIP_BD); - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - return; - } - - // Keep track of the fingertip velocity to impart when we release the object. - // Note that the idea of using a constant 'tip' velocity regardless of the - // object's actual held offset is an idea intended to make it easier to throw things: - // Because we might catch something or transfer it between hands without a good idea - // of it's actual offset, let's try imparting a velocity which is at a fixed radius - // from the palm. - - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentHandControllerTipPosition = handControllerPosition; - this.currentObjectTime = now; - Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); - - if (this.state === STATE_CONTINUE_EQUIP_BD) { - Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); - } - - if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { - // if less than a 5 seconds left, refresh the actions ttl - Entities.updateAction(this.grabbedEntity, this.actionID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - } - }; - - this.waitingForBumperRelease = function() { - if (this.bumperReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - Entities.callEntityMethod(this.grabbedEntity, "unequip"); - this.endHandGrasp(); - - } - }; - - this.pullTowardEquipPosition = function() { - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - // use a spring to pull the object to where it will be when equipped - var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var targetRotation = Quat.multiply(handRotation, relativeRotation); - var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); - var targetPosition = Vec3.sum(handPosition, offset); - - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = null; - this.setState(STATE_OFF); - return; - } - } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - } - - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; - this.setState(STATE_EQUIP); - } - }; - - this.nearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); - }; - - this.farTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); - }; - - this.continueNearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); - }; - - this.continueFarTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - var handPosition = this.getHandPosition(); - var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - } - - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); - }; - - _this.allTouchedIDs = {}; - - this.touchTest = function() { - var maxDistance = 0.05; - var leftHandPosition = MyAvatar.getLeftPalmPosition(); - var rightHandPosition = MyAvatar.getRightPalmPosition(); - var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); - var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); - var ids = []; - - if (leftEntities.length !== 0) { - leftEntities.forEach(function(entity) { - ids.push(entity); - }); - - } - - if (rightEntities.length !== 0) { - rightEntities.forEach(function(entity) { - ids.push(entity); - }); - } - - ids.forEach(function(id) { - - var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); - if (props.name === 'pointer') { - return; - } else { - var entityMinPoint = props.boundingBox.brn; - var entityMaxPoint = props.boundingBox.tfl; - var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); - var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); - - if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { - // we haven't been touched before, but either right or left is touching us now - _this.allTouchedIDs[id] = true; - _this.startTouch(id); - } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { - // we have been touched before and are still being touched - // continue touch - _this.continueTouch(id); - } else if (_this.allTouchedIDs[id]) { - delete _this.allTouchedIDs[id]; - _this.stopTouch(id); - - } else { - //we are in another state - return; - } - } - - }); - - }; - - this.startTouch = function(entityID) { - Entities.callEntityMethod(entityID, "startTouch"); - }; - - this.continueTouch = function(entityID) { - Entities.callEntityMethod(entityID, "continueTouch"); - }; - - this.stopTouch = function(entityID) { - Entities.callEntityMethod(entityID, "stopTouch"); - }; - - this.release = function() { - - this.turnLightsOff(); - this.turnOffVisualizations(); - - if (this.grabbedEntity !== null) { - if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); - } - } - - this.deactivateEntity(this.grabbedEntity); - - this.grabbedEntity = null; - this.actionID = null; - this.setState(STATE_OFF); - }; - - this.cleanup = function() { - this.release(); - this.endHandGrasp(); - Entities.deleteEntity(this.particleBeam); - Entities.deleteEntity(this.spotLight); - Entities.deleteEntity(this.pointLight); - }; - - this.activateEntity = function(entityID, grabbedProperties) { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["activated"] = true; - data["avatarId"] = MyAvatar.sessionUUID; - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; - // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["gravity"] = grabbedProperties.gravity; - data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; - data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - } - }; - if (invertSolidWhileHeld) { - whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; - } - Entities.editEntity(entityID, whileHeldProperties); - } - - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - return data; - }; - - this.deactivateEntity = function(entityID) { - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { - Entities.editEntity(entityID, { - gravity: data["gravity"], - ignoreForCollisions: data["ignoreForCollisions"], - collisionsWillMove: data["collisionsWillMove"] - }); - data = null; - } - } else { - data = null; - } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - }; - - - //this is our handler, where we do the actual work of changing animation settings - this.graspHand = function(animationProperties) { - var result = {}; - //full alpha on overlay for this hand - //set grab to true - //set idle to false - //full alpha on the blend btw open and grab - if (_this.hand === RIGHT_HAND) { - result['rightHandOverlayAlpha'] = 1.0; - result['isRightHandGrab'] = true; - result['isRightHandIdle'] = false; - result['rightHandGrabBlend'] = 1.0; - } else if (_this.hand === LEFT_HAND) { - result['leftHandOverlayAlpha'] = 1.0; - result['isLeftHandGrab'] = true; - result['isLeftHandIdle'] = false; - result['leftHandGrabBlend'] = 1.0; - } - //return an object with our updated settings - return result; - }; - - this.graspHandler = null - - this.startHandGrasp = function() { - if (this.hand === RIGHT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); - } else if (this.hand === LEFT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); - } - }; - - this.endHandGrasp = function() { - // Tell the animation system we don't need any more callbacks. - MyAvatar.removeAnimationStateHandler(this.graspHandler); - }; - -}; - -var rightController = new MyController(RIGHT_HAND); -var leftController = new MyController(LEFT_HAND); - -//preload the particle beams so that they are full length when you start searching -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - rightController.createParticleBeam(); - leftController.createParticleBeam(); -} - -var MAPPING_NAME = "com.highfidelity.handControllerGrab"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); - -mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); - -Controller.enableMapping(MAPPING_NAME); - -//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items -var handToDisable = 'none'; - -function update() { - if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); - } - if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); - } -} - -Messages.subscribe('Hifi-Hand-Disabler'); - -handleHandDisablerMessages = function(channel, message, sender) { - - if (sender === MyAvatar.sessionUUID) { - if (message === 'left') { - handToDisable = LEFT_HAND; - } - if (message === 'right') { - handToDisable = RIGHT_HAND; - } - if (message === 'both') { - handToDisable = 'both'; - } - if (message === 'none') { - handToDisable = 'none'; - } - } - -} - -Messages.messageReceived.connect(handleHandDisablerMessages); - -function cleanup() { - rightController.cleanup(); - leftController.cleanup(); - Controller.disableMapping(MAPPING_NAME); -} - -Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab-spotlight.js b/examples/controllers/handControllerGrab-spotlight.js deleted file mode 100644 index ee6dcfa681..0000000000 --- a/examples/controllers/handControllerGrab-spotlight.js +++ /dev/null @@ -1,1656 +0,0 @@ -// handControllerGrab.js -// -// Created by Eric Levin on 9/2/15 -// Additions by James B. Pollack @imgntn on 9/24/2015 -// Additions By Seth Alves on 10/20/2015 -// Copyright 2015 High Fidelity, Inc. -// -// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. -// Also supports touch and equipping objects. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ - -Script.include("../libraries/utils.js"); - -// -// add lines where the hand ray picking is happening -// -var WANT_DEBUG = false; - -// -// these tune time-averaging and "on" value for analog trigger -// - -var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.4; -var TRIGGER_OFF_VALUE = 0.15; - -var BUMPER_ON_VALUE = 0.5; - -// -// distant manipulation -// - -var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object -var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects - -var NO_INTERSECT_COLOR = { - red: 10, - green: 10, - blue: 255 -}; // line color when pick misses -var INTERSECT_COLOR = { - red: 250, - green: 10, - blue: 10 -}; // line color when pick hits -var LINE_ENTITY_DIMENSIONS = { - x: 1000, - y: 1000, - z: 1000 -}; - -var LINE_LENGTH = 500; -var PICK_MAX_DISTANCE = 500; // max length of pick-ray - -// -// near grabbing -// - -var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected -var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected -var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things -var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object -var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed - -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - -// -// other constants -// - -var RIGHT_HAND = 1; -var LEFT_HAND = 0; - -var ZERO_VEC = { - x: 0, - y: 0, - z: 0 -}; - -var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; -var MSEC_PER_SEC = 1000.0; - -// these control how long an abandoned pointer line or action will hang around -var LIFETIME = 10; -var ACTION_TTL = 15; // seconds -var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 5; -var MSECS_PER_SEC = 1000.0; -var GRABBABLE_PROPERTIES = [ - "position", - "rotation", - "gravity", - "ignoreForCollisions", - "collisionsWillMove", - "locked", - "name" -]; - -var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js -var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js - -var DEFAULT_GRABBABLE_DATA = { - grabbable: true, - invertSolidWhileHeld: false -}; - -//we've created various ways of visualizing looking for and moving distant objects -var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_PARTICLE_BEAM_FOR_SEARCHING = false; - -var USE_ENTITY_LINES_FOR_MOVING = true; -var USE_OVERLAY_LINES_FOR_MOVING = false; -var USE_PARTICLE_BEAM_FOR_MOVING = false; - -var USE_SPOTLIGHT = true; -var USE_POINTLIGHT = false; - -// states for the state machine -var STATE_OFF = 0; -var STATE_SEARCHING = 1; -var STATE_DISTANCE_HOLDING = 2; -var STATE_CONTINUE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; -var STATE_EQUIP_SEARCHING = 11; -var STATE_EQUIP = 12 -var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down -var STATE_CONTINUE_EQUIP = 14; -var STATE_WAITING_FOR_BUMPER_RELEASE = 15; -var STATE_EQUIP_SPRING = 16; - - - -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP_SEARCHING: - return "equip_searching"; - case STATE_EQUIP: - return "equip"; - case STATE_CONTINUE_EQUIP_BD: - return "continue_equip_bd"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_BUMPER_RELEASE: - return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; - } - - return "unknown"; -} - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; - if (tag == getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 5) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -} - -function getSpatialOffsetPosition(hand, spatialKey) { - var position = Vec3.ZERO; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { - position = spatialKey.leftRelativePosition; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { - position = spatialKey.rightRelativePosition; - } - if (spatialKey.relativePosition) { - position = spatialKey.relativePosition; - } - - return position; -} - -var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); - -function getSpatialOffsetRotation(hand, spatialKey) { - var rotation = Quat.IDENTITY; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { - rotation = spatialKey.leftRelativeRotation; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { - rotation = spatialKey.rightRelativeRotation; - } - if (spatialKey.relativeRotation) { - rotation = spatialKey.relativeRotation; - } - - // Flip left hand - if (hand !== RIGHT_HAND) { - rotation = Quat.multiply(yFlip, rotation); - } - - return rotation; -} - -function MyController(hand) { - this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - - var SPATIAL_CONTROLLERS_PER_PALM = 2; - var TIP_CONTROLLER_OFFSET = 1; - this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; - this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; - - this.actionID = null; // action this script created... - this.grabbedEntity = null; // on this entity. - this.state = STATE_OFF; - this.pointer = null; // entity-id of line object - this.triggerValue = 0; // rolling average of trigger value - this.rawTriggerValue = 0; - this.rawBumperValue = 0; - - //for visualizations - this.overlayLine = null; - this.particleBeam = null; - - //for lights - this.spotlight = null; - this.pointlight = null; - - this.ignoreIK = false; - this.offsetPosition = Vec3.ZERO; - this.offsetRotation = Quat.IDENTITY; - - var _this = this; - - this.update = function() { - - this.updateSmoothedTrigger(); - - switch (this.state) { - case STATE_OFF: - this.off(); - this.touchTest(); - break; - case STATE_SEARCHING: - this.search(); - break; - case STATE_EQUIP_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_BUMPER_RELEASE: - this.waitingForBumperRelease(); - break; - case STATE_EQUIP_SPRING: - this.pullTowardEquipPosition() - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_EQUIP_BD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; - } - }; - - this.setState = function(newState) { - if (WANT_DEBUG) { - print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); - } - this.state = newState; - }; - - this.debugLine = function(closePoint, farPoint, color) { - Entities.addEntity({ - type: "Line", - name: "Grab Debug Entity", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - }; - - this.lineOn = function(closePoint, farPoint, color) { - // draw a line - if (this.pointer === null) { - this.pointer = Entities.addEntity({ - type: "Line", - name: "grab pointer", - dimensions: LINE_ENTITY_DIMENSIONS, - visible: true, - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: LIFETIME, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } else { - var age = Entities.getEntityProperties(this.pointer, "age").age; - this.pointer = Entities.editEntity(this.pointer, { - position: closePoint, - linePoints: [ZERO_VEC, farPoint], - color: color, - lifetime: age + LIFETIME - }); - } - }; - - this.overlayLineOn = function(closePoint, farPoint, color) { - if (this.overlayLine === null) { - var lineProperties = { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - visible: true, - alpha: 1 - }; - - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - var success = Overlays.editOverlay(this.overlayLine, { - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - visible: true, - ignoreRayIntersection: true, // always ignore this - alpha: 1 - }); - } - }; - - this.handleParticleBeam = function(position, orientation, color) { - - var rotation = Quat.angleAxis(0, { - x: 1, - y: 0, - z: 0 - }); - - var finalRotation = Quat.multiply(orientation, rotation); - var lifespan = LINE_LENGTH / 10; - var speed = 5; - var spread = 2; - if (this.particleBeam === null) { - this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } - }; - - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { - - var handToObject = Vec3.subtract(objectPosition, handPosition); - var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); - - var distance = Vec3.distance(handPosition, objectPosition); - var speed = 5; - var spread = 0; - - var lifespan = distance / speed; - - - if (this.particleBeam === null) { - this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } else { - this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan); - } - }; - - this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - - var particleBeamProperties = { - type: "ParticleEffect", - isEmitting: true, - position: position, - visible: false, - "name": "Particle Beam", - "color": color, - "maxParticles": 2000, - "lifespan": lifespan, - "emitRate": 50, - "emitSpeed": speed, - "speedSpread": spread, - "emitOrientation": { - "x": -1, - "y": 0, - "z": 0, - "w": 1 - }, - "emitDimensions": { - "x": 0, - "y": 0, - "z": 0 - }, - "emitRadiusStart": 0.5, - "polarStart": 0, - "polarFinish": 0, - "azimuthStart": -3.1415927410125732, - "azimuthFinish": 3.1415927410125732, - "emitAcceleration": { - x: 0, - y: 0, - z: 0 - }, - "accelerationSpread": { - "x": 0, - "y": 0, - "z": 0 - }, - "particleRadius": 0.01, - "radiusSpread": 0, - // "radiusStart": 0.01, - // "radiusFinish": 0.01, - // "colorSpread": { - // "red": 0, - // "green": 0, - // "blue": 0 - // }, - // "colorStart": color, - // "colorFinish": color, - "alpha": 1, - "alphaSpread": 0, - "alphaStart": 1, - "alphaFinish": 1, - "additiveBlending": 0, - "textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png" - } - - this.particleBeam = Entities.addEntity(particleBeamProperties); - }; - - this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - print('lifespan::' + lifespan); - Entities.editEntity(this.particleBeam, { - rotation: orientation, - position: position, - visible: true, - color: color, - emitSpeed: speed, - speedSpread:spread, - lifespan: lifespan - - }) - - }; - - this.evalLightWorldTransform = function(modelPos, modelRot) { - - var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 - }; - - var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - }); - - return { - p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) - }; - }; - - this.handleSpotlight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - var lightProperties = { - type: "Light", - isSpotlight: true, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.spotlight === null) { - this.spotlight = Entities.addEntity(lightProperties); - } else { - Entities.editEntity(this.spotlight, { - //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), - }) - } - }; - - this.handlePointLight = function(parentID, position) { - var LIFETIME = 100; - - var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); - var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation); - - var lightProperties = { - type: "Light", - isSpotlight: false, - dimensions: { - x: 2, - y: 2, - z: 20 - }, - parentID: parentID, - color: { - red: 255, - green: 255, - blue: 255 - }, - intensity: 2, - exponent: 0.3, - cutoff: 20, - lifetime: LIFETIME, - position: lightTransform.p, - }; - - if (this.pointlight === null) { - this.pointlight = Entities.addEntity(lightProperties); - } else { - - } - }; - - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.particleBeamOff = function() { - if (this.particleBeam !== null) { - Entities.editEntity(this.particleBeam, { - visible: false - }) - } - } - - this.turnLightsOff = function() { - if (this.spotlight !== null) { - Entities.deleteEntity(this.spotlight); - this.spotlight = null; - } - - if (this.pointlight !== null) { - Entities.deleteEntity(this.pointlight); - this.pointlight = null; - } - }; - - - this.turnOffVisualizations = function() { - if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOff(); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOff(); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.particleBeamOff(); - } - }; - - this.triggerPress = function(value) { - _this.rawTriggerValue = value; - }; - - this.bumperPress = function(value) { - _this.rawBumperValue = value; - }; - - this.updateSmoothedTrigger = function() { - var triggerValue = this.rawTriggerValue; - // smooth out trigger value - this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + - (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); - }; - - this.triggerSmoothedSqueezed = function() { - return this.triggerValue > TRIGGER_ON_VALUE; - }; - - this.triggerSmoothedReleased = function() { - return this.triggerValue < TRIGGER_OFF_VALUE; - }; - - this.triggerSqueezed = function() { - var triggerValue = this.rawTriggerValue; - return triggerValue > TRIGGER_ON_VALUE; - }; - - this.bumperSqueezed = function() { - return _this.rawBumperValue > BUMPER_ON_VALUE; - }; - - this.bumperReleased = function() { - return _this.rawBumperValue < BUMPER_ON_VALUE; - }; - - this.off = function() { - if (this.triggerSmoothedSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_SEARCHING); - return; - } - if (this.bumperSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_EQUIP_SEARCHING); - return; - } - }; - - this.search = function() { - this.grabbedEntity = null; - - if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { - this.setState(STATE_RELEASE); - return; - } - - // the trigger is being pressed, do a ray test - var handPosition = this.getHandPosition(); - var distantPickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()), - length: PICK_MAX_DISTANCE - }; - - // don't pick 60x per second. - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - if (WANT_DEBUG) { - this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { - red: 0, - green: 255, - blue: 0 - }) - } - - var intersection = Entities.findRayIntersection(pickRayBacked, true); - - if (intersection.intersects) { - // the ray is intersecting something we can move. - var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); - - if (intersection.properties.name == "Grab Debug Entity") { - continue; - } - - if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { - continue; - } - if (intersectionDistance > pickRay.length) { - // too far away for this ray. - continue; - } - if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { - // the hand is very close to the intersected object. go into close-grabbing mode. - if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!intersection.properties.locked) { - this.grabbedEntity = intersection.entityID; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // equipping - if (typeof grabbableData.spatialKey !== 'undefined') { - // TODO - // if we go to STATE_EQUIP_SPRING the item will be pulled to the hand and will then switch - // to STATE_EQUIP. This needs some debugging, so just jump straight to STATE_EQUIP here. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - } else { - this.setState(STATE_EQUIP); - } - } - return; - } - } else if (!entityIsGrabbedByOther(intersection.entityID)) { - // don't allow two people to distance grab the same object - if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { - // the hand is far from the intersected object. go into distance-holding mode - this.grabbedEntity = intersection.entityID; - if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { - // if a distance pick in equip mode hits something with a spatialKey, equip it - // TODO use STATE_EQUIP_SPRING here once it works right. - // this.setState(STATE_EQUIP_SPRING); - this.setState(STATE_EQUIP); - return; - } else if (this.state == STATE_SEARCHING) { - this.setState(STATE_DISTANCE_HOLDING); - return; - } - } else if (grabbableData.wantsTrigger) { - this.grabbedEntity = intersection.entityID; - this.setState(STATE_FAR_TRIGGER); - return; - } - } - } - } - - // forward ray test failed, try sphere test. - if (WANT_DEBUG) { - Entities.addEntity({ - type: "Sphere", - name: "Grab Debug Entity", - dimensions: { - x: GRAB_RADIUS, - y: GRAB_RADIUS, - z: GRAB_RADIUS - }, - visible: true, - position: handPosition, - color: { - red: 0, - green: 255, - blue: 0 - }, - lifetime: 0.1, - collisionsWillMove: false, - ignoreForCollisions: true, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - } - - var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < nearbyEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); - if (typeof grabbableDataForCandidate.grabbable !== 'undefined' && !grabbableDataForCandidate.grabbable) { - continue; - } - var propsForCandidate = Entities.getEntityProperties(nearbyEntities[i], GRABBABLE_PROPERTIES); - - if (propsForCandidate.type == 'Unknown') { - continue; - } - - if (propsForCandidate.type == 'Light') { - continue; - } - - if (propsForCandidate.type == 'ParticleEffect') { - continue; - } - - if (propsForCandidate.type == 'PolyLine') { - continue; - } - - if (propsForCandidate.type == 'Zone') { - continue; - } - - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - continue; - } - - if (propsForCandidate.name == "Grab Debug Entity") { - continue; - } - - if (propsForCandidate.name == "grab pointer") { - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance < minDistance) { - this.grabbedEntity = nearbyEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if (this.grabbedEntity !== null) { - if (grabbableData.wantsTrigger) { - this.setState(STATE_NEAR_TRIGGER); - return; - } else if (!props.locked && props.collisionsWillMove) { - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) - return; - } - } - - //search line visualizations - if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - } - - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { - this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); - } - - }; - - this.distanceHolding = function() { - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); - this.handPreviousRotation = handRotation; - this.currentCameraOrientation = Camera.orientation; - - // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object - this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - tag: getTag(), - ttl: ACTION_TTL - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - - if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); - } - - this.currentAvatarPosition = MyAvatar.position; - this.currentAvatarOrientation = MyAvatar.orientation; - - this.turnOffVisualizations(); - }; - - this.continueDistanceHolding = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - var handPosition = this.getHandPosition(); - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && - typeof grabbableData.spatialKey !== 'undefined') { - var saveGrabbedID = this.grabbedEntity; - this.release(); - this.setState(STATE_EQUIP); - this.grabbedEntity = saveGrabbedID; - return; - } - - - // the action was set up on a previous call. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - // how far did avatar move this timestep? - var currentPosition = MyAvatar.position; - var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); - this.currentAvatarPosition = currentPosition; - - // How far did the avatar turn this timestep? - // Note: The following code is too long because we need a Quat.quatBetween() function - // that returns the minimum quaternion between two quaternions. - var currentOrientation = MyAvatar.orientation; - if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { - var negativeCurrentOrientation = { - x: -currentOrientation.x, - y: -currentOrientation.y, - z: -currentOrientation.z, - w: -currentOrientation.w - }; - var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } else { - var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); - } - var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); - var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); - var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); - this.currentAvatarOrientation = currentOrientation; - - // how far did hand move this timestep? - var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); - this.handRelativePreviousPosition = handToAvatar; - - // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, handMovementFromTurning); - var superHandMoved = Vec3.multiply(handMoved, radius); - - // Move the object by the magnified amount and then by amount from avatar movement & rotation - var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); - newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); - newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); - - var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters - var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentObjectPosition = newObjectPosition; - this.currentObjectTime = now; - - // this doubles hand rotation - var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); - this.handPreviousRotation = handRotation; - this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); - - Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); - } - - - //visualizations - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); - } - if (USE_OVERLAY_LINES_FOR_MOVING === true) { - this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); - } - if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) - } - if (USE_POINTLIGHT === true) { - this.handlePointLight(this.grabbedEntity); - } - if (USE_SPOTLIGHT === true) { - this.handleSpotlight(this.grabbedEntity); - } - - Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, - ttl: ACTION_TTL - }); - - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - }; - - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } - - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - } else { - this.ignoreIK = false; - - var objectRotation = grabbedProperties.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); - - this.setState(STATE_CONTINUE_EQUIP_BD); - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - - } - - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - - this.currentObjectTime = Date.now(); - }; - - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } - if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { - this.setState(STATE_CONTINUE_EQUIP); - return; - } - if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { - this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); - return; - } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { - this.setState(STATE_CONTINUE_EQUIP_BD); - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - return; - } - - // Keep track of the fingertip velocity to impart when we release the object. - // Note that the idea of using a constant 'tip' velocity regardless of the - // object's actual held offset is an idea intended to make it easier to throw things: - // Because we might catch something or transfer it between hands without a good idea - // of it's actual offset, let's try imparting a velocity which is at a fixed radius - // from the palm. - - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds - - this.currentHandControllerTipPosition = handControllerPosition; - this.currentObjectTime = now; - Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); - - if (this.state === STATE_CONTINUE_EQUIP_BD) { - Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); - } - - if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { - // if less than a 5 seconds left, refresh the actions ttl - Entities.updateAction(this.grabbedEntity, this.actionID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - } - }; - - this.waitingForBumperRelease = function() { - if (this.bumperReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - Entities.callEntityMethod(this.grabbedEntity, "unequip"); - this.endHandGrasp(); - - } - }; - - this.pullTowardEquipPosition = function() { - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - // use a spring to pull the object to where it will be when equipped - var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var targetRotation = Quat.multiply(handRotation, relativeRotation); - var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); - var targetPosition = Vec3.sum(handPosition, offset); - - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = null; - this.setState(STATE_OFF); - return; - } - } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - } - - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; - this.setState(STATE_EQUIP); - } - }; - - this.nearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); - }; - - this.farTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); - }; - - this.continueNearTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); - }; - - this.continueFarTrigger = function() { - if (this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); - return; - } - - var handPosition = this.getHandPosition(); - var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - this.lastPickTime = now; - if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); - return; - } - } - - if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); - } - - Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); - }; - - _this.allTouchedIDs = {}; - - this.touchTest = function() { - var maxDistance = 0.05; - var leftHandPosition = MyAvatar.getLeftPalmPosition(); - var rightHandPosition = MyAvatar.getRightPalmPosition(); - var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); - var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); - var ids = []; - - if (leftEntities.length !== 0) { - leftEntities.forEach(function(entity) { - ids.push(entity); - }); - - } - - if (rightEntities.length !== 0) { - rightEntities.forEach(function(entity) { - ids.push(entity); - }); - } - - ids.forEach(function(id) { - - var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); - if (props.name === 'pointer') { - return; - } else { - var entityMinPoint = props.boundingBox.brn; - var entityMaxPoint = props.boundingBox.tfl; - var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); - var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); - - if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { - // we haven't been touched before, but either right or left is touching us now - _this.allTouchedIDs[id] = true; - _this.startTouch(id); - } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { - // we have been touched before and are still being touched - // continue touch - _this.continueTouch(id); - } else if (_this.allTouchedIDs[id]) { - delete _this.allTouchedIDs[id]; - _this.stopTouch(id); - - } else { - //we are in another state - return; - } - } - - }); - - }; - - this.startTouch = function(entityID) { - Entities.callEntityMethod(entityID, "startTouch"); - }; - - this.continueTouch = function(entityID) { - Entities.callEntityMethod(entityID, "continueTouch"); - }; - - this.stopTouch = function(entityID) { - Entities.callEntityMethod(entityID, "stopTouch"); - }; - - this.release = function() { - - this.turnLightsOff(); - this.turnOffVisualizations(); - - if (this.grabbedEntity !== null) { - if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); - } - } - - this.deactivateEntity(this.grabbedEntity); - - this.grabbedEntity = null; - this.actionID = null; - this.setState(STATE_OFF); - }; - - this.cleanup = function() { - this.release(); - this.endHandGrasp(); - Entities.deleteEntity(this.particleBeam); - Entities.deleteEntity(this.spotLight); - Entities.deleteEntity(this.pointLight); - }; - - this.activateEntity = function(entityID, grabbedProperties) { - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var invertSolidWhileHeld = grabbableData["invertSolidWhileHeld"]; - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["activated"] = true; - data["avatarId"] = MyAvatar.sessionUUID; - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; - // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["gravity"] = grabbedProperties.gravity; - data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; - data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - } - }; - if (invertSolidWhileHeld) { - whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; - } - Entities.editEntity(entityID, whileHeldProperties); - } - - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - return data; - }; - - this.deactivateEntity = function(entityID) { - var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { - Entities.editEntity(entityID, { - gravity: data["gravity"], - ignoreForCollisions: data["ignoreForCollisions"], - collisionsWillMove: data["collisionsWillMove"] - }); - data = null; - } - } else { - data = null; - } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); - }; - - - //this is our handler, where we do the actual work of changing animation settings - this.graspHand = function(animationProperties) { - var result = {}; - //full alpha on overlay for this hand - //set grab to true - //set idle to false - //full alpha on the blend btw open and grab - if (_this.hand === RIGHT_HAND) { - result['rightHandOverlayAlpha'] = 1.0; - result['isRightHandGrab'] = true; - result['isRightHandIdle'] = false; - result['rightHandGrabBlend'] = 1.0; - } else if (_this.hand === LEFT_HAND) { - result['leftHandOverlayAlpha'] = 1.0; - result['isLeftHandGrab'] = true; - result['isLeftHandIdle'] = false; - result['leftHandGrabBlend'] = 1.0; - } - //return an object with our updated settings - return result; - }; - - this.graspHandler = null - - this.startHandGrasp = function() { - if (this.hand === RIGHT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); - } else if (this.hand === LEFT_HAND) { - this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); - } - }; - - this.endHandGrasp = function() { - // Tell the animation system we don't need any more callbacks. - MyAvatar.removeAnimationStateHandler(this.graspHandler); - }; - -}; - -var rightController = new MyController(RIGHT_HAND); -var leftController = new MyController(LEFT_HAND); - -//preload the particle beams so that they are full length when you start searching -if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { - rightController.createParticleBeam(); - leftController.createParticleBeam(); -} - -var MAPPING_NAME = "com.highfidelity.handControllerGrab"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); - -mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress); - -Controller.enableMapping(MAPPING_NAME); - -//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items -var handToDisable = 'none'; - -function update() { - if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); - } - if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); - } -} - -Messages.subscribe('Hifi-Hand-Disabler'); - -handleHandDisablerMessages = function(channel, message, sender) { - - if (sender === MyAvatar.sessionUUID) { - if (message === 'left') { - handToDisable = LEFT_HAND; - } - if (message === 'right') { - handToDisable = RIGHT_HAND; - } - if (message === 'both') { - handToDisable = 'both'; - } - if (message === 'none') { - handToDisable = 'none'; - } - } - -} - -Messages.messageReceived.connect(handleHandDisablerMessages); - -function cleanup() { - rightController.cleanup(); - leftController.cleanup(); - Controller.disableMapping(MAPPING_NAME); -} - -Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index b6f1a82c3f..bfe51927d0 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -118,12 +118,12 @@ var DEFAULT_GRABBABLE_DATA = { //we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = true; -var USE_PARTICLE_BEAM_FOR_SEARCHING = false; +var USE_OVERLAY_LINES_FOR_SEARCHING = false; +var USE_PARTICLE_BEAM_FOR_SEARCHING = true; -var USE_ENTITY_LINES_FOR_MOVING = true; +var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; -var USE_PARTICLE_BEAM_FOR_MOVING = false; +var USE_PARTICLE_BEAM_FOR_MOVING = true; var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; From 60f2314469db50a6ca06c9f1b3a163d5dbf5d28a Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 22 Dec 2015 14:38:05 -0800 Subject: [PATCH 21/24] remove debugging print --- examples/controllers/handControllerGrab.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index bfe51927d0..61e7e1d16a 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -529,7 +529,6 @@ function MyController(hand) { }; this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) { - print('lifespan::' + lifespan); Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, @@ -538,7 +537,6 @@ function MyController(hand) { emitSpeed: speed, speedSpread:spread, lifespan: lifespan - }) }; From edd11a670a8d48688a4b545d90793a3ee5bc2584 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 22 Dec 2015 14:38:53 -0800 Subject: [PATCH 22/24] minor adjustments to particles --- examples/controllers/handControllerGrab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 61e7e1d16a..8f291b509a 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -506,8 +506,8 @@ function MyController(hand) { "y": 0, "z": 0 }, - "particleRadius": 0.01, - "radiusSpread": 0, + "particleRadius": 0.015, + "radiusSpread": 0.005, // "radiusStart": 0.01, // "radiusFinish": 0.01, // "colorSpread": { From df78f895a67f810357766082ff103c912c39110a Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 22 Dec 2015 14:53:18 -0800 Subject: [PATCH 23/24] Reorder RenderContext initializer to avoid -Wreorder --- libraries/render/src/render/Engine.cpp | 7 ++++--- libraries/render/src/render/Engine.h | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index c38ceda034..907c836347 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -14,9 +14,10 @@ using namespace render; RenderContext::RenderContext(ItemsConfig items, Tone tone, int drawStatus, bool drawHitEffect, glm::vec4 deferredDebugSize, int deferredDebugMode) - : _args{ nullptr }, _items{ items }, _tone{ tone }, - _drawStatus{ drawStatus }, _drawHitEffect{ drawHitEffect }, - _deferredDebugSize{ deferredDebugSize }, _deferredDebugMode{ deferredDebugMode } {}; + : _deferredDebugMode{ deferredDebugMode }, _deferredDebugSize{ deferredDebugSize }, + _args{ nullptr }, + _drawStatus{ drawStatus }, _drawHitEffect{ drawHitEffect }, + _items{ items }, _tone{ tone } {} void RenderContext::setOptions(bool occlusion, bool fxaa, bool showOwned) { _occlusionStatus = occlusion; diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 02f3bf3b2c..4192dd3ed9 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -91,15 +91,15 @@ public: void setOptions(bool occlusion, bool fxaa, bool showOwned); // Debugging - int _deferredDebugMode = -1; - glm::vec4 _deferredDebugSize { 0.0f, -1.0f, 1.0f, 1.0f }; + int _deferredDebugMode; + glm::vec4 _deferredDebugSize; protected: RenderArgs* _args; // Options - int _drawStatus = 0; // bitflag - bool _drawHitEffect = false; + int _drawStatus; // bitflag + bool _drawHitEffect; bool _occlusionStatus = false; bool _fxaaStatus = false; From 4115138b5b9920d615420dd81cce9ee86eee82f4 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 22 Dec 2015 17:22:55 -0800 Subject: [PATCH 24/24] fix merge problems --- examples/controllers/handControllerGrab.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 04b52ebb50..9e7623b483 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -274,8 +274,6 @@ function MyController(hand) { this.triggerValue = 0; // rolling average of trigger value this.rawTriggerValue = 0; this.rawBumperValue = 0; - -<<<<<<< HEAD //for visualizations this.overlayLine = null; this.particleBeam = null; @@ -283,9 +281,7 @@ function MyController(hand) { //for lights this.spotlight = null; this.pointlight = null; -======= this.overlayLine = null; ->>>>>>> master this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; @@ -510,7 +506,7 @@ function MyController(hand) { "y": 0, "z": 0 }, - "particleRadius": 0.015, + "particleRadius": 0.015, "radiusSpread": 0.005, // "radiusStart": 0.01, // "radiusFinish": 0.01, @@ -539,7 +535,7 @@ function MyController(hand) { visible: true, color: color, emitSpeed: speed, - speedSpread:spread, + speedSpread: spread, lifespan: lifespan }) @@ -1085,7 +1081,6 @@ function MyController(hand) { this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - // mix in head motion if (MOVE_WITH_HEAD) { var objDistance = Vec3.length(objectToAvatar); @@ -1113,8 +1108,8 @@ function MyController(hand) { this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR); } if (USE_PARTICLE_BEAM_FOR_MOVING === true) { - this.handleDistantParticleBeam(handPosition,grabbedProperties.position, INTERSECT_COLOR) - // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) + this.handleDistantParticleBeam(handPosition, grabbedProperties.position, INTERSECT_COLOR) + // this.handleDistantParticleBeam(handPosition, this.currentObjectPosition, INTERSECT_COLOR) } if (USE_POINTLIGHT === true) { this.handlePointLight(this.grabbedEntity);