mirror of
https://github.com/lubosz/overte.git
synced 2025-04-16 21:46:34 +02:00
Merge pull request #8247 from hyperlogic/feature/grab-script-polish-part-deux
Grab Script Polish (part deux)
This commit is contained in:
commit
a0e6654ac6
1 changed files with 330 additions and 210 deletions
|
@ -42,10 +42,6 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only
|
|||
|
||||
var PICK_WITH_HAND_RAY = true;
|
||||
|
||||
var DRAW_GRAB_BOXES = false;
|
||||
var DRAW_HAND_SPHERES = false;
|
||||
var DROP_WITHOUT_SHAKE = false;
|
||||
|
||||
var EQUIP_SPHERE_COLOR = { red: 179, green: 120, blue: 211 };
|
||||
var EQUIP_SPHERE_ALPHA = 0.15;
|
||||
var EQUIP_SPHERE_SCALE_FACTOR = 0.65;
|
||||
|
@ -79,8 +75,6 @@ var LINE_ENTITY_DIMENSIONS = {
|
|||
var LINE_LENGTH = 500;
|
||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||
|
||||
var EQUIP_RADIUS_EMBIGGEN_FACTOR = 1.1;
|
||||
|
||||
//
|
||||
// near grabbing
|
||||
//
|
||||
|
@ -208,7 +202,8 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = {
|
|||
CONTROLLER_STATE_MACHINE[STATE_HOLD] = {
|
||||
name: "hold",
|
||||
enterMethod: "nearGrabbingEnter",
|
||||
updateMethod: "nearGrabbing"
|
||||
updateMethod: "nearGrabbing",
|
||||
exitMethod: "holdExit"
|
||||
};
|
||||
CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = {
|
||||
name: "trigger",
|
||||
|
@ -268,6 +263,51 @@ function propsArePhysical(props) {
|
|||
return isPhysical;
|
||||
}
|
||||
|
||||
// currently disabled.
|
||||
var USE_ATTACH_POINT_SETTINGS = false;
|
||||
|
||||
var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints";
|
||||
function getAttachPointSettings() {
|
||||
try {
|
||||
var str = Settings.getValue(ATTACH_POINT_SETTINGS);
|
||||
print("getAttachPointSettings = " + str);
|
||||
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);
|
||||
print("setAttachPointSettings = " + str);
|
||||
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 removeMyAvatarFromCollidesWith(origCollidesWith) {
|
||||
var collidesWithSplit = origCollidesWith.split(",");
|
||||
// remove myAvatar from the array
|
||||
|
@ -302,20 +342,32 @@ function restore2DMode() {
|
|||
|
||||
// EntityPropertiesCache is a helper class that contains a cache of entity properties.
|
||||
// the hope is to prevent excess calls to Entity.getEntityProperties()
|
||||
//
|
||||
// usage:
|
||||
// call EntityPropertiesCache.addEntities with all the entities that you are interested in.
|
||||
// This will fetch their properties. Then call EntityPropertiesCache.getProps to receive an object
|
||||
// containing a cache of all the properties previously fetched.
|
||||
function EntityPropertiesCache() {
|
||||
this.cache = {};
|
||||
}
|
||||
EntityPropertiesCache.prototype.clear = function () {
|
||||
this.cache = {};
|
||||
};
|
||||
EntityPropertiesCache.prototype.findEntities = function (position, radius) {
|
||||
var entities = Entities.findEntities(position, radius);
|
||||
EntityPropertiesCache.prototype.addEntity = function (entityID) {
|
||||
var cacheEntry = this.cache[entityID];
|
||||
if (cacheEntry && cacheEntry.refCount) {
|
||||
cacheEntry.refCount += 1;
|
||||
} else {
|
||||
this._updateCacheEntry(entityID);
|
||||
}
|
||||
};
|
||||
EntityPropertiesCache.prototype.addEntities = function (entities) {
|
||||
var _this = this;
|
||||
entities.forEach(function (x) {
|
||||
_this.updateEntity(x);
|
||||
entities.forEach(function (entityID) {
|
||||
_this.addEntity(entityID);
|
||||
});
|
||||
};
|
||||
EntityPropertiesCache.prototype.updateEntity = function (entityID) {
|
||||
EntityPropertiesCache.prototype._updateCacheEntry = function (entityID) {
|
||||
var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES);
|
||||
|
||||
// convert props.userData from a string to an object.
|
||||
|
@ -328,11 +380,21 @@ EntityPropertiesCache.prototype.updateEntity = function (entityID) {
|
|||
}
|
||||
}
|
||||
props.userData = userData;
|
||||
props.refCount = 1;
|
||||
|
||||
this.cache[entityID] = props;
|
||||
};
|
||||
EntityPropertiesCache.prototype.getEntities = function () {
|
||||
return Object.keys(this.cache);
|
||||
EntityPropertiesCache.prototype.update = function () {
|
||||
// delete any cacheEntries with zero refCounts.
|
||||
var entities = Object.keys(this.cache);
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
var props = this.cache[entities[i]];
|
||||
if (props.refCount === 0) {
|
||||
delete this.cache[entities[i]];
|
||||
} else {
|
||||
props.refCount = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
EntityPropertiesCache.prototype.getProps = function (entityID) {
|
||||
var obj = this.cache[entityID];
|
||||
|
@ -371,6 +433,158 @@ EntityPropertiesCache.prototype.getEquipHotspotsProps = function (entityID) {
|
|||
}
|
||||
};
|
||||
|
||||
// global cache
|
||||
var entityPropertiesCache = new EntityPropertiesCache();
|
||||
|
||||
// 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;
|
||||
|
||||
if (hotspot.modelURL) {
|
||||
// override default sphere with a user specified model
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
|
||||
url: hotspot.modelURL,
|
||||
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";
|
||||
} else {
|
||||
// default sphere overlay
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", {
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR,
|
||||
color: EQUIP_SPHERE_COLOR,
|
||||
alpha: EQUIP_SPHERE_ALPHA,
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: false
|
||||
}));
|
||||
overlayInfoSet.type = "sphere";
|
||||
}
|
||||
|
||||
this.map[hotspot.key] = overlayInfoSet;
|
||||
} else {
|
||||
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) {
|
||||
|
||||
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) {
|
||||
// because this item timestamp has expired, it might not be in the cache anymore....
|
||||
entityPropertiesCache.addEntity(overlayInfoSet.entityID);
|
||||
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) {
|
||||
// this is an old overlay, that has finished fading out, delete it!
|
||||
overlayInfoSet.overlays.forEach(function (overlay) {
|
||||
Overlays.deleteOverlay(overlay);
|
||||
});
|
||||
delete this.map[keys[i]];
|
||||
} else {
|
||||
// update overlay position, rotation to follow the object it's attached to.
|
||||
|
||||
var props = entityPropertiesCache.getProps(overlayInfoSet.entityID);
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// global EquipHotspotBuddy instance
|
||||
var equipHotspotBuddy = new EquipHotspotBuddy();
|
||||
|
||||
function MyController(hand) {
|
||||
this.hand = hand;
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
|
@ -421,8 +635,6 @@ function MyController(hand) {
|
|||
this.lastPickTime = 0;
|
||||
this.lastUnequipCheckTime = 0;
|
||||
|
||||
this.entityPropertyCache = new EntityPropertiesCache();
|
||||
|
||||
this.equipOverlayInfoSetMap = {};
|
||||
|
||||
var _this = this;
|
||||
|
@ -434,7 +646,7 @@ function MyController(hand) {
|
|||
return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode();
|
||||
};
|
||||
|
||||
this.update = function (deltaTime) {
|
||||
this.update = function (deltaTime, timestamp) {
|
||||
|
||||
this.updateSmoothedTrigger();
|
||||
|
||||
|
@ -447,7 +659,7 @@ function MyController(hand) {
|
|||
var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod;
|
||||
var updateMethod = this[updateMethodName];
|
||||
if (updateMethod) {
|
||||
updateMethod.call(this, deltaTime);
|
||||
updateMethod.call(this, deltaTime, timestamp);
|
||||
} else {
|
||||
print("WARNING: could not find updateMethod for state " + stateToName(this.state));
|
||||
}
|
||||
|
@ -907,7 +1119,7 @@ function MyController(hand) {
|
|||
return _this.rawThumbValue < THUMB_ON_VALUE;
|
||||
};
|
||||
|
||||
this.off = function () {
|
||||
this.off = function (deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.waitForTriggerRelease = false;
|
||||
}
|
||||
|
@ -921,17 +1133,18 @@ function MyController(hand) {
|
|||
}
|
||||
}
|
||||
|
||||
this.entityPropertyCache.clear();
|
||||
this.entityPropertyCache.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS);
|
||||
var candidateEntities = this.entityPropertyCache.getEntities();
|
||||
|
||||
var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS);
|
||||
entityPropertiesCache.addEntities(candidateEntities);
|
||||
var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities);
|
||||
if (!this.waitForTriggerRelease) {
|
||||
this.updateEquipHaptics(potentialEquipHotspot);
|
||||
}
|
||||
|
||||
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS);
|
||||
this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot);
|
||||
equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp);
|
||||
if (potentialEquipHotspot) {
|
||||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||
}
|
||||
};
|
||||
|
||||
this.clearEquipHaptics = function () {
|
||||
|
@ -946,95 +1159,6 @@ function MyController(hand) {
|
|||
this.prevPotentialEquipHotspot = potentialEquipHotspot;
|
||||
};
|
||||
|
||||
this.clearEquipHotspotRendering = function () {
|
||||
var keys = Object.keys(this.equipOverlayInfoSetMap);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]];
|
||||
this.deleteOverlayInfoSet(overlayInfoSet);
|
||||
}
|
||||
this.equipOverlayInfoSetMap = {};
|
||||
};
|
||||
|
||||
this.createOverlayInfoSet = function (hotspot, timestamp) {
|
||||
var overlayInfoSet = {
|
||||
timestamp: timestamp,
|
||||
entityID: hotspot.entityID,
|
||||
localPosition: hotspot.localPosition,
|
||||
hotspot: hotspot,
|
||||
overlays: []
|
||||
};
|
||||
|
||||
var diameter = hotspot.radius * 2;
|
||||
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", {
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR,
|
||||
color: EQUIP_SPHERE_COLOR,
|
||||
alpha: EQUIP_SPHERE_ALPHA,
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: false
|
||||
}));
|
||||
|
||||
return overlayInfoSet;
|
||||
};
|
||||
|
||||
this.updateOverlayInfoSet = function (overlayInfoSet, timestamp, potentialEquipHotspot) {
|
||||
overlayInfoSet.timestamp = timestamp;
|
||||
|
||||
var diameter = overlayInfoSet.hotspot.radius * 2;
|
||||
|
||||
// embiggen the overlays if it maches the potentialEquipHotspot
|
||||
if (potentialEquipHotspot && overlayInfoSet.entityID == potentialEquipHotspot.entityID &&
|
||||
Vec3.equal(overlayInfoSet.localPosition, potentialEquipHotspot.localPosition)) {
|
||||
diameter = diameter * EQUIP_RADIUS_EMBIGGEN_FACTOR;
|
||||
}
|
||||
|
||||
var props = _this.entityPropertyCache.getProps(overlayInfoSet.entityID);
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
var position = entityXform.xformPoint(overlayInfoSet.localPosition);
|
||||
|
||||
overlayInfoSet.overlays.forEach(function (overlay) {
|
||||
Overlays.editOverlay(overlay, {
|
||||
position: position,
|
||||
rotation: props.rotation,
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.deleteOverlayInfoSet = function (overlayInfoSet) {
|
||||
overlayInfoSet.overlays.forEach(function (overlay) {
|
||||
Overlays.deleteOverlay(overlay);
|
||||
});
|
||||
};
|
||||
|
||||
this.updateEquipHotspotRendering = function (hotspots, potentialEquipHotspot) {
|
||||
var now = Date.now();
|
||||
var _this = this;
|
||||
|
||||
hotspots.forEach(function (hotspot) {
|
||||
var overlayInfoSet = _this.equipOverlayInfoSetMap[hotspot.key];
|
||||
if (overlayInfoSet) {
|
||||
_this.updateOverlayInfoSet(overlayInfoSet, now, potentialEquipHotspot);
|
||||
} else {
|
||||
_this.equipOverlayInfoSetMap[hotspot.key] = _this.createOverlayInfoSet(hotspot, now);
|
||||
}
|
||||
});
|
||||
|
||||
// delete sets with old timestamps.
|
||||
var keys = Object.keys(this.equipOverlayInfoSetMap);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]];
|
||||
if (overlayInfoSet.timestamp !== now) {
|
||||
this.deleteOverlayInfoSet(overlayInfoSet);
|
||||
delete this.equipOverlayInfoSetMap[keys[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Performs ray pick test from the hand controller into the world
|
||||
// @param {number} which hand to use, RIGHT_HAND or LEFT_HAND
|
||||
// @returns {object} returns object with two keys entityID and distance
|
||||
|
@ -1093,7 +1217,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.entityWantsTrigger = function (entityID) {
|
||||
var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID);
|
||||
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
|
||||
return grabbableProps && grabbableProps.wantsTrigger;
|
||||
};
|
||||
|
||||
|
@ -1107,11 +1231,13 @@ function MyController(hand) {
|
|||
// * 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 (entityID) {
|
||||
var result = [];
|
||||
var props = this.entityPropertyCache.getProps(entityID);
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
var equipHotspotsProps = this.entityPropertyCache.getEquipHotspotsProps(entityID);
|
||||
var equipHotspotsProps = entityPropertiesCache.getEquipHotspotsProps(entityID);
|
||||
if (equipHotspotsProps && equipHotspotsProps.length > 0) {
|
||||
var i, length = equipHotspotsProps.length;
|
||||
for (i = 0; i < length; i++) {
|
||||
|
@ -1123,12 +1249,14 @@ function MyController(hand) {
|
|||
localPosition: hotspot.position,
|
||||
worldPosition: entityXform.xformPoint(hotspot.position),
|
||||
radius: hotspot.radius,
|
||||
joints: hotspot.joints
|
||||
joints: hotspot.joints,
|
||||
modelURL: hotspot.modelURL,
|
||||
modelScale: hotspot.modelScale
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var wearableProps = this.entityPropertyCache.getWearableProps(entityID);
|
||||
var wearableProps = entityPropertiesCache.getWearableProps(entityID);
|
||||
if (wearableProps && wearableProps.joints) {
|
||||
result.push({
|
||||
key: entityID.toString() + "0",
|
||||
|
@ -1136,7 +1264,9 @@ function MyController(hand) {
|
|||
localPosition: {x: 0, y: 0, z: 0},
|
||||
worldPosition: entityXform.pos,
|
||||
radius: EQUIP_RADIUS,
|
||||
joints: wearableProps.joints
|
||||
joints: wearableProps.joints,
|
||||
modelURL: null,
|
||||
modelScale: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1144,8 +1274,8 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.hotspotIsEquippable = function (hotspot) {
|
||||
var props = this.entityPropertyCache.getProps(hotspot.entityID);
|
||||
var grabProps = this.entityPropertyCache.getGrabProps(hotspot.entityID);
|
||||
var props = entityPropertiesCache.getProps(hotspot.entityID);
|
||||
var grabProps = entityPropertiesCache.getGrabProps(hotspot.entityID);
|
||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||
|
||||
var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0;
|
||||
|
@ -1163,9 +1293,9 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.entityIsGrabbable = function (entityID) {
|
||||
var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID);
|
||||
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
||||
var props = this.entityPropertyCache.getProps(entityID);
|
||||
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
|
||||
var grabProps = entityPropertiesCache.getGrabProps(entityID);
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
var physical = propsArePhysical(props);
|
||||
var grabbable = false;
|
||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||
|
@ -1219,7 +1349,7 @@ function MyController(hand) {
|
|||
return false;
|
||||
}
|
||||
|
||||
var props = this.entityPropertyCache.getProps(entityID);
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||
|
||||
|
@ -1257,7 +1387,7 @@ function MyController(hand) {
|
|||
return false;
|
||||
}
|
||||
|
||||
var props = this.entityPropertyCache.getProps(entityID);
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||
|
||||
|
@ -1298,7 +1428,7 @@ function MyController(hand) {
|
|||
}
|
||||
};
|
||||
|
||||
this.search = function () {
|
||||
this.search = function (deltaTime, timestamp) {
|
||||
var _this = this;
|
||||
var name;
|
||||
|
||||
|
@ -1315,16 +1445,15 @@ function MyController(hand) {
|
|||
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
this.entityPropertyCache.clear();
|
||||
this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||
var candidateEntities = this.entityPropertyCache.getEntities();
|
||||
var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||
entityPropertiesCache.addEntities(candidateEntities);
|
||||
|
||||
var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities);
|
||||
if (potentialEquipHotspot) {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.grabbedEntity = potentialEquipHotspot.entityID;
|
||||
this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(this.grabbedEntity).name + "'");
|
||||
this.setState(STATE_HOLD, "eqipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1336,7 +1465,7 @@ function MyController(hand) {
|
|||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||
if (rayPickInfo.entityID) {
|
||||
this.intersectionDistance = rayPickInfo.distance;
|
||||
this.entityPropertyCache.updateEntity(rayPickInfo.entityID);
|
||||
entityPropertiesCache.addEntity(rayPickInfo.entityID);
|
||||
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||
grabbableEntities.push(rayPickInfo.entityID);
|
||||
}
|
||||
|
@ -1350,12 +1479,12 @@ function MyController(hand) {
|
|||
if (grabbableEntities.length > 0) {
|
||||
// sort by distance
|
||||
grabbableEntities.sort(function (a, b) {
|
||||
var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition);
|
||||
var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition);
|
||||
var aDistance = Vec3.distance(entityPropertiesCache.getProps(a).position, handPosition);
|
||||
var bDistance = Vec3.distance(entityPropertiesCache.getProps(b).position, handPosition);
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
entity = grabbableEntities[0];
|
||||
name = this.entityPropertyCache.getProps(entity).name;
|
||||
name = entityPropertiesCache.getProps(entity).name;
|
||||
this.grabbedEntity = entity;
|
||||
if (this.entityWantsTrigger(entity)) {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
|
@ -1366,8 +1495,8 @@ function MyController(hand) {
|
|||
}
|
||||
} else {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
var props = this.entityPropertyCache.getProps(entity);
|
||||
var grabProps = this.entityPropertyCache.getGrabProps(entity);
|
||||
var props = entityPropertiesCache.getProps(entity);
|
||||
var grabProps = entityPropertiesCache.getGrabProps(entity);
|
||||
var refCount = grabProps.refCount ? grabProps.refCount : 0;
|
||||
if (refCount >= 1) {
|
||||
// if another person is holding the object, remember to restore the
|
||||
|
@ -1387,7 +1516,7 @@ function MyController(hand) {
|
|||
|
||||
if (rayPickInfo.entityID) {
|
||||
entity = rayPickInfo.entityID;
|
||||
name = this.entityPropertyCache.getProps(entity).name;
|
||||
name = entityPropertiesCache.getProps(entity).name;
|
||||
if (this.entityWantsTrigger(entity)) {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
this.grabbedEntity = entity;
|
||||
|
@ -1410,7 +1539,10 @@ function MyController(hand) {
|
|||
this.updateEquipHaptics(potentialEquipHotspot);
|
||||
|
||||
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS);
|
||||
this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot);
|
||||
equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp);
|
||||
if (potentialEquipHotspot) {
|
||||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||
}
|
||||
|
||||
// search line visualizations
|
||||
if (USE_ENTITY_LINES_FOR_SEARCHING === true) {
|
||||
|
@ -1439,7 +1571,6 @@ function MyController(hand) {
|
|||
|
||||
this.distanceHoldingEnter = function () {
|
||||
|
||||
this.clearEquipHotspotRendering();
|
||||
this.clearEquipHaptics();
|
||||
|
||||
// controller pose is in avatar frame
|
||||
|
@ -1499,7 +1630,7 @@ function MyController(hand) {
|
|||
this.previousControllerRotation = controllerRotation;
|
||||
};
|
||||
|
||||
this.distanceHolding = function () {
|
||||
this.distanceHolding = function (deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
|
@ -1668,47 +1799,35 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.dropGestureReset = function () {
|
||||
this.fastHandMoveDetected = false;
|
||||
this.fastHandMoveTimer = 0;
|
||||
this.prevHandIsUpsideDown = false;
|
||||
};
|
||||
|
||||
this.dropGestureProcess = function (deltaTime) {
|
||||
var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
var pose = Controller.getPoseValue(standardControllerValue);
|
||||
var worldHandVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity);
|
||||
var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation);
|
||||
|
||||
if (this.fastHandMoveDetected) {
|
||||
this.fastHandMoveTimer -= deltaTime;
|
||||
}
|
||||
if (this.fastHandMoveTimer < 0) {
|
||||
this.fastHandMoveDetected = false;
|
||||
}
|
||||
var FAST_HAND_SPEED_REST_TIME = 1; // sec
|
||||
var FAST_HAND_SPEED_THRESHOLD = 0.4; // m/sec
|
||||
if (Vec3.length(worldHandVelocity) > FAST_HAND_SPEED_THRESHOLD) {
|
||||
this.fastHandMoveDetected = true;
|
||||
this.fastHandMoveTimer = FAST_HAND_SPEED_REST_TIME;
|
||||
}
|
||||
|
||||
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 ROTATION_THRESHOLD = Math.cos(Math.PI / 8);
|
||||
|
||||
var DROP_ANGLE = Math.PI / 7;
|
||||
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) > ROTATION_THRESHOLD) {
|
||||
if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) {
|
||||
handIsUpsideDown = true;
|
||||
}
|
||||
|
||||
var WANT_DEBUG = false;
|
||||
if (WANT_DEBUG) {
|
||||
print("zAxis = " + worldHandUpAxis.x + ", " + worldHandUpAxis.y + ", " + worldHandUpAxis.z);
|
||||
print("dot = " + Vec3.dot(worldHandUpAxis, DOWN) + ", ROTATION_THRESHOLD = " + ROTATION_THRESHOLD);
|
||||
print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown);
|
||||
if (handIsUpsideDown != this.prevHandIsUpsideDown) {
|
||||
this.prevHandIsUpsideDown = handIsUpsideDown;
|
||||
Controller.triggerShortHapticPulse(0.5, this.hand);
|
||||
}
|
||||
|
||||
return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown;
|
||||
return handIsUpsideDown;
|
||||
};
|
||||
|
||||
this.nearGrabbingEnter = function () {
|
||||
|
@ -1718,7 +1837,6 @@ function MyController(hand) {
|
|||
|
||||
this.dropGestureReset();
|
||||
this.clearEquipHaptics();
|
||||
this.clearEquipHotspotRendering();
|
||||
|
||||
Controller.triggerShortHapticPulse(1.0, this.hand);
|
||||
|
||||
|
@ -1747,11 +1865,18 @@ function MyController(hand) {
|
|||
// if an object is "equipped" and has a predefined offset, use it.
|
||||
this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false;
|
||||
|
||||
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 offsets = USE_ATTACH_POINT_SETTINGS && getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand);
|
||||
if (offsets) {
|
||||
this.offsetPosition = offsets[0];
|
||||
this.offsetRotation = offsets[1];
|
||||
hasPresetPosition = true;
|
||||
} 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];
|
||||
hasPresetPosition = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.ignoreIK = false;
|
||||
|
@ -1820,7 +1945,7 @@ function MyController(hand) {
|
|||
this.currentAngularVelocity = ZERO_VEC;
|
||||
};
|
||||
|
||||
this.nearGrabbing = function (deltaTime) {
|
||||
this.nearGrabbing = function (deltaTime, timestamp) {
|
||||
|
||||
var dropDetected = this.dropGestureProcess(deltaTime);
|
||||
|
||||
|
@ -1831,6 +1956,14 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
if (this.state == STATE_HOLD) {
|
||||
|
||||
// highlight the grabbed hotspot when the dropGesture is detected.
|
||||
if (dropDetected) {
|
||||
entityPropertiesCache.addEntity(this.grabbedHotspot.entityID);
|
||||
equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp);
|
||||
equipHotspotBuddy.highlightHotspot(this.grabbedHotspot);
|
||||
}
|
||||
|
||||
if (dropDetected && this.triggerSmoothedGrab()) {
|
||||
this.callEntityMethodOnGrabbed("releaseEquip");
|
||||
this.setState(STATE_OFF, "drop gesture detected");
|
||||
|
@ -1937,9 +2070,24 @@ function MyController(hand) {
|
|||
}
|
||||
};
|
||||
|
||||
this.holdExit = function () {
|
||||
// store the offset attach points into preferences.
|
||||
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) {
|
||||
entityPropertiesCache.addEntity(this.grabbedEntity);
|
||||
var props = entityPropertiesCache.getProps(this.grabbedEntity);
|
||||
var entityXform = new Xform(props.rotation, props.position);
|
||||
var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position);
|
||||
var handRot = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation();
|
||||
var avatarHandPos = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
|
||||
var palmXform = new Xform(handRot, avatarXform.xformPoint(avatarHandPos));
|
||||
var offsetXform = Xform.mul(palmXform.inv(), entityXform);
|
||||
|
||||
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, offsetXform.pos, offsetXform.rot);
|
||||
}
|
||||
};
|
||||
|
||||
this.nearTriggerEnter = function () {
|
||||
|
||||
this.clearEquipHotspotRendering();
|
||||
this.clearEquipHaptics();
|
||||
|
||||
Controller.triggerShortHapticPulse(1.0, this.hand);
|
||||
|
@ -1947,13 +2095,12 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.farTriggerEnter = function () {
|
||||
this.clearEquipHotspotRendering();
|
||||
this.clearEquipHaptics();
|
||||
|
||||
this.callEntityMethodOnGrabbed("startFarTrigger");
|
||||
};
|
||||
|
||||
this.nearTrigger = function () {
|
||||
this.nearTrigger = function (deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.callEntityMethodOnGrabbed("stopNearTrigger");
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
|
@ -1962,7 +2109,7 @@ function MyController(hand) {
|
|||
this.callEntityMethodOnGrabbed("continueNearTrigger");
|
||||
};
|
||||
|
||||
this.farTrigger = function () {
|
||||
this.farTrigger = function (deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.callEntityMethodOnGrabbed("stopFarTrigger");
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
|
@ -2010,7 +2157,7 @@ function MyController(hand) {
|
|||
// and rotation of the held thing to help content creators set the userData.
|
||||
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
|
||||
if (grabData.refCount > 1) {
|
||||
grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]);
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]);
|
||||
if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) {
|
||||
print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" +
|
||||
'[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y +
|
||||
|
@ -2214,7 +2361,7 @@ function MyController(hand) {
|
|||
delayedDeactivateTimeout = null;
|
||||
_this.delayedDeactivateEntity(delayedEntityID, delayedCollidesWith);
|
||||
return delayedCollidesWith;
|
||||
}
|
||||
};
|
||||
delayedDeactivateTimeout =
|
||||
Script.setTimeout(delayedDeactivateFunc, COLLIDE_WITH_AV_AFTER_RELEASE_DELAY * MSECS_PER_SEC);
|
||||
delayedDeactivateEntityID = entityID;
|
||||
|
@ -2317,12 +2464,16 @@ Controller.enableMapping(MAPPING_NAME);
|
|||
var handToDisable = 'none';
|
||||
|
||||
function update(deltaTime) {
|
||||
var timestamp = Date.now();
|
||||
|
||||
if (handToDisable !== LEFT_HAND && handToDisable !== 'both') {
|
||||
leftController.update(deltaTime);
|
||||
leftController.update(deltaTime, timestamp);
|
||||
}
|
||||
if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') {
|
||||
rightController.update(deltaTime);
|
||||
rightController.update(deltaTime, timestamp);
|
||||
}
|
||||
equipHotspotBuddy.update(deltaTime, timestamp);
|
||||
entityPropertiesCache.update();
|
||||
}
|
||||
|
||||
Messages.subscribe('Hifi-Hand-Disabler');
|
||||
|
@ -2392,38 +2543,7 @@ function cleanup() {
|
|||
leftController.cleanup();
|
||||
Controller.disableMapping(MAPPING_NAME);
|
||||
Reticle.setVisible(true);
|
||||
Menu.removeMenuItem("Developer > Hands", "Drop Without Shake");
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update);
|
||||
|
||||
if (!Menu.menuExists("Developer > Grab Script")) {
|
||||
Menu.addMenu("Developer > Grab Script");
|
||||
}
|
||||
|
||||
Menu.addMenuItem({
|
||||
menuName: "Developer > Grab Script",
|
||||
menuItemName: "Drop Without Shake",
|
||||
isCheckable: true,
|
||||
isChecked: DROP_WITHOUT_SHAKE
|
||||
});
|
||||
|
||||
Menu.addMenuItem({
|
||||
menuName: "Developer > Grab Script",
|
||||
menuItemName: "Draw Grab Boxes",
|
||||
isCheckable: true,
|
||||
isChecked: DRAW_GRAB_BOXES
|
||||
});
|
||||
|
||||
function handleMenuItemEvent(menuItem) {
|
||||
if (menuItem === "Drop Without Shake") {
|
||||
DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake");
|
||||
}
|
||||
if (menuItem === "Draw Grab Boxes") {
|
||||
DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes");
|
||||
DRAW_HAND_SPHERES = DRAW_GRAB_BOXES;
|
||||
}
|
||||
}
|
||||
|
||||
Menu.menuItemEvent.connect(handleMenuItemEvent);
|
||||
|
|
Loading…
Reference in a new issue