iterate over hotspots not entities.

This makes it possible to render multiple hotspots per entity.
Also, it will use the same logic to decide how to deal with overlapping entity
equip hotspots.
This commit is contained in:
Anthony J. Thibault 2016-06-27 18:05:45 -07:00
parent 1b98c73473
commit 43d4dba4c0
2 changed files with 78 additions and 75 deletions

View file

@ -10,7 +10,7 @@
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ /*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, flatten, Xform */
Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/utils.js");
Script.include("../libraries/Xform.js"); Script.include("../libraries/Xform.js");
@ -879,7 +879,7 @@ function MyController(hand) {
}; };
this.createHotspots = function () { this.createHotspots = function () {
var props, overlay; var _this = this;
var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 };
var HAND_EQUIP_SPHERE_ALPHA = 0.7; var HAND_EQUIP_SPHERE_ALPHA = 0.7;
@ -897,6 +897,7 @@ function MyController(hand) {
this.hotspotOverlays = []; this.hotspotOverlays = [];
var overlay;
if (DRAW_HAND_SPHERES) { if (DRAW_HAND_SPHERES) {
// add tiny green sphere around the palm. // add tiny green sphere around the palm.
var handPosition = this.getHandPosition(); var handPosition = this.getHandPosition();
@ -910,7 +911,6 @@ function MyController(hand) {
ignoreRayIntersection: true, ignoreRayIntersection: true,
drawInFront: false drawInFront: false
}); });
this.hotspotOverlays.push({ this.hotspotOverlays.push({
entityID: undefined, entityID: undefined,
overlay: overlay, overlay: overlay,
@ -928,7 +928,6 @@ function MyController(hand) {
ignoreRayIntersection: true, ignoreRayIntersection: true,
drawInFront: false drawInFront: false
}); });
this.hotspotOverlays.push({ this.hotspotOverlays.push({
entityID: undefined, entityID: undefined,
overlay: overlay, overlay: overlay,
@ -941,55 +940,54 @@ function MyController(hand) {
this.entityPropertyCache.clear(); this.entityPropertyCache.clear();
this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
var _this = this; if (DRAW_GRAB_BOXES) {
this.entityPropertyCache.getEntities().forEach(function (entityID) { // add blue box overlays for grabbable entities.
var props = _this.entityPropertyCache.getProps(entityID); this.entityPropertyCache.getEntities().forEach(function (entityID) {
if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) { var props = _this.entityPropertyCache.getProps(entityID);
var equipHotspots = _this.collectEquipHotspots(entityID); if (_this.entityIsGrabbable(entityID)) {
var entityXform = new Xform(props.rotation, props.position); var overlay = Overlays.addOverlay("cube", {
var i, length = equipHotspots.length; rotation: props.rotation,
for (i = 0; i < length; i++) { position: props.position,
var hotspot = equipHotspots[i]; size: props.dimensions,
overlay = Overlays.addOverlay("sphere", { color: GRAB_BOX_COLOR,
position: entityXform.xformPoint(hotspot.position), alpha: GRAB_BOX_ALPHA,
size: hotspot.radius * 2,
color: EQUIP_SPHERE_COLOR,
alpha: EQUIP_SPHERE_ALPHA,
solid: true, solid: true,
visible: true, visible: true,
ignoreRayIntersection: true, ignoreRayIntersection: true,
drawInFront: false drawInFront: false
}); });
_this.hotspotOverlays.push({ _this.hotspotOverlays.push({
entityID: entityID, entityID: entityID,
overlay: overlay, overlay: overlay,
type: "equip", type: "near",
localPosition: hotspot.position localPosition: {x: 0, y: 0, z: 0}
}); });
} }
} });
}
if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) { // add green spheres for each equippable hotspot.
overlay = Overlays.addOverlay("cube", { flatten(this.entityPropertyCache.getEntities().map(function (entityID) {
rotation: props.rotation, return _this.collectEquipHotspots(entityID);
position: props.position, })).filter(function (hotspot) {
size: props.dimensions, return _this.hotspotIsEquippable(hotspot);
color: GRAB_BOX_COLOR, }).forEach(function (hotspot) {
alpha: GRAB_BOX_ALPHA, var overlay = Overlays.addOverlay("sphere", {
solid: true, position: hotspot.worldPosition,
visible: true, size: hotspot.radius * 2,
ignoreRayIntersection: true, color: EQUIP_SPHERE_COLOR,
drawInFront: false alpha: EQUIP_SPHERE_ALPHA,
}); solid: true,
visible: true,
_this.hotspotOverlays.push({ ignoreRayIntersection: true,
entityID: entityID, drawInFront: false
overlay: overlay, });
type: "near", _this.hotspotOverlays.push({
localPosition: {x: 0, y: 0, z: 0} entityID: hotspot.entityID,
}); overlay: overlay,
} type: "equip",
localPosition: hotspot.localPosition
});
}); });
}; };
@ -1091,8 +1089,18 @@ function MyController(hand) {
return grabbableProps && grabbableProps.wantsTrigger; return grabbableProps && grabbableProps.wantsTrigger;
}; };
/// @param {UUID} entityID
// @returns {Object[]} array of objects with the following fields.
// * 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.
this.collectEquipHotspots = function (entityID) { this.collectEquipHotspots = function (entityID) {
var result = []; var result = [];
var props = this.entityPropertyCache.getProps(entityID);
var entityXform = new Xform(props.rotation, props.position);
var equipHotspotsProps = this.entityPropertyCache.getEquipHotspotsProps(entityID); var equipHotspotsProps = this.entityPropertyCache.getEquipHotspotsProps(entityID);
if (equipHotspotsProps && equipHotspotsProps.length > 0) { if (equipHotspotsProps && equipHotspotsProps.length > 0) {
var i, length = equipHotspotsProps.length; var i, length = equipHotspotsProps.length;
@ -1100,7 +1108,9 @@ function MyController(hand) {
var hotspot = equipHotspotsProps[i]; var hotspot = equipHotspotsProps[i];
if (hotspot.position && hotspot.radius && hotspot.joints) { if (hotspot.position && hotspot.radius && hotspot.joints) {
result.push({ result.push({
position: hotspot.position, entityID: entityID,
localPosition: hotspot.position,
worldPosition: entityXform.xformPoint(hotspot.position),
radius: hotspot.radius, radius: hotspot.radius,
joints: hotspot.joints joints: hotspot.joints
}); });
@ -1110,7 +1120,9 @@ function MyController(hand) {
var wearableProps = this.entityPropertyCache.getWearableProps(entityID); var wearableProps = this.entityPropertyCache.getWearableProps(entityID);
if (wearableProps && wearableProps.joints) { if (wearableProps && wearableProps.joints) {
result.push({ result.push({
position: {x: 0, y: 0, z: 0}, entityID: entityID,
localPosition: {x: 0, y: 0, z: 0},
worldPosition: entityXform.pos,
radius: EQUIP_RADIUS, radius: EQUIP_RADIUS,
joints: wearableProps.joints joints: wearableProps.joints
}); });
@ -1119,13 +1131,10 @@ function MyController(hand) {
return result; return result;
}; };
this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition, skipDistanceCheck) { this.hotspotIsEquippable = function (hotspot) {
var distance; var props = this.entityPropertyCache.getProps(hotspot.entityID);
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; var grabProps = this.entityPropertyCache.getGrabProps(hotspot.entityID);
var props = this.entityPropertyCache.getProps(entityID);
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
var entityXform = new Xform(props.rotation, props.position);
var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0;
if (refCount > 0) { if (refCount > 0) {
@ -1135,21 +1144,7 @@ function MyController(hand) {
return false; return false;
} }
var equipHotspots = this.collectEquipHotspots(entityID); return true;
var i, length = equipHotspots.length;
for (i = 0; i < length; i++) {
var hotspot = equipHotspots[i];
distance = Vec3.distance(entityXform.xformPoint(hotspot.position), handPosition);
if ((skipDistanceCheck || distance < hotspot.radius) && hotspot.joints[handJointName]) {
return true;
}
}
return false;
};
this.entityIsEquippableWithoutDistanceCheck = function (entityID) {
var handPosition = this.getHandPosition();
return this.entityIsEquippableWithDistanceCheck(entityID, handPosition, true);
}; };
this.entityIsGrabbable = function (entityID) { this.entityIsGrabbable = function (entityID) {
@ -1285,22 +1280,24 @@ function MyController(hand) {
this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS);
var candidateEntities = this.entityPropertyCache.getEntities(); var candidateEntities = this.entityPropertyCache.getEntities();
var equippableEntities = candidateEntities.filter(function (entity) { var equippableHotspots = flatten(candidateEntities.map(function (entityID) {
return _this.entityIsEquippableWithDistanceCheck(entity, handPosition); return _this.collectEquipHotspots(entityID);
})).filter(function (hotspot) {
return _this.hotspotIsEquippable(hotspot) && Vec3.distance(hotspot.worldPosition, handPosition) < hotspot.radius;
}); });
var entity; var entity;
if (equippableEntities.length > 0) { if (equippableHotspots.length > 0) {
// sort by distance // sort by distance
equippableEntities.sort(function (a, b) { equippableHotspots.sort(function (a, b) {
var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); var aDistance = Vec3.distance(a.worldPosition, handPosition);
var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); var bDistance = Vec3.distance(b.worldPosition, handPosition);
return aDistance - bDistance; return aDistance - bDistance;
}); });
entity = equippableEntities[0];
if (this.triggerSmoothedGrab()) { if (this.triggerSmoothedGrab()) {
this.grabbedEntity = entity; this.grabbedHotspot = equippableHotspots[0];
this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(entity).name + "'"); this.grabbedEntity = equippableHotspots[0].entityID;
this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(this.grabbedEntity).name + "'");
return; return;
} else { } else {
// TODO: highlight the equippable object? // TODO: highlight the equippable object?

View file

@ -309,5 +309,11 @@ calculateHandSizeRatio = function() {
clamp = function(val, min, max){ clamp = function(val, min, max){
return Math.max(min, Math.min(max, val)) return Math.max(min, Math.min(max, val))
} }
// flattens an array of arrays into a single array
// example: flatten([[1], [3, 4], []) => [1, 3, 4]
// NOTE: only does one level of flattening, it is not recursive.
flatten = function(array) {
return [].concat.apply([], array);
}