mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-05 05:50:30 +02:00
trying to get equipping working
This commit is contained in:
parent
7e5999913f
commit
b52a406ff1
5 changed files with 704 additions and 41 deletions
|
@ -90,23 +90,28 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|||
this.leftTriggerClicked = 0;
|
||||
this.rightTriggerValue = 0;
|
||||
this.rightTriggerClicked = 0;
|
||||
|
||||
this.leftSecondaryValue = 0;
|
||||
this.rightSecondaryValue = 0;
|
||||
|
||||
this.leftTriggerPress = function (value) {
|
||||
_this.leftTriggerValue = value;
|
||||
};
|
||||
|
||||
this.leftTriggerClick = function (value) {
|
||||
_this.leftTriggerClicked = value;
|
||||
};
|
||||
|
||||
this.rightTriggerPress = function (value) {
|
||||
_this.rightTriggerValue = value;
|
||||
};
|
||||
|
||||
this.rightTriggerClick = function (value) {
|
||||
_this.rightTriggerClicked = value;
|
||||
};
|
||||
this.leftSecondaryPress = function (value) {
|
||||
_this.leftSecondaryValue = value;
|
||||
};
|
||||
this.rightSecondaryPress = function (value) {
|
||||
_this.rightSecondaryValue = value;
|
||||
};
|
||||
|
||||
|
||||
this.dataGatherers = {};
|
||||
this.dataGatherers.leftControllerLocation = function () {
|
||||
|
@ -150,10 +155,21 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|||
}
|
||||
}
|
||||
this.orderedPluginNames.sort(function (a, b) {
|
||||
return controllerDispatcherPlugins[a].priority < controllerDispatcherPlugins[b].priority;
|
||||
return controllerDispatcherPlugins[a].parameters.priority -
|
||||
controllerDispatcherPlugins[b].parameters.priority;
|
||||
});
|
||||
|
||||
print("controllerDispatcher: new plugin order: " + JSON.stringify(this.orderedPluginNames));
|
||||
// print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames));
|
||||
var output = "controllerDispatcher -- new plugin order: ";
|
||||
for (var k = 0; k < this.orderedPluginNames.length; k++) {
|
||||
var dbgPluginName = this.orderedPluginNames[k];
|
||||
var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority;
|
||||
output += dbgPluginName + ":" + priority;
|
||||
if (k + 1 < this.orderedPluginNames.length) {
|
||||
output += ", ";
|
||||
}
|
||||
}
|
||||
print(output);
|
||||
|
||||
controllerDispatcherPluginsNeedSort = false;
|
||||
}
|
||||
|
@ -166,44 +182,31 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|||
var h;
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
// todo: check controllerLocations[h].valid
|
||||
var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MIN_RADIUS);
|
||||
var makeOverlaySorter = function (handIndex) {
|
||||
return function (a, b) {
|
||||
var aPosition = Overlays.getProperty(a, "position");
|
||||
var aDistance = Vec3.distance(aPosition, controllerLocations[handIndex]);
|
||||
var bPosition = Overlays.getProperty(b, "position");
|
||||
var bDistance = Vec3.distance(bPosition, controllerLocations[handIndex]);
|
||||
return aDistance - bDistance;
|
||||
};
|
||||
};
|
||||
nearbyOverlays.sort(makeOverlaySorter(h));
|
||||
var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS);
|
||||
nearbyOverlays.sort(function (a, b) {
|
||||
var aPosition = Overlays.getProperty(a, "position");
|
||||
var aDistance = Vec3.distance(aPosition, controllerLocations[h].position);
|
||||
var bPosition = Overlays.getProperty(b, "position");
|
||||
var bDistance = Vec3.distance(bPosition, controllerLocations[h].position);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
nearbyOverlayIDs.push(nearbyOverlays);
|
||||
}
|
||||
|
||||
// find entities near each hand
|
||||
var nearbyEntityProperties = [[], []];
|
||||
var nearbyEntityPropertiesByID = {};
|
||||
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
||||
// todo: check controllerLocations[h].valid
|
||||
var controllerPosition = controllerLocations[h].position;
|
||||
var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS);
|
||||
var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MAX_RADIUS);
|
||||
for (var j = 0; j < nearbyEntityIDs.length; j++) {
|
||||
var entityID = nearbyEntityIDs[j];
|
||||
var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||
props.id = entityID;
|
||||
props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position));
|
||||
if (props.distanceFromController < NEAR_MAX_RADIUS) {
|
||||
nearbyEntityProperties[h].push(props);
|
||||
}
|
||||
nearbyEntityPropertiesByID[entityID] = props;
|
||||
nearbyEntityProperties[h].push(props);
|
||||
}
|
||||
// sort by distance from each hand
|
||||
var makeSorter = function (handIndex) {
|
||||
return function (a, b) {
|
||||
var aDistance = Vec3.distance(a.position, controllerLocations[handIndex]);
|
||||
var bDistance = Vec3.distance(b.position, controllerLocations[handIndex]);
|
||||
return aDistance - bDistance;
|
||||
};
|
||||
};
|
||||
nearbyEntityProperties[h].sort(makeSorter(h));
|
||||
}
|
||||
|
||||
// raypick for each controller
|
||||
|
@ -221,25 +224,33 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|||
length: 1000
|
||||
};
|
||||
|
||||
var nearEntityID = rayPicks[h].entityID;
|
||||
if (nearEntityID) {
|
||||
if (rayPicks[h].type == RayPick.INTERSECTED_ENTITY) {
|
||||
// XXX check to make sure this one isn't already in nearbyEntityProperties?
|
||||
if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) {
|
||||
var nearEntityID = rayPicks[h].objectID;
|
||||
var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES);
|
||||
nearbyProps.id = nearEntityID;
|
||||
if (entityIsGrabbable(nearbyProps)) {
|
||||
nearbyEntityProperties[h].push(nearbyProps);
|
||||
}
|
||||
nearbyEntityPropertiesByID[nearEntityID] = nearbyProps;
|
||||
nearbyEntityProperties[h].push(nearbyProps);
|
||||
}
|
||||
}
|
||||
|
||||
// sort by distance from each hand
|
||||
nearbyEntityProperties[h].sort(function (a, b) {
|
||||
var aDistance = Vec3.distance(a.position, controllerLocations[h].position);
|
||||
var bDistance = Vec3.distance(b.position, controllerLocations[h].position);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
}
|
||||
|
||||
// bundle up all the data about the current situation
|
||||
var controllerData = {
|
||||
triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue],
|
||||
triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked],
|
||||
secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue],
|
||||
controllerLocations: controllerLocations,
|
||||
nearbyEntityProperties: nearbyEntityProperties,
|
||||
nearbyEntityPropertiesByID: nearbyEntityPropertiesByID,
|
||||
nearbyOverlayIDs: nearbyOverlayIDs,
|
||||
rayPicks: rayPicks
|
||||
};
|
||||
|
@ -288,6 +299,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|||
mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick);
|
||||
mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress);
|
||||
mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick);
|
||||
|
||||
mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress);
|
||||
mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress);
|
||||
mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress);
|
||||
|
||||
Controller.enableMapping(MAPPING_NAME);
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
|
||||
COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
|
||||
COLORS_GRAB_DISTANCE_HOLD,
|
||||
Entities,
|
||||
makeDispatcherModuleParameters,
|
||||
makeRunningValues,
|
||||
enableDispatcherModule,
|
||||
|
@ -30,7 +31,8 @@
|
|||
controllerDispatcherPluginsNeedSort,
|
||||
projectOntoXYPlane,
|
||||
projectOntoEntityXYPlane,
|
||||
projectOntoOverlayXYPlane
|
||||
projectOntoOverlayXYPlane,
|
||||
entityHasActions
|
||||
*/
|
||||
|
||||
MSECS_PER_SEC = 1000.0;
|
||||
|
@ -111,10 +113,14 @@ getGrabbableData = function (props) {
|
|||
var grabbableData = {};
|
||||
var userDataParsed = null;
|
||||
try {
|
||||
userDataParsed = JSON.parse(props.userData);
|
||||
if (!props.userDataParsed) {
|
||||
props.userDataParsed = JSON.parse(props.userData);
|
||||
}
|
||||
userDataParsed = props.userDataParsed;
|
||||
} catch (err) {
|
||||
userDataParsed = {};
|
||||
}
|
||||
if (userDataParsed && userDataParsed.grabbable) {
|
||||
if (userDataParsed.grabbable) {
|
||||
grabbableData = userDataParsed.grabbable;
|
||||
}
|
||||
if (!grabbableData.hasOwnProperty("grabbable")) {
|
||||
|
@ -230,3 +236,7 @@ projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldP
|
|||
|
||||
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
|
||||
};
|
||||
|
||||
entityHasActions = function (entityID) {
|
||||
return Entities.getActionIDs(entityID).length > 0;
|
||||
};
|
||||
|
|
634
scripts/system/controllers/controllerModules/equipEntity.js
Normal file
634
scripts/system/controllers/controllerModules/equipEntity.js
Normal file
|
@ -0,0 +1,634 @@
|
|||
"use strict";
|
||||
|
||||
// equipEntity.js
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
|
||||
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID,
|
||||
getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule,
|
||||
Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions,
|
||||
Vec3, Overlays, flatten, Xform, getControllerWorldLocation
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/Xform.js");
|
||||
Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
|
||||
var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx";
|
||||
var EQUIP_SPHERE_SCALE_FACTOR = 0.65;
|
||||
|
||||
|
||||
// Each overlayInfoSet describes a single equip hotspot.
|
||||
// It is an object with the following keys:
|
||||
// timestamp - last time this object was updated, used to delete stale hotspot overlays.
|
||||
// entityID - entity assosicated with this hotspot
|
||||
// localPosition - position relative to the entity
|
||||
// hotspot - hotspot object
|
||||
// overlays - array of overlay objects created by Overlay.addOverlay()
|
||||
// currentSize - current animated scale value
|
||||
// targetSize - the target of our scale animations
|
||||
// type - "sphere" or "model".
|
||||
function EquipHotspotBuddy() {
|
||||
// holds map from {string} hotspot.key to {object} overlayInfoSet.
|
||||
this.map = {};
|
||||
|
||||
// array of all hotspots that are highlighed.
|
||||
this.highlightedHotspots = [];
|
||||
}
|
||||
EquipHotspotBuddy.prototype.clear = function() {
|
||||
var keys = Object.keys(this.map);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.map[keys[i]];
|
||||
this.deleteOverlayInfoSet(overlayInfoSet);
|
||||
}
|
||||
this.map = {};
|
||||
this.highlightedHotspots = [];
|
||||
};
|
||||
EquipHotspotBuddy.prototype.highlightHotspot = function(hotspot) {
|
||||
this.highlightedHotspots.push(hotspot.key);
|
||||
};
|
||||
EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) {
|
||||
var overlayInfoSet = this.map[hotspot.key];
|
||||
if (!overlayInfoSet) {
|
||||
// create a new overlayInfoSet
|
||||
overlayInfoSet = {
|
||||
timestamp: timestamp,
|
||||
entityID: hotspot.entityID,
|
||||
localPosition: hotspot.localPosition,
|
||||
hotspot: hotspot,
|
||||
currentSize: 0,
|
||||
targetSize: 1,
|
||||
overlays: []
|
||||
};
|
||||
|
||||
var diameter = hotspot.radius * 2;
|
||||
|
||||
// override default sphere with a user specified model, if it exists.
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
|
||||
name: "hotspot overlay",
|
||||
url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL,
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 1
|
||||
},
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR,
|
||||
scale: hotspot.modelScale,
|
||||
ignoreRayIntersection: true
|
||||
}));
|
||||
overlayInfoSet.type = "model";
|
||||
print("QQQ adding hopspot: " + hotspot.key);
|
||||
this.map[hotspot.key] = overlayInfoSet;
|
||||
} else {
|
||||
print("QQQ updating hopspot: " + hotspot.key);
|
||||
overlayInfoSet.timestamp = timestamp;
|
||||
}
|
||||
};
|
||||
EquipHotspotBuddy.prototype.updateHotspots = function(hotspots, timestamp) {
|
||||
var _this = this;
|
||||
hotspots.forEach(function(hotspot) {
|
||||
_this.updateHotspot(hotspot, timestamp);
|
||||
});
|
||||
this.highlightedHotspots = [];
|
||||
};
|
||||
EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerData) {
|
||||
|
||||
var HIGHLIGHT_SIZE = 1.1;
|
||||
var NORMAL_SIZE = 1.0;
|
||||
|
||||
var keys = Object.keys(this.map);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.map[keys[i]];
|
||||
|
||||
// this overlayInfo is highlighted.
|
||||
if (this.highlightedHotspots.indexOf(keys[i]) != -1) {
|
||||
overlayInfoSet.targetSize = HIGHLIGHT_SIZE;
|
||||
} else {
|
||||
overlayInfoSet.targetSize = NORMAL_SIZE;
|
||||
}
|
||||
|
||||
// start to fade out this hotspot.
|
||||
if (overlayInfoSet.timestamp != timestamp) {
|
||||
print("QQQ fading " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp);
|
||||
overlayInfoSet.targetSize = 0;
|
||||
}
|
||||
|
||||
// animate the size.
|
||||
var SIZE_TIMESCALE = 0.1;
|
||||
var tau = deltaTime / SIZE_TIMESCALE;
|
||||
if (tau > 1.0) {
|
||||
tau = 1.0;
|
||||
}
|
||||
overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau;
|
||||
|
||||
if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) {
|
||||
print("QQQ deleting " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp);
|
||||
|
||||
// this is an old overlay, that has finished fading out, delete it!
|
||||
overlayInfoSet.overlays.forEach(Overlays.deleteOverlay);
|
||||
delete this.map[keys[i]];
|
||||
} else {
|
||||
// update overlay position, rotation to follow the object it's attached to.
|
||||
|
||||
print("QQQ grew " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp);
|
||||
|
||||
var props = controllerData.nearbyEntityPropertiesByID[overlayInfoSet.entityID];
|
||||
if (props) {
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
var position = entityXform.xformPoint(overlayInfoSet.localPosition);
|
||||
|
||||
var dimensions;
|
||||
if (overlayInfoSet.type == "sphere") {
|
||||
dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR;
|
||||
} else {
|
||||
dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize;
|
||||
}
|
||||
|
||||
overlayInfoSet.overlays.forEach(function(overlay) {
|
||||
Overlays.editOverlay(overlay, {
|
||||
position: position,
|
||||
rotation: props.rotation,
|
||||
dimensions: dimensions
|
||||
});
|
||||
});
|
||||
} else {
|
||||
print("QQQ but no props for " + overlayInfoSet.entityID);
|
||||
overlayInfoSet.overlays.forEach(Overlays.deleteOverlay);
|
||||
delete this.map[keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var debug = true;
|
||||
var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints";
|
||||
|
||||
var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping.
|
||||
|
||||
var HAPTIC_PULSE_STRENGTH = 1.0;
|
||||
var HAPTIC_PULSE_DURATION = 13.0;
|
||||
var HAPTIC_TEXTURE_STRENGTH = 0.1;
|
||||
var HAPTIC_TEXTURE_DURATION = 3.0;
|
||||
var HAPTIC_TEXTURE_DISTANCE = 0.002;
|
||||
var HAPTIC_DEQUIP_STRENGTH = 0.75;
|
||||
var HAPTIC_DEQUIP_DURATION = 50.0;
|
||||
|
||||
var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
|
||||
var TRIGGER_OFF_VALUE = 0.1;
|
||||
var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab
|
||||
var BUMPER_ON_VALUE = 0.5;
|
||||
|
||||
|
||||
function getWearableData(props) {
|
||||
var wearable = {};
|
||||
try {
|
||||
if (!props.userDataParsed) {
|
||||
props.userDataParsed = JSON.parse(props.userData);
|
||||
}
|
||||
wearable = props.userDataParsed.wearable ? props.userDataParsed.wearable : {};
|
||||
} catch (err) {
|
||||
}
|
||||
return wearable;
|
||||
}
|
||||
function getEquipHotspotsData(props) {
|
||||
var equipHotspots = [];
|
||||
try {
|
||||
if (!props.userDataParsed) {
|
||||
props.userDataParsed = JSON.parse(props.userData);
|
||||
}
|
||||
equipHotspots = props.userDataParsed.equipHotspots ? props.userDataParsed.equipHotspots : [];
|
||||
} catch (err) {
|
||||
}
|
||||
return equipHotspots;
|
||||
}
|
||||
|
||||
function getAttachPointSettings() {
|
||||
try {
|
||||
var str = Settings.getValue(ATTACH_POINT_SETTINGS);
|
||||
if (str === "false") {
|
||||
return {};
|
||||
} else {
|
||||
return JSON.parse(str);
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error parsing attachPointSettings: " + err);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function setAttachPointSettings(attachPointSettings) {
|
||||
var str = JSON.stringify(attachPointSettings);
|
||||
Settings.setValue(ATTACH_POINT_SETTINGS, str);
|
||||
}
|
||||
|
||||
function getAttachPointForHotspotFromSettings(hotspot, hand) {
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
var joints = attachPointSettings[hotspot.key];
|
||||
if (joints) {
|
||||
return joints[jointName];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) {
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
var joints = attachPointSettings[hotspot.key];
|
||||
if (!joints) {
|
||||
joints = {};
|
||||
attachPointSettings[hotspot.key] = joints;
|
||||
}
|
||||
joints[jointName] = [offsetPosition, offsetRotation];
|
||||
setAttachPointSettings(attachPointSettings);
|
||||
}
|
||||
|
||||
function EquipEntity(hand) {
|
||||
this.hand = hand;
|
||||
this.targetEntityID = null;
|
||||
this.prevHandIsUpsideDown = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
300,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
||||
var equipHotspotBuddy = new EquipHotspotBuddy();
|
||||
|
||||
// returns a list of all equip-hotspots assosiated with this entity.
|
||||
// @param {UUID} entityID
|
||||
// @returns {Object[]} array of objects with the following fields.
|
||||
// * key {string} a string that can be used to uniquely identify this hotspot
|
||||
// * entityID {UUID}
|
||||
// * localPosition {Vec3} position of the hotspot in object space.
|
||||
// * worldPosition {vec3} position of the hotspot in world space.
|
||||
// * radius {number} radius of equip hotspot
|
||||
// * joints {Object} keys are joint names values are arrays of two elements:
|
||||
// offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint.
|
||||
// * modelURL {string} url for model to use instead of default sphere.
|
||||
// * modelScale {Vec3} scale factor for model
|
||||
this.collectEquipHotspots = function(props) {
|
||||
var result = [];
|
||||
var entityID = props.id;
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
|
||||
var equipHotspotsProps = getEquipHotspotsData(props);
|
||||
if (equipHotspotsProps && equipHotspotsProps.length > 0) {
|
||||
var i, length = equipHotspotsProps.length;
|
||||
for (i = 0; i < length; i++) {
|
||||
var hotspot = equipHotspotsProps[i];
|
||||
if (hotspot.position && hotspot.radius && hotspot.joints) {
|
||||
result.push({
|
||||
key: entityID.toString() + i.toString(),
|
||||
entityID: entityID,
|
||||
localPosition: hotspot.position,
|
||||
worldPosition: entityXform.xformPoint(hotspot.position),
|
||||
radius: hotspot.radius,
|
||||
joints: hotspot.joints,
|
||||
modelURL: hotspot.modelURL,
|
||||
modelScale: hotspot.modelScale
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var wearableProps = getWearableData(props);
|
||||
if (wearableProps && wearableProps.joints) {
|
||||
result.push({
|
||||
key: entityID.toString() + "0",
|
||||
entityID: entityID,
|
||||
localPosition: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
worldPosition: entityXform.pos,
|
||||
radius: EQUIP_RADIUS,
|
||||
joints: wearableProps.joints,
|
||||
modelURL: null,
|
||||
modelScale: null
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
this.hotspotIsEquippable = function(hotspot, controllerData) {
|
||||
var props = controllerData.nearbyEntityPropertiesByID[hotspot.entityID];
|
||||
|
||||
var hasParent = true;
|
||||
if (props.parentID === NULL_UUID) {
|
||||
hasParent = false;
|
||||
}
|
||||
if (hasParent || entityHasActions(hotspot.entityID)) {
|
||||
if (debug) {
|
||||
print("equip is skipping '" + props.name + "': grabbed by someone else: " +
|
||||
hasParent + " : " + entityHasActions(hotspot.entityID));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
this.handToController = function() {
|
||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.triggerPress = function(value) {
|
||||
this.rawTriggerValue = value;
|
||||
};
|
||||
|
||||
this.triggerClick = function(value) {
|
||||
this.triggerClicked = value;
|
||||
};
|
||||
|
||||
this.secondaryPress = function(value) {
|
||||
this.rawSecondaryValue = value;
|
||||
};
|
||||
|
||||
this.updateSmoothedTrigger = function(controllerData) {
|
||||
var triggerValue = controllerData.triggerValues[this.hand];
|
||||
// smooth out trigger value
|
||||
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
||||
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
||||
};
|
||||
|
||||
this.triggerSmoothedGrab = function() {
|
||||
return this.triggerClicked;
|
||||
};
|
||||
|
||||
this.triggerSmoothedSqueezed = function() {
|
||||
return this.triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.triggerSmoothedReleased = function() {
|
||||
return this.triggerValue < TRIGGER_OFF_VALUE;
|
||||
};
|
||||
|
||||
this.secondarySqueezed = function() {
|
||||
return this.rawSecondaryValue > BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.secondaryReleased = function() {
|
||||
return this.rawSecondaryValue < BUMPER_ON_VALUE;
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
|
||||
var _this = this;
|
||||
var collectedHotspots = flatten(candidateEntityProps.map(function(props) {
|
||||
return _this.collectEquipHotspots(props);
|
||||
}));
|
||||
var controllerLocation = controllerData.controllerLocations[_this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
var equippableHotspots = collectedHotspots.filter(function(hotspot) {
|
||||
var hotspotDistance = Vec3.distance(hotspot.worldPosition, worldControllerPosition);
|
||||
return _this.hotspotIsEquippable(hotspot, controllerData) &&
|
||||
hotspotDistance < hotspot.radius;
|
||||
});
|
||||
return equippableHotspots;
|
||||
};
|
||||
|
||||
this.chooseBestEquipHotspot = function(candidateEntityProps, controllerData) {
|
||||
var equippableHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData);
|
||||
if (equippableHotspots.length > 0) {
|
||||
// sort by distance;
|
||||
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||
var worldControllerPosition = controllerLocation.position;
|
||||
equippableHotspots.sort(function(a, b) {
|
||||
var aDistance = Vec3.distance(a.worldPosition, worldControllerPosition);
|
||||
var bDistance = Vec3.distance(b.worldPosition, worldControllerPosition);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
return equippableHotspots[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
this.dropGestureReset = function() {
|
||||
this.prevHandIsUpsideDown = false;
|
||||
};
|
||||
|
||||
this.dropGestureProcess = function (deltaTime) {
|
||||
var worldHandRotation = getControllerWorldLocation(this.handToController(), true).orientation;
|
||||
var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, z: 0 } : { x: -1, y: 0, z: 0 };
|
||||
var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis);
|
||||
var DOWN = { x: 0, y: -1, z: 0 };
|
||||
|
||||
var DROP_ANGLE = Math.PI / 3;
|
||||
var HYSTERESIS_FACTOR = 1.1;
|
||||
var ROTATION_ENTER_THRESHOLD = Math.cos(DROP_ANGLE);
|
||||
var ROTATION_EXIT_THRESHOLD = Math.cos(DROP_ANGLE * HYSTERESIS_FACTOR);
|
||||
var rotationThreshold = this.prevHandIsUpsideDown ? ROTATION_EXIT_THRESHOLD : ROTATION_ENTER_THRESHOLD;
|
||||
|
||||
var handIsUpsideDown = false;
|
||||
if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) {
|
||||
handIsUpsideDown = true;
|
||||
}
|
||||
|
||||
if (handIsUpsideDown != this.prevHandIsUpsideDown) {
|
||||
this.prevHandIsUpsideDown = handIsUpsideDown;
|
||||
Controller.triggerHapticPulse(HAPTIC_DEQUIP_STRENGTH, HAPTIC_DEQUIP_DURATION, this.hand);
|
||||
}
|
||||
|
||||
return handIsUpsideDown;
|
||||
};
|
||||
|
||||
this.clearEquipHaptics = function() {
|
||||
this.prevPotentialEquipHotspot = null;
|
||||
};
|
||||
|
||||
this.updateEquipHaptics = function(potentialEquipHotspot, currentLocation) {
|
||||
if (potentialEquipHotspot && !this.prevPotentialEquipHotspot ||
|
||||
!potentialEquipHotspot && this.prevPotentialEquipHotspot) {
|
||||
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
||||
this.lastHapticPulseLocation = currentLocation;
|
||||
} else if (potentialEquipHotspot &&
|
||||
Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) {
|
||||
Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand);
|
||||
this.lastHapticPulseLocation = currentLocation;
|
||||
}
|
||||
this.prevPotentialEquipHotspot = potentialEquipHotspot;
|
||||
};
|
||||
|
||||
this.startEquipEntity = function (controllerData) {
|
||||
this.dropGestureReset();
|
||||
this.clearEquipHaptics();
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
// if an object is "equipped" and has a predefined offset, use it.
|
||||
var offsets = getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand);
|
||||
if (offsets) {
|
||||
this.offsetPosition = offsets[0];
|
||||
this.offsetRotation = offsets[1];
|
||||
} else {
|
||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
||||
if (this.grabbedHotspot.joints[handJointName]) {
|
||||
this.offsetPosition = this.grabbedHotspot.joints[handJointName][0];
|
||||
this.offsetRotation = this.grabbedHotspot.joints[handJointName][1];
|
||||
}
|
||||
}
|
||||
|
||||
var handJointIndex;
|
||||
if (this.ignoreIK) {
|
||||
handJointIndex = this.controllerJointIndex;
|
||||
} else {
|
||||
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
}
|
||||
|
||||
var reparentProps = {
|
||||
parentID: AVATAR_SELF_ID,
|
||||
parentJointIndex: handJointIndex,
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0},
|
||||
localPosition: this.offsetPosition,
|
||||
localRotation: this.offsetRotation
|
||||
};
|
||||
Entities.editEntity(this.grabbedThingID, reparentProps);
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'equip',
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
};
|
||||
|
||||
this.endEquipEntity = function () {
|
||||
Entities.editEntity(this.targetEntityID, {
|
||||
parentID: NULL_UUID,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
|
||||
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||
Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args);
|
||||
|
||||
this.targetEntityID = null;
|
||||
};
|
||||
|
||||
this.isReady = function (controllerData, deltaTime) {
|
||||
|
||||
this.rawTriggerValue = controllerData.triggerValues[this.hand];
|
||||
this.triggerClicked = controllerData.triggerClicks[this.hand];
|
||||
this.rawSecondaryValue = controllerData.secondaryValues[this.hand];
|
||||
this.updateSmoothedTrigger(controllerData);
|
||||
|
||||
this.controllerJointIndex = getControllerJointIndex(this.hand);
|
||||
|
||||
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
|
||||
this.waitForTriggerRelease = false;
|
||||
}
|
||||
|
||||
var controllerLocation = getControllerWorldLocation(this.handToController(), true);
|
||||
var worldHandPosition = controllerLocation.position;
|
||||
|
||||
// var candidateEntities = controllerData.nearbyEntityProperties[this.hand].map(function (props) {
|
||||
// return props.id;
|
||||
// });
|
||||
|
||||
var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand];
|
||||
|
||||
var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData);
|
||||
if (!this.waitForTriggerRelease) {
|
||||
this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition);
|
||||
}
|
||||
|
||||
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData);
|
||||
var timestamp = Date.now();
|
||||
equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp);
|
||||
if (potentialEquipHotspot) {
|
||||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||
}
|
||||
|
||||
equipHotspotBuddy.update(deltaTime, timestamp, controllerData);
|
||||
|
||||
return makeRunningValues(false, [], []);
|
||||
};
|
||||
|
||||
this.run = function (controllerData, deltaTime) {
|
||||
|
||||
if (controllerData.secondaryValues[this.hand]) {
|
||||
// this.secondaryReleased() will always be true when not depressed
|
||||
// so we cannot simply rely on that for release - ensure that the
|
||||
// trigger was first "prepared" by being pushed in before the release
|
||||
this.preparingHoldRelease = true;
|
||||
}
|
||||
|
||||
if (this.preparingHoldRelease && !controllerData.secondaryValues[this.hand]) {
|
||||
// we have an equipped object and the secondary trigger was released
|
||||
// short-circuit the other checks and release it
|
||||
this.preparingHoldRelease = false;
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
||||
var dropDetected = this.dropGestureProcess(deltaTime);
|
||||
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.waitForTriggerRelease = false;
|
||||
}
|
||||
|
||||
if (dropDetected && this.prevDropDetected != dropDetected) {
|
||||
this.waitForTriggerRelease = true;
|
||||
}
|
||||
|
||||
// highlight the grabbed hotspot when the dropGesture is detected.
|
||||
var timestamp = Date.now();
|
||||
if (dropDetected) {
|
||||
equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp);
|
||||
equipHotspotBuddy.highlightHotspot(this.grabbedHotspot);
|
||||
}
|
||||
|
||||
if (dropDetected && !this.waitForTriggerRelease && controllerData.triggerClicks[this.hand]) {
|
||||
// store the offset attach points into preferences.
|
||||
if (this.grabbedHotspot && this.grabbedThingID) {
|
||||
var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]);
|
||||
if (prefprops && prefprops.localPosition && prefprops.localRotation) {
|
||||
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand,
|
||||
prefprops.localPosition, prefprops.localRotation);
|
||||
}
|
||||
}
|
||||
|
||||
this.endEquipEntity();
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
this.prevDropDetected = dropDetected;
|
||||
|
||||
return makeRunningValues(true, [this.targetEntityID], []);
|
||||
};
|
||||
|
||||
this.cleanup = function () {
|
||||
if (this.targetEntityID) {
|
||||
this.endEquipEntity();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var leftEquipEntity = new EquipEntity(LEFT_HAND);
|
||||
var rightEquipEntity = new EquipEntity(RIGHT_HAND);
|
||||
|
||||
enableDispatcherModule("LeftEquipEntity", leftEquipEntity);
|
||||
enableDispatcherModule("RightEquipEntity", rightEquipEntity);
|
||||
|
||||
this.cleanup = function () {
|
||||
leftEquipEntity.cleanup();
|
||||
rightEquipEntity.cleanup();
|
||||
disableDispatcherModule("LeftEquipEntity");
|
||||
disableDispatcherModule("RightEquipEntity");
|
||||
};
|
||||
Script.scriptEnding.connect(this.cleanup);
|
||||
}());
|
|
@ -397,7 +397,8 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
if (entityIsDistanceGrabbable(targetProps)) {
|
||||
this.grabbedThingID = entityID;
|
||||
this.grabbedDistance = rayPickInfo.distance;
|
||||
var otherModuleName = this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";
|
||||
var otherModuleName =
|
||||
this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";
|
||||
var otherFarGrabModule = getEnabledModuleByName(otherModuleName);
|
||||
if (otherFarGrabModule.grabbedThingID == this.grabbedThingID) {
|
||||
this.distanceRotating = true;
|
||||
|
|
|
@ -23,7 +23,8 @@ var CONTOLLER_SCRIPTS = [
|
|||
"controllerModules/nearParentGrabOverlay.js",
|
||||
"controllerModules/nearActionGrabEntity.js",
|
||||
"controllerModules/farActionGrabEntity.js",
|
||||
"controllerModules/tabletStylusInput.js"
|
||||
"controllerModules/tabletStylusInput.js",
|
||||
"controllerModules/equipEntity.js"
|
||||
];
|
||||
|
||||
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";
|
||||
|
|
Loading…
Reference in a new issue