From 848d5a89753df1972ccdc730650d236cb94faa61 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 13 Jul 2016 13:57:43 -0700 Subject: [PATCH 01/21] Reduce calls to Entity.getEntityProperties(). By using a single EntityProprtiesCache instance instead of one per controller. --- .../system/controllers/handControllerGrab.js | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 94a10798fe..2f82dd9e17 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -286,20 +286,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. @@ -312,11 +324,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]; @@ -355,6 +377,9 @@ EntityPropertiesCache.prototype.getEquipHotspotsProps = function (entityID) { } }; +// global cache +var entityPropertiesCache = new EntityPropertiesCache(); + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -404,8 +429,6 @@ function MyController(hand) { this.lastPickTime = 0; this.lastUnequipCheckTime = 0; - this.entityPropertyCache = new EntityPropertiesCache(); - this.equipOverlayInfoSetMap = {}; var _this = this; @@ -900,10 +923,8 @@ 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); @@ -971,7 +992,7 @@ function MyController(hand) { diameter = diameter * EQUIP_RADIUS_EMBIGGEN_FACTOR; } - var props = _this.entityPropertyCache.getProps(overlayInfoSet.entityID); + var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); var entityXform = new Xform(props.rotation, props.position); var position = entityXform.xformPoint(overlayInfoSet.localPosition); @@ -1072,7 +1093,7 @@ function MyController(hand) { }; this.entityWantsTrigger = function (entityID) { - var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); return grabbableProps && grabbableProps.wantsTrigger; }; @@ -1088,9 +1109,9 @@ function MyController(hand) { // offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint. 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++) { @@ -1107,7 +1128,7 @@ function MyController(hand) { } } } else { - var wearableProps = this.entityPropertyCache.getWearableProps(entityID); + var wearableProps = entityPropertiesCache.getWearableProps(entityID); if (wearableProps && wearableProps.joints) { result.push({ key: entityID.toString() + "0", @@ -1123,8 +1144,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; @@ -1142,9 +1163,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); @@ -1198,7 +1219,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); @@ -1236,7 +1257,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); @@ -1294,16 +1315,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; } } @@ -1315,7 +1335,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); } @@ -1329,12 +1349,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()) { @@ -1345,8 +1365,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 @@ -1366,7 +1386,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; @@ -1989,7 +2009,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 + @@ -2248,6 +2268,7 @@ function update(deltaTime) { if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { rightController.update(deltaTime); } + entityPropertiesCache.update(); } Messages.subscribe('Hifi-Hand-Disabler'); From 0ce970408c7059a93880ae58a9a4fe0000672c45 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 13 Jul 2016 14:44:15 -0700 Subject: [PATCH 02/21] Fix for double rendering of equip hotspots --- .../system/controllers/handControllerGrab.js | 222 +++++++++--------- 1 file changed, 117 insertions(+), 105 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2f82dd9e17..47fb689c33 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -380,6 +380,106 @@ 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() +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); +}; +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, + overlays: [] + }; + + var diameter = hotspot.radius * 2; + + // TODO: check for custom hotspot model urls. + + // default sphere + 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 + })); + + 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 keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + + // TODO: figure out how to do animation.... fade in, fade out, highlight, un-highlight + + if (overlayInfoSet.timestamp != timestamp) { + // this is an old stale overlay, 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); + + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.editOverlay(overlay, { + position: position, + rotation: props.rotation + }); + }); + } + } +}; + +// global EquipHotspotBuddy instance +var equipHotspotBuddy = new EquipHotspotBuddy(); + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -440,7 +540,7 @@ function MyController(hand) { return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; - this.update = function (deltaTime) { + this.update = function (deltaTime, timestamp) { this.updateSmoothedTrigger(); @@ -453,7 +553,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)); } @@ -909,7 +1009,7 @@ function MyController(hand) { return _this.rawThumbValue < THUMB_ON_VALUE; }; - this.off = function () { + this.off = function (deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.waitForTriggerRelease = false; } @@ -931,7 +1031,8 @@ function MyController(hand) { } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); }; this.clearEquipHaptics = function () { @@ -946,95 +1047,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 = entityPropertiesCache.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 @@ -1298,7 +1310,7 @@ function MyController(hand) { } }; - this.search = function () { + this.search = function (deltaTime, timestamp) { var _this = this; var name; @@ -1409,7 +1421,8 @@ function MyController(hand) { this.updateEquipHaptics(potentialEquipHotspot); var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); // search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { @@ -1438,7 +1451,6 @@ function MyController(hand) { this.distanceHoldingEnter = function () { - this.clearEquipHotspotRendering(); this.clearEquipHaptics(); // controller pose is in avatar frame @@ -1498,7 +1510,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"); @@ -1717,7 +1729,6 @@ function MyController(hand) { this.dropGestureReset(); this.clearEquipHaptics(); - this.clearEquipHotspotRendering(); Controller.triggerShortHapticPulse(1.0, this.hand); @@ -1819,7 +1830,7 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.nearGrabbing = function (deltaTime) { + this.nearGrabbing = function (deltaTime, timestamp) { var dropDetected = this.dropGestureProcess(deltaTime); @@ -1938,7 +1949,6 @@ function MyController(hand) { this.nearTriggerEnter = function () { - this.clearEquipHotspotRendering(); this.clearEquipHaptics(); Controller.triggerShortHapticPulse(1.0, this.hand); @@ -1946,13 +1956,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"); @@ -1961,7 +1970,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"); @@ -2262,12 +2271,15 @@ 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(); } From f3ef2801b4d5275dd27ff3070120a91e8781585d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 13 Jul 2016 15:07:09 -0700 Subject: [PATCH 03/21] Support for model overrides for equip hotspots rendering Bug fix for modelScale on hotspots --- .../system/controllers/handControllerGrab.js | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 47fb689c33..01a4e2588a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -420,20 +420,30 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { var diameter = hotspot.radius * 2; - // TODO: check for custom hotspot model urls. - - // default sphere - 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 - })); + 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 + })); + } 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 + })); + } this.map[hotspot.key] = overlayInfoSet; } else { @@ -1119,6 +1129,8 @@ 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 = entityPropertiesCache.getProps(entityID); @@ -1135,7 +1147,9 @@ 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 }); } } @@ -1148,7 +1162,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 }); } } From 66830a05387ad12b32c9687461e6f626e93509c2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 13 Jul 2016 19:44:51 -0700 Subject: [PATCH 04/21] First pass at equip sphere animation --- .../system/controllers/handControllerGrab.js | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 01a4e2588a..e446162278 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -78,8 +78,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 // @@ -404,7 +402,7 @@ EquipHotspotBuddy.prototype.clear = function () { this.highlightedHotspots = []; }; EquipHotspotBuddy.prototype.highlightHotspot = function (hotspot) { - this.highlightedHotspots.push(hotspot); + this.highlightedHotspots.push(hotspot.key); }; EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { var overlayInfoSet = this.map[hotspot.key]; @@ -415,6 +413,8 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { entityID: hotspot.entityID, localPosition: hotspot.localPosition, hotspot: hotspot, + currentSize: 0, + targetSize: 1, overlays: [] }; @@ -458,14 +458,38 @@ EquipHotspotBuddy.prototype.updateHotspots = function (hotspots, 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]]; - // TODO: figure out how to do animation.... fade in, fade out, highlight, un-highlight + // 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) { - // this is an old stale overlay, delete it! + // 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); }); @@ -476,11 +500,13 @@ EquipHotspotBuddy.prototype.update = function (deltaTime, timestamp) { var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); var entityXform = new Xform(props.rotation, props.position); var position = entityXform.xformPoint(overlayInfoSet.localPosition); + var alpha = overlayInfoSet.currentSize / HIGHLIGHT_SIZE; // AJT: TEMPORARY overlayInfoSet.overlays.forEach(function (overlay) { Overlays.editOverlay(overlay, { position: position, - rotation: props.rotation + rotation: props.rotation, + alpha: alpha // AJT: TEMPORARY }); }); } @@ -1042,7 +1068,9 @@ function MyController(hand) { var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); - equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } }; this.clearEquipHaptics = function () { @@ -1438,7 +1466,9 @@ function MyController(hand) { var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); - equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } // search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { From 23ea85541cbcae3f94b2b12e85c7806d7a7879ab Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 10:24:08 -0700 Subject: [PATCH 05/21] Animate equip hotspots scale instead of alpha --- scripts/system/controllers/handControllerGrab.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index e446162278..c3177c5338 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -385,6 +385,9 @@ var entityPropertiesCache = new EntityPropertiesCache(); // 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 = {}; @@ -430,6 +433,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { scale: hotspot.modelScale, ignoreRayIntersection: true })); + overlayInfoSet.type = "model"; } else { // default sphere overlay overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", { @@ -443,6 +447,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { ignoreRayIntersection: true, drawInFront: false })); + overlayInfoSet.type = "sphere"; } this.map[hotspot.key] = overlayInfoSet; @@ -500,13 +505,19 @@ EquipHotspotBuddy.prototype.update = function (deltaTime, timestamp) { var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); var entityXform = new Xform(props.rotation, props.position); var position = entityXform.xformPoint(overlayInfoSet.localPosition); - var alpha = overlayInfoSet.currentSize / HIGHLIGHT_SIZE; // AJT: TEMPORARY + + 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, - alpha: alpha // AJT: TEMPORARY + dimensions: dimensions }); }); } From 51cce939b30e09f041028d5e5c5dcf3cfc4605af Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 10:32:02 -0700 Subject: [PATCH 06/21] removed shake to drop --- .../system/controllers/handControllerGrab.js | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index c3177c5338..3b3015563a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -41,10 +41,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; @@ -1743,22 +1739,8 @@ function MyController(hand) { 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}; @@ -1776,7 +1758,7 @@ function MyController(hand) { print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); } - return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown; + return handIsUpsideDown; }; this.nearGrabbingEnter = function () { @@ -2407,38 +2389,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); From e62d34136d24ea7c4102fdd75f8a6d04a6c00b0c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 10:53:47 -0700 Subject: [PATCH 07/21] Added haptics and hysteresis to drop gesture --- .../system/controllers/handControllerGrab.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 3b3015563a..0ba0b1be7a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1732,8 +1732,7 @@ function MyController(hand) { }; this.dropGestureReset = function () { - this.fastHandMoveDetected = false; - this.fastHandMoveTimer = 0; + this.prevHandIsUpsideDown = false; }; this.dropGestureProcess = function (deltaTime) { @@ -1744,18 +1743,21 @@ function MyController(hand) { 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 handIsUpsideDown; From eb5107d9a510dcf6da5d117198bb13869cb6c267 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 12:07:07 -0700 Subject: [PATCH 08/21] highlight the grabbed hotspot when the drop gesture is detected --- scripts/system/controllers/handControllerGrab.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0ba0b1be7a..c78cf4dbbb 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1882,6 +1882,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"); From 6094fb3de3eb90e40d8e137bf20e0e0cacb14f1e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 15:51:05 -0700 Subject: [PATCH 09/21] Remember object attach point offsets in user settings --- .../system/controllers/handControllerGrab.js | 76 +++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index c78cf4dbbb..fef58d28b4 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -197,7 +197,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", @@ -257,6 +258,48 @@ function propsArePhysical(props) { return isPhysical; } +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); +} + // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; @@ -1798,11 +1841,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 = 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; @@ -1996,6 +2046,22 @@ function MyController(hand) { } }; + this.holdExit = function () { + // store the offset attach points into preferences. + if (this.grabbedHotspot) { + 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.clearEquipHaptics(); From 37d60c6c8575b3423c74755c320782ebc4fd9e38 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 14 Jul 2016 15:58:13 -0700 Subject: [PATCH 10/21] eslint clean --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 93c8dbde76..d6bf5cdd5f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2354,7 +2354,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; From c462b8c387559f14150d4d9416eab2d8c3d89e15 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 13:51:14 -0700 Subject: [PATCH 11/21] Make search line termination a circle, like 2D UI --- .../system/controllers/handControllerGrab.js | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 13d71dca1c..e1c4c9e757 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -229,6 +229,14 @@ function getTag() { return "grab-" + MyAvatar.sessionUUID; } +function colorPow(color, power) { + return { + red: Math.pow(color.red / 255.0, power) * 255, + green: Math.pow(color.green / 255.0, power) * 255, + blue: Math.pow(color.blue / 255.0, power) * 255, + }; +} + function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } @@ -547,23 +555,34 @@ function MyController(hand) { var SEARCH_SPHERE_ALPHA = 0.5; this.searchSphereOn = function (location, size, color) { + + var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + var brightColor = colorPow(color, 0.1); + print("bright color " + brightColor.red + " " + brightColor.green + " " + brightColor.blue); if (this.searchSphere === null) { var sphereProperties = { position: location, - size: size, - color: color, - alpha: SEARCH_SPHERE_ALPHA, + rotation: rotation, + outerRadius: size * 3.0, + innerColor: brightColor, + outerColor: color, + innerAlpha: 0.9, + outerAlpha: 0.0, solid: true, ignoreRayIntersection: true, drawInFront: true, // Even when burried inside of something, show it. visible: true }; - this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); + this.searchSphere = Overlays.addOverlay("circle3d", sphereProperties); } else { Overlays.editOverlay(this.searchSphere, { position: location, - size: size, - color: color, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + innerAlpha: 1.0, + outerAlpha: 0.0, + outerRadius: size * 3.0, visible: true }); } From 0d811c489aa3464f9a75761581078ad5c0a50b29 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 14:05:16 -0700 Subject: [PATCH 12/21] Removing debug logging --- scripts/system/controllers/handControllerGrab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index e1c4c9e757..4f0578369f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -558,7 +558,6 @@ function MyController(hand) { var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); var brightColor = colorPow(color, 0.1); - print("bright color " + brightColor.red + " " + brightColor.green + " " + brightColor.blue); if (this.searchSphere === null) { var sphereProperties = { position: location, From e4794450c202e38b13b837131bbb4f85de89843f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 15 Jul 2016 15:36:20 -0700 Subject: [PATCH 13/21] Disable attach point saving --- scripts/system/controllers/handControllerGrab.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d6bf5cdd5f..1e83fca024 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -264,6 +264,9 @@ function propsArePhysical(props) { return isPhysical; } +// currently disabled. +var USE_ATTACH_POINT_SETTINGS = false; + var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; function getAttachPointSettings() { try { @@ -1858,7 +1861,7 @@ function MyController(hand) { // if an object is "equipped" and has a predefined offset, use it. this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; - var offsets = getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); + var offsets = USE_ATTACH_POINT_SETTINGS && getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); if (offsets) { this.offsetPosition = offsets[0]; this.offsetRotation = offsets[1]; @@ -2065,7 +2068,7 @@ function MyController(hand) { this.holdExit = function () { // store the offset attach points into preferences. - if (this.grabbedHotspot) { + 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); From 317dee1b235c12ddbdb316d3c36ef229e456d4a7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 16:15:53 -0700 Subject: [PATCH 14/21] Fix decoration inflation --- .../resources/qml/windows/Decoration.qml | 22 +++++++++++++++++-- .../src/display-plugins/CompositorHelper.cpp | 9 ++++++++ .../src/display-plugins/CompositorHelper.h | 6 ++--- .../controllers/handControllerPointer.js | 9 +++++++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index 0048c556eb..843ae25596 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -48,7 +48,23 @@ Rectangle { drag.target: window hoverEnabled: true onEntered: window.mouseEntered(); - onExited: window.mouseExited(); + onExited: { + if (!containsMouseGlobal()) { + window.mouseExited(); + } + } + + function containsMouseGlobal() { + var reticlePos = Reticle.position; + var globalPosition = decorationMouseArea.mapToItem(desktop, 0, 0); + var localPosition = { + x: reticlePos.x - globalPosition.x, + y: reticlePos.y - globalPosition.y, + }; + return localPosition.x >= 0 && localPosition.x <= width && + localPosition.y >= 0 && localPosition.y <= height; + } + } Connections { target: window @@ -57,7 +73,9 @@ Rectangle { root.inflateDecorations() } } - onMouseExited: root.deflateDecorations(); + onMouseExited: { + root.deflateDecorations(); + } } Connections { target: desktop diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 89ff2e0c8d..56be8e1cf9 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -442,3 +442,12 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } return result; } + + +QVariant ReticleInterface::getPosition() const { + return vec2toVariant(_compositor->getReticlePosition()); +} + +void ReticleInterface::setPosition(QVariant position) { + _compositor->setReticlePosition(vec2FromVariant(position)); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 2a3dd0c852..b0b96d86be 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -174,7 +174,7 @@ private: // Scripting interface available to control the Reticle class ReticleInterface : public QObject { Q_OBJECT - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) @@ -198,8 +198,8 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } - Q_INVOKABLE glm::vec2 getPosition() { return _compositor->getReticlePosition(); } - Q_INVOKABLE void setPosition(glm::vec2 position) { _compositor->setReticlePosition(position); } + Q_INVOKABLE QVariant getPosition() const; + Q_INVOKABLE void setPosition(QVariant position); Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 9ff82b3767..dab6438efa 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -32,6 +32,7 @@ function setupHandler(event, handler) { event.disconnect(handler); }); } + // If some capability is not available until expiration milliseconds after the last update. function TimeLock(expiration) { var last = 0; @@ -42,6 +43,7 @@ function TimeLock(expiration) { return ((optionalNow || Date.now()) - last) > expiration; }; } + var handControllerLockOut = new TimeLock(2000); function Trigger(label) { @@ -118,6 +120,10 @@ function ignoreMouseActivity() { if (!Reticle.allowMouseCapture) { return true; } + var pos = Reticle.position; + if (pos.x == -1 && pos.y == -1) { + return true; + } // Only we know if we moved it, which is why this script has to replace depthReticle.js if (!weMovedReticle) { return false; @@ -433,11 +439,12 @@ function clearSystemLaser() { } HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; + weMovedReticle = true; + Reticle.position = { x: -1, y: -1 }; } function setColoredLaser() { // answer trigger state if lasers supported, else falsey. var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; - } // MAIN OPERATIONS ----------- From b31300406a63fd39c34c570cc554f4ceadc87161 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 16:19:19 -0700 Subject: [PATCH 15/21] Don't extend glow line length --- libraries/render-utils/src/glowLine.slg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/render-utils/src/glowLine.slg b/libraries/render-utils/src/glowLine.slg index 429cb8ee37..9af8eaa4d0 100644 --- a/libraries/render-utils/src/glowLine.slg +++ b/libraries/render-utils/src/glowLine.slg @@ -71,28 +71,24 @@ void main() { lineOrthogonal *= 0.02; gl_Position = gl_PositionIn[0]; - gl_Position.xy -= lineNormal; gl_Position.xy -= lineOrthogonal; outColor = inColor[0]; outLineDistance = vec3(-1.02, -1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[0]; - gl_Position.xy -= lineNormal; gl_Position.xy += lineOrthogonal; outColor = inColor[0]; outLineDistance = vec3(-1.02, 1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[1]; - gl_Position.xy += lineNormal; gl_Position.xy -= lineOrthogonal; outColor = inColor[1]; outLineDistance = vec3(1.02, -1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[1]; - gl_Position.xy += lineNormal; gl_Position.xy += lineOrthogonal; outColor = inColor[1]; outLineDistance = vec3(1.02, 1, gl_Position.z); From 9ebc0c28c846b309716b8313e4e9bff9f5cbddb8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 17:12:49 -0700 Subject: [PATCH 16/21] Fix hover states for toolbar buttons --- interface/resources/qml/hifi/toolbars/ToolbarButton.qml | 4 ++-- scripts/system/edit.js | 5 +++++ scripts/system/examples.js | 4 ++++ scripts/system/goto.js | 4 ++++ scripts/system/hmd.js | 4 ++++ scripts/system/ignore.js | 4 ++++ scripts/system/mute.js | 8 +++++++- 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index f4693adec5..aed90cd433 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -50,12 +50,12 @@ Item { anchors.fill: parent onClicked: asyncClickSender.start(); onEntered: { - if (hoverState > 0) { + if (hoverState >= 0) { buttonState = hoverState; } } onExited: { - if (defaultState > 0) { + if (defaultState >= 0) { buttonState = defaultState; } } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 05d554393f..f7e44933d7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -234,11 +234,16 @@ var toolBar = (function() { objectName: EDIT_TOGGLE_BUTTON, imageURL: TOOL_ICON_URL + "edit.svg", visible: true, + alpha: 0.9, buttonState: 1, + hoverState: 3, + defaultState: 1, }); activeButton.clicked.connect(function() { that.setActive(!isActive); activeButton.writeProperty("buttonState", isActive ? 0 : 1); + activeButton.writeProperty("defaultState", isActive ? 0 : 1); + activeButton.writeProperty("hoverState", isActive ? 2 : 3); }); toolBar = Toolbars.getToolbar(EDIT_TOOLBAR); diff --git a/scripts/system/examples.js b/scripts/system/examples.js index cea176f6b1..6fdb2a2874 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -56,11 +56,15 @@ var browseExamplesButton = toolBar.addButton({ imageURL: toolIconUrl + "market.svg", objectName: "examples", buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9 }); function onExamplesWindowVisibilityChanged() { browseExamplesButton.writeProperty('buttonState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('defaultState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('hoverState', examplesWindow.visible ? 2 : 3); } function onClick() { toggleExamples(); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 9cdf579d72..2ed98c689b 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -17,11 +17,15 @@ var button = toolBar.addButton({ imageURL: Script.resolvePath("assets/images/tools/directory.svg"), visible: true, buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9, }); function onAddressBarShown(visible) { button.writeProperty('buttonState', visible ? 0 : 1); + button.writeProperty('defaultState', visible ? 0 : 1); + button.writeProperty('hoverState', visible ? 2 : 3); } function onClicked(){ DialogsManager.toggleAddressBar(); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 9b749c306f..ac1918b001 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -22,6 +22,8 @@ var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var button; function onHmdChanged(isHmd) { button.writeProperty('buttonState', isHmd ? 0 : 1); + button.writeProperty('defaultState', isHmd ? 0 : 1); + button.writeProperty('hoverState', isHmd ? 2 : 3); } function onClicked(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); @@ -32,6 +34,8 @@ if (headset) { objectName: "hmdToggle", imageURL: Script.resolvePath("assets/images/tools/switch.svg"), visible: true, + hoverState: 2, + defaultState: 0, alpha: 0.9, }); onHmdChanged(HMD.active); diff --git a/scripts/system/ignore.js b/scripts/system/ignore.js index 39405a2e57..1c996a7fcc 100644 --- a/scripts/system/ignore.js +++ b/scripts/system/ignore.js @@ -18,6 +18,8 @@ var button = toolbar.addButton({ imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), visible: true, buttonState: 1, + defaultState: 2, + hoverState: 3, alpha: 0.9 }); @@ -46,6 +48,8 @@ function buttonClicked(){ } button.writeProperty('buttonState', isShowingOverlays ? 0 : 1); + button.writeProperty('defaultState', isShowingOverlays ? 0 : 1); + button.writeProperty('hoverState', isShowingOverlays ? 2 : 3); } button.clicked.connect(buttonClicked); diff --git a/scripts/system/mute.js b/scripts/system/mute.js index 511c013d5e..4ea8aee546 100644 --- a/scripts/system/mute.js +++ b/scripts/system/mute.js @@ -17,13 +17,19 @@ var button = toolBar.addButton({ imageURL: Script.resolvePath("assets/images/tools/mic.svg"), visible: true, buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9 }); function onMuteToggled() { // We could just toggle state, but we're less likely to get out of wack if we read the AudioDevice. // muted => button "on" state => 1. go figure. - button.writeProperty('buttonState', AudioDevice.getMuted() ? 0 : 1); + var state = AudioDevice.getMuted() ? 0 : 1; + var hoverState = AudioDevice.getMuted() ? 2 : 3; + button.writeProperty('buttonState', state); + button.writeProperty('defaultState', state); + button.writeProperty('hoverState', hoverState); } onMuteToggled(); function onClicked(){ From 1145c3b59006e2bf3378f80a8d85e5f48b9858dc Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Jul 2016 17:45:16 -0700 Subject: [PATCH 17/21] Smaller and hotter circle, per Philip --- scripts/system/controllers/handControllerGrab.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4f0578369f..8020163d32 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -557,12 +557,12 @@ function MyController(hand) { this.searchSphereOn = function (location, size, color) { var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); - var brightColor = colorPow(color, 0.1); + var brightColor = colorPow(color, 0.06); if (this.searchSphere === null) { var sphereProperties = { position: location, rotation: rotation, - outerRadius: size * 3.0, + outerRadius: size * 1.2, innerColor: brightColor, outerColor: color, innerAlpha: 0.9, @@ -581,7 +581,7 @@ function MyController(hand) { outerColor: color, innerAlpha: 1.0, outerAlpha: 0.0, - outerRadius: size * 3.0, + outerRadius: size * 1.2, visible: true }); } From 59ac2a789ff9bd406bc20c4605754f64f9effe3c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 15 Jul 2016 18:07:19 -0700 Subject: [PATCH 18/21] Fix the AudioMixer distance attenuation. The zone settings are still used, and still match the documentation where 0 = no attenuation and 1 = max attenuation. The default is now 0.5 which corresponds to -6dB per doubling of distance. This is the attenuation for a spherical wave in the free field. --- assignment-client/src/audio/AudioMixer.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 08e6e029a0..40e22f855a 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -62,7 +62,7 @@ #include "AudioMixer.h" const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f; -const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.18f; +const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) const float DEFAULT_NOISE_MUTING_THRESHOLD = 0.003f; const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; const QString AUDIO_ENV_GROUP_KEY = "audio_env"; @@ -141,13 +141,14 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd, } if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) { - // calculate the distance coefficient using the distance to this node - float distanceCoefficient = 1.0f - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f) - * attenuationPerDoublingInDistance); - if (distanceCoefficient < 0) { - distanceCoefficient = 0; - } + // translate the zone setting to gain per log2(distance) + float g = 1.0f - attenuationPerDoublingInDistance; + g = (g < EPSILON) ? EPSILON : g; + g = (g > 1.0f) ? 1.0f : g; + + // calculate the distance coefficient using the distance to this node + float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE)); // multiply the current attenuation coefficient by the distance coefficient gain *= distanceCoefficient; From 1ee608ad2cb13ce7f23374ea93f7f040a717d16b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 15 Jul 2016 18:23:12 -0700 Subject: [PATCH 19/21] Cleanup --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 84d0146f3b..1ec2f40928 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1309,7 +1309,7 @@ float AudioClient::gainForSource(float distance, float volume) { // attenuate based on distance if (distance >= ATTENUATION_BEGINS_AT_DISTANCE) { - gain /= distance; // attenuation = -6dB * log2(distance) + gain /= (distance/ATTENUATION_BEGINS_AT_DISTANCE); // attenuation = -6dB * log2(distance) } return gain; From 03cb6175ab79d143eecc015e3dee21375e70326d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 Jul 2016 09:10:21 -0700 Subject: [PATCH 20/21] Support triangle and hexagon shapes, add shape support to overlays. --- interface/src/ui/overlays/Overlays.cpp | 3 + interface/src/ui/overlays/Shape3DOverlay.cpp | 130 ++++++++++++++++++ interface/src/ui/overlays/Shape3DOverlay.h | 46 +++++++ .../src/RenderableShapeEntityItem.cpp | 6 +- libraries/entities/src/ShapeEntityItem.cpp | 8 +- libraries/entities/src/ShapeEntityItem.h | 2 + libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/render-utils/src/GeometryCache.cpp | 93 ++++++++++++- libraries/render-utils/src/GeometryCache.h | 16 ++- scripts/system/html/entityProperties.html | 3 + 10 files changed, 298 insertions(+), 12 deletions(-) create mode 100644 interface/src/ui/overlays/Shape3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Shape3DOverlay.h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9ff7f6268f..e99ca3a9e0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -22,6 +22,7 @@ #include "Image3DOverlay.h" #include "Circle3DOverlay.h" #include "Cube3DOverlay.h" +#include "Shape3DOverlay.h" #include "ImageOverlay.h" #include "Line3DOverlay.h" #include "LocalModelsOverlay.h" @@ -157,6 +158,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QVariant& propertie thisOverlay = std::make_shared(); } else if (type == Text3DOverlay::TYPE) { thisOverlay = std::make_shared(); + } else if (type == Shape3DOverlay::TYPE) { + thisOverlay = std::make_shared(); } else if (type == Cube3DOverlay::TYPE) { thisOverlay = std::make_shared(); } else if (type == Sphere3DOverlay::TYPE) { diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp new file mode 100644 index 0000000000..cd07385aab --- /dev/null +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -0,0 +1,130 @@ +// +// Shape3DOverlay.cpp +// interface/src/ui/overlays +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// include this before QGLWidget, which includes an earlier version of OpenGL +#include "Shape3DOverlay.h" + +#include +#include +#include +#include + +QString const Shape3DOverlay::TYPE = "shape"; + +Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) : + Volume3DOverlay(Shape3DOverlay) +{ +} + +void Shape3DOverlay::render(RenderArgs* args) { + if (!_visible) { + return; // do nothing if we're not visible + } + + float alpha = getAlpha(); + xColor color = getColor(); + const float MAX_COLOR = 255.0f; + glm::vec4 cubeColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + // TODO: handle registration point?? + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); + glm::quat rotation = getRotation(); + + auto batch = args->_batch; + + if (batch) { + Transform transform; + transform.setTranslation(position); + transform.setRotation(rotation); + auto geometryCache = DependencyManager::get(); + auto pipeline = args->_pipeline; + if (!pipeline) { + pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline(); + } + + transform.setScale(dimensions); + batch->setModelTransform(transform); + if (_isSolid) { + geometryCache->renderSolidShapeInstance(*batch, _shape, cubeColor, pipeline); + } else { + geometryCache->renderWireShapeInstance(*batch, _shape, cubeColor, pipeline); + } + } +} + +const render::ShapeKey Shape3DOverlay::getShapeKey() { + auto builder = render::ShapeKey::Builder(); + if (getAlpha() != 1.0f) { + builder.withTranslucent(); + } + if (!getIsSolid()) { + builder.withUnlit().withDepthBias(); + } + return builder.build(); +} + +Shape3DOverlay* Shape3DOverlay::createClone() const { + return new Shape3DOverlay(this); +} + + +static const std::array shapeStrings { { + "Line", + "Triangle", + "Quad", + "Hexagon", + "Octagon", + "Circle", + "Cube", + "Sphere", + "Tetrahedron", + "Octahedron", + "Dodecahedron", + "Icosahedron", + "Torus", + "Cone", + "Cylinder" +} }; + + +void Shape3DOverlay::setProperties(const QVariantMap& properties) { + Volume3DOverlay::setProperties(properties); + + auto shape = properties["shape"]; + if (shape.isValid()) { + const QString shapeStr = shape.toString(); + for (size_t i = 0; i < shapeStrings.size(); ++i) { + if (shapeStr == shapeStrings[i]) { + this->_shape = static_cast(i); + break; + } + } + } + + auto borderSize = properties["borderSize"]; + + if (borderSize.isValid()) { + float value = borderSize.toFloat(); + setBorderSize(value); + } +} + +QVariant Shape3DOverlay::getProperty(const QString& property) { + if (property == "borderSize") { + return _borderSize; + } + + if (property == "shape") { + return shapeStrings[_shape]; + } + + return Volume3DOverlay::getProperty(property); +} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h new file mode 100644 index 0000000000..2361001721 --- /dev/null +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -0,0 +1,46 @@ +// +// Shape3DOverlay.h +// interface/src/ui/overlays +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Shape3DOverlay_h +#define hifi_Shape3DOverlay_h + +#include "Volume3DOverlay.h" + +#include + +class Shape3DOverlay : public Volume3DOverlay { + Q_OBJECT + +public: + static QString const TYPE; + virtual QString getType() const override { return TYPE; } + + Shape3DOverlay() {} + Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay); + + virtual void render(RenderArgs* args) override; + virtual const render::ShapeKey getShapeKey() override; + + virtual Shape3DOverlay* createClone() const override; + + float getBorderSize() const { return _borderSize; } + + void setBorderSize(float value) { _borderSize = value; } + + void setProperties(const QVariantMap& properties) override; + QVariant getProperty(const QString& property) override; + +private: + float _borderSize; + GeometryCache::Shape _shape { GeometryCache::Hexagon }; +}; + + +#endif // hifi_Shape3DOverlay_h diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index ec07e10ccf..48ad05a714 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -23,9 +23,11 @@ // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; -static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = { +static std::array MAPPING { { GeometryCache::Triangle, GeometryCache::Quad, + GeometryCache::Hexagon, + GeometryCache::Octagon, GeometryCache::Circle, GeometryCache::Cube, GeometryCache::Sphere, @@ -36,7 +38,7 @@ static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = { GeometryCache::Torus, GeometryCache::Cone, GeometryCache::Cylinder, -}; +} }; RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 84208cc6f1..141526643c 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -20,17 +20,19 @@ #include "ShapeEntityItem.h" namespace entity { - static const std::vector shapeStrings { { + static const std::array shapeStrings { { "Triangle", "Quad", - "Circle", + "Hexagon", + "Octagon", + "Circle", "Cube", "Sphere", "Tetrahedron", "Octahedron", "Dodecahedron", "Icosahedron", - "Torus", + "Torus", "Cone", "Cylinder" } }; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 2ae4ae2ca1..122fc98dc0 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -15,6 +15,8 @@ namespace entity { enum Shape { Triangle, Quad, + Hexagon, + Octagon, Circle, Cube, Sphere, diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9581f3ca20..85030135a1 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -97,7 +97,8 @@ public: ICEServerHeartbeatACK, NegotiateAudioFormat, SelectedAudioFormat, - LAST_PACKET_TYPE = SelectedAudioFormat + MoreEntityShapes, + LAST_PACKET_TYPE = MoreEntityShapes }; }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 4558b68af9..50a93d2200 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -60,6 +60,18 @@ static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32); +template +std::vector polygon() { + std::vector result; + result.reserve(SIDES); + double angleIncrement = 2.0 * M_PI / SIDES; + for (size_t i = 0; i < SIDES; ++i) { + double angle = (double)i * angleIncrement; + result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 }); + } + return result; +} + void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { vertexBuffer->append(vertices); @@ -239,6 +251,75 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); } +template +void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + VertexVector vertices; + IndexVector solidIndices, wireIndices; + + // Top and bottom faces + std::vector shape = polygon(); + for (const vec3& v : shape) { + vertices.push_back(vec3(v.x, 0.5f, v.z)); + vertices.push_back(vec3(0, 1, 0)); + } + for (const vec3& v : shape) { + vertices.push_back(vec3(v.x, -0.5f, v.z)); + vertices.push_back(vec3(0, -1, 0)); + } + for (uint32_t i = 2; i < N; ++i) { + solidIndices.push_back(baseVertex + 0); + solidIndices.push_back(baseVertex + i); + solidIndices.push_back(baseVertex + i - 1); + solidIndices.push_back(baseVertex + N); + solidIndices.push_back(baseVertex + i + N - 1); + solidIndices.push_back(baseVertex + i + N); + } + for (uint32_t i = 1; i <= N; ++i) { + wireIndices.push_back(baseVertex + (i % N)); + wireIndices.push_back(baseVertex + i - 1); + wireIndices.push_back(baseVertex + (i % N) + N); + wireIndices.push_back(baseVertex + (i - 1) + N); + } + + // Now do the sides + baseVertex += 2 * N; + + for (uint32_t i = 0; i < N; ++i) { + vec3 left = shape[i]; + vec3 right = shape[(i + 1) % N]; + vec3 normal = glm::normalize(left + right); + vec3 topLeft = vec3(left.x, 0.5f, left.z); + vec3 topRight = vec3(right.x, 0.5f, right.z); + vec3 bottomLeft = vec3(left.x, -0.5f, left.z); + vec3 bottomRight = vec3(right.x, -0.5f, right.z); + + vertices.push_back(topLeft); + vertices.push_back(normal); + vertices.push_back(bottomLeft); + vertices.push_back(normal); + vertices.push_back(topRight); + vertices.push_back(normal); + vertices.push_back(bottomRight); + vertices.push_back(normal); + + solidIndices.push_back(baseVertex + 0); + solidIndices.push_back(baseVertex + 2); + solidIndices.push_back(baseVertex + 1); + solidIndices.push_back(baseVertex + 1); + solidIndices.push_back(baseVertex + 2); + solidIndices.push_back(baseVertex + 3); + wireIndices.push_back(baseVertex + 0); + wireIndices.push_back(baseVertex + 1); + wireIndices.push_back(baseVertex + 3); + wireIndices.push_back(baseVertex + 2); + baseVertex += 4; + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements @@ -285,10 +366,13 @@ void GeometryCache::buildShapes() { // Not implememented yet: //Triangle, + extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); + //Hexagon, + extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); + //Octagon, + extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); //Quad, //Circle, - //Octahetron, - //Dodecahedron, //Torus, //Cone, //Cylinder, @@ -1757,6 +1841,11 @@ void GeometryCache::renderSolidShapeInstance(gpu::Batch& batch, GeometryCache::S renderInstances(batch, color, false, pipeline, shape); } +void GeometryCache::renderWireShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + renderInstances(batch, color, true, pipeline, shape); +} + + void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { renderInstances(batch, color, false, pipeline, GeometryCache::Sphere); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 385f2c6fa4..bab0942672 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -132,6 +132,8 @@ public: Line, Triangle, Quad, + Hexagon, + Octagon, Circle, Cube, Sphere, @@ -139,10 +141,9 @@ public: Octahedron, Dodecahedron, Icosahedron, - Torus, - Cone, - Cylinder, - + Torus, // not yet implemented + Cone, // not yet implemented + Cylinder, // not yet implemented NUM_SHAPES, }; @@ -170,6 +171,13 @@ public: renderSolidShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline); } + void renderWireShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1), + const render::ShapePipelinePointer& pipeline = _simplePipeline); + void renderWireShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec3& color, + const render::ShapePipelinePointer& pipeline = _simplePipeline) { + renderWireShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline); + } + void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simplePipeline); void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec3& color, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 54c79b1d9f..f2ade39144 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1354,6 +1354,9 @@ + + +
From b03686c37f53c8362595cb9b65fe75bc2890d4b9 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 15 Jul 2016 22:02:56 -0700 Subject: [PATCH 21/21] Change the default domain attenuation level to 0.5 --- domain-server/resources/describe-settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 948c6ddc18..e1334ee46f 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -589,8 +589,8 @@ "name": "attenuation_per_doubling_in_distance", "label": "Default Domain Attenuation", "help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)", - "placeholder": "0.18", - "default": "0.18", + "placeholder": "0.5", + "default": "0.5", "advanced": false }, { @@ -686,7 +686,7 @@ "name": "coefficient", "label": "Attenuation coefficient", "can_set": true, - "placeholder": "0.18" + "placeholder": "0.5" } ] },