mirror of
https://github.com/overte-org/overte.git
synced 2025-05-04 08:39:23 +02:00
865 lines
30 KiB
JavaScript
865 lines
30 KiB
JavaScript
// shopItemGrab.js
|
|
//
|
|
// Simplified and coarse version of handControllerGrab.js with the addition of the ownerID concept.
|
|
// This grab is the only one which should run in the vrShop. It allows only near grab and add the feature of checking the ownerID. (See shopGrapSwapperEntityScript.js)
|
|
//
|
|
|
|
Script.include("../../libraries/utils.js");
|
|
|
|
var SHOP_GRAB_CHANNEL = "Hifi-vrShop-Grab";
|
|
Messages.sendMessage('Hifi-Hand-Disabler', "both"); //disable both the hands from handControlledGrab
|
|
|
|
//
|
|
// 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;
|
|
//
|
|
// distant manipulation
|
|
//
|
|
|
|
var DISTANCE_HOLDING_RADIUS_FACTOR = 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 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_TRACTOR_SHUTOFF_DISTANCE = 0.05;
|
|
var EQUIP_TRACTOR_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
|
|
};
|
|
|
|
|
|
// states for the state machine
|
|
var STATE_OFF = 0;
|
|
var STATE_SEARCHING = 1;
|
|
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_EQUIP_TRACTOR = 16;
|
|
|
|
|
|
function stateToName(state) {
|
|
switch (state) {
|
|
case STATE_OFF:
|
|
return "off";
|
|
case STATE_SEARCHING:
|
|
return "searching";
|
|
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_EQUIP_TRACTOR:
|
|
return "state_equip_tractor";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
|
|
|
|
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.offsetPosition = {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0
|
|
};
|
|
this.offsetRotation = {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
w: 1.0
|
|
};
|
|
|
|
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_NEAR_GRABBING:
|
|
case STATE_EQUIP:
|
|
this.nearGrabbing();
|
|
break;
|
|
case STATE_EQUIP_TRACTOR:
|
|
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: false,
|
|
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.lineOff = function() {
|
|
if (this.pointer !== null) {
|
|
Entities.deleteEntity(this.pointer);
|
|
}
|
|
this.pointer = null;
|
|
};
|
|
|
|
this.triggerPress = function(value) {
|
|
_this.rawTriggerValue = 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.off = function() {
|
|
if (this.triggerSmoothedSqueezed()) {
|
|
this.lastPickTime = 0;
|
|
this.setState(STATE_SEARCHING);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.search = function() {
|
|
this.grabbedEntity = null;
|
|
|
|
if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) {
|
|
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
|
|
};
|
|
|
|
|
|
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);
|
|
|
|
var properties = Entites.getEntityProperties(intersection.entityID, ["locked", "name"]);
|
|
|
|
|
|
if (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 (!properties.locked) {
|
|
var ownerObj = getEntityCustomData('ownerKey', intersection.entityID, null);
|
|
|
|
if (ownerObj == null || ownerObj.ownerID === MyAvatar.sessionUUID) { //I can only grab new or already mine items
|
|
this.grabbedEntity = intersection.entityID;
|
|
if (this.state == STATE_SEARCHING) {
|
|
this.setState(STATE_NEAR_GRABBING);
|
|
} else { // equipping
|
|
if (typeof grabbableData.spatialKey !== 'undefined') {
|
|
this.setState(STATE_EQUIP_TRACTOR);
|
|
} else {
|
|
this.setState(STATE_EQUIP);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
|
};
|
|
|
|
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.lineOff();
|
|
|
|
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
|
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.
|
|
if (grabbableData.spatialKey.relativePosition) {
|
|
this.offsetPosition = grabbableData.spatialKey.relativePosition;
|
|
}
|
|
if (grabbableData.spatialKey.relativeRotation) {
|
|
this.offsetRotation = grabbableData.spatialKey.relativeRotation;
|
|
}
|
|
} else {
|
|
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
|
|
});
|
|
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;
|
|
}
|
|
|
|
// 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
|
|
});
|
|
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
|
|
}
|
|
};
|
|
|
|
|
|
this.pullTowardEquipPosition = function() {
|
|
this.lineOff();
|
|
|
|
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
|
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
|
|
|
// use a tractor to pull the object to where it will be when equipped
|
|
var relativeRotation = {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
w: 1.0
|
|
};
|
|
var relativePosition = {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0
|
|
};
|
|
if (grabbableData.spatialKey.relativePosition) {
|
|
relativePosition = grabbableData.spatialKey.relativePosition;
|
|
}
|
|
if (grabbableData.spatialKey.relativeRotation) {
|
|
relativeRotation = grabbableData.spatialKey.relativeRotation;
|
|
}
|
|
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.equipTractorID === 'undefined' ||
|
|
this.equipTractorID === null ||
|
|
this.equipTractorID === NULL_ACTION_ID) {
|
|
this.equipTractorID = Entities.addAction("tractor", this.grabbedEntity, {
|
|
targetPosition: targetPosition,
|
|
linearTimeScale: EQUIP_TRACTOR_TIMEFRAME,
|
|
targetRotation: targetRotation,
|
|
angularTimeScale: EQUIP_TRACTOR_TIMEFRAME,
|
|
ttl: ACTION_TTL
|
|
});
|
|
if (this.equipTractorID === NULL_ACTION_ID) {
|
|
this.equipTractorID = null;
|
|
this.setState(STATE_OFF);
|
|
return;
|
|
}
|
|
} else {
|
|
Entities.updateAction(this.grabbedEntity, this.equipTractorID, {
|
|
targetPosition: targetPosition,
|
|
linearTimeScale: EQUIP_TRACTOR_TIMEFRAME,
|
|
targetRotation: targetRotation,
|
|
angularTimeScale: EQUIP_TRACTOR_TIMEFRAME,
|
|
ttl: ACTION_TTL
|
|
});
|
|
}
|
|
|
|
if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_TRACTOR_SHUTOFF_DISTANCE) {
|
|
Entities.deleteAction(this.grabbedEntity, this.equipTractorID);
|
|
this.equipTractorID = 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.lineOff();
|
|
|
|
if (this.grabbedEntity !== null) {
|
|
if (this.actionID !== null) {
|
|
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
|
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
|
print("CALLING releaseGrab");
|
|
}
|
|
}
|
|
|
|
|
|
this.grabbedEntity = null;
|
|
this.actionID = null;
|
|
this.setState(STATE_OFF);
|
|
|
|
};
|
|
|
|
this.cleanup = function() {
|
|
this.release();
|
|
this.endHandGrasp();
|
|
};
|
|
|
|
|
|
|
|
|
|
//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);
|
|
|
|
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);
|
|
|
|
Controller.enableMapping(MAPPING_NAME);
|
|
|
|
var handToDisable = 'none';
|
|
|
|
function update() {
|
|
if (handToDisable !== LEFT_HAND) {
|
|
leftController.update();
|
|
}
|
|
if (handToDisable !== RIGHT_HAND) {
|
|
rightController.update();
|
|
}
|
|
}
|
|
|
|
Messages.subscribe(SHOP_GRAB_CHANNEL);
|
|
|
|
stopScriptMessage = function(channel, message, sender) {
|
|
if (channel == SHOP_GRAB_CHANNEL && sender === MyAvatar.sessionUUID) {
|
|
//stop this script and enable the handControllerGrab
|
|
Messages.sendMessage('Hifi-Hand-Disabler', "none");
|
|
Messages.unsubscribe(SHOP_GRAB_CHANNEL);
|
|
Messages.messageReceived.disconnect(stopScriptMessage);
|
|
Script.stop();
|
|
}
|
|
}
|
|
|
|
Messages.messageReceived.connect(stopScriptMessage);
|
|
|
|
function cleanup() {
|
|
rightController.cleanup();
|
|
leftController.cleanup();
|
|
Controller.disableMapping(MAPPING_NAME);
|
|
}
|
|
|
|
Script.scriptEnding.connect(cleanup);
|
|
Script.update.connect(update);
|