From 84e11db772c30cc3f926619067127639187a0953 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 29 Jun 2017 16:58:56 +1200 Subject: [PATCH 001/504] VR edit app script and button --- scripts/vr-edit/vr-edit.js | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/vr-edit/vr-edit.js diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js new file mode 100644 index 0000000000..3a81495708 --- /dev/null +++ b/scripts/vr-edit/vr-edit.js @@ -0,0 +1,61 @@ +"use strict"; + +// +// vr-edit.js +// +// Created by David Rowe on 27 Jun 2017. +// Copyright 2017 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 +// + +(function () { + + var APP_NAME = "VR EDIT", // TODO: App name. + APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons. + APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", + tablet, + button, + isAppActive = false; + + function onButtonClicked() { + isAppActive = !isAppActive; + button.editProperties({ isActive: isAppActive }); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet/toolbar button. + button = tablet.addButton({ + icon: APP_ICON_INACTIVE, + activeIcon: APP_ICON_ACTIVE, + text: APP_NAME, + isActive: isAppActive + }); + if (button) { + button.clicked.connect(onButtonClicked); + } + } + + function tearDown() { + if (!tablet) { + return; + } + + if (button) { + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + button = null; + } + + tablet = null; + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); From a863e37eb5a1482b9383d861ed9a4ce9a9ce71c3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 30 Jun 2017 12:11:06 +1200 Subject: [PATCH 002/504] Communicate VR edit enabled state to hand controller script --- scripts/system/controllers/handControllerGrab.js | 2 +- scripts/system/edit.js | 2 +- scripts/system/libraries/utils.js | 10 +++++++--- scripts/vr-edit/vr-edit.js | 8 +++++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 04921fe14d..77042e1ac6 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -14,7 +14,7 @@ /* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, - setGrabCommunications, Menu, HMD, isInEditMode, AvatarList */ + setGrabCommunications, Menu, HMD, isInEditMode, isInVREditMode, AvatarList */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a83d2159bb..4dc91ac024 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -228,7 +228,7 @@ var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; var toolBar = (function () { - var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var that = {}, toolBar, activeButton = null, diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index a5e97d8949..0f367e0cfe 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -6,12 +6,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// note: this constant is currently duplicated in edit.js -EDIT_SETTING = "io.highfidelity.isEditting"; -isInEditMode = function isInEditMode() { +EDIT_SETTING = "io.highfidelity.isEditing"; // Note: This constant is duplicated in edit.js. +isInEditMode = function () { return Settings.getValue(EDIT_SETTING); }; +VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in vr-edit.js. +isInVREditMode = function () { + return HMD.active && Settings.getValue(VR_EDIT_SETTING); +} + if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3a81495708..6ceb4ff7ac 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -17,14 +17,20 @@ APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", tablet, button, - isAppActive = false; + isAppActive = false, + + VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in utils.js. + function onButtonClicked() { isAppActive = !isAppActive; + Settings.setValue(VR_EDIT_SETTING, isAppActive); button.editProperties({ isActive: isAppActive }); } function setUp() { + Settings.setValue(VR_EDIT_SETTING, isAppActive); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!tablet) { return; From 83a78aa4074c631d23a02c592d95d329e0a261c8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Jul 2017 15:54:33 +1200 Subject: [PATCH 003/504] No handControllerGrab.js lasers while in VR edit mode --- scripts/system/controllers/handControllerGrab.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 77042e1ac6..cbed0b1007 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -537,7 +537,7 @@ function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, off var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; function isEditing() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && isInEditMode(); + return EXTERNALLY_MANAGED_2D_MINOR_MODE && (isInEditMode() || isInVREditMode()); } function isIn2DMode() { @@ -1220,7 +1220,7 @@ function MyController(hand) { }; this.setState = function(newState, reason) { - if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) && + if (((isInEditMode() || isInVREditMode()) && this.grabbedThingID !== HMD.tabletID) && (newState !== STATE_OFF && newState !== STATE_SEARCHING && newState !== STATE_STYLUS_TOUCHING && @@ -1799,8 +1799,9 @@ function MyController(hand) { this.processStylus(); - if (isInEditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { + if (isInEditMode() && !isInVREditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { // Always showing lasers while in edit mode and hands/stylus is not active. + // But don't show lasers while in VR edit mode. var rayPickInfo = this.calcRayPickInfo(this.hand); this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; this.searchIndicatorOn(rayPickInfo.searchRay); @@ -2263,7 +2264,7 @@ function MyController(hand) { return aDistance - bDistance; }); entity = grabbableEntities[0]; - if (!isInEditMode() || entity == HMD.tabletID) { // tablet is grabbable, even when editing + if ((!isInEditMode() && !isInVREditMode()) || entity == HMD.tabletID) { // tablet is grabbable, even when editing name = entityPropertiesCache.getProps(entity).name; this.grabbedThingID = entity; this.grabbedIsOverlay = false; @@ -2371,7 +2372,7 @@ function MyController(hand) { equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); } - if (farGrabEnabled && farSearching) { + if (farGrabEnabled && farSearching && !isInVREditMode()) { this.searchIndicatorOn(rayPickInfo.searchRay); } Reticle.setVisible(false); From cadd4685ee2fe899ed35b743a8f404a8411c5c2c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Jul 2017 15:59:04 +1200 Subject: [PATCH 004/504] No haptic pulse in edit modes when hand enters near-grab distance --- 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 cbed0b1007..be073742b0 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1786,7 +1786,7 @@ function MyController(hand) { var nonTabletEntities = grabbableEntities.filter(function(entityID) { return entityID != HMD.tabletID && entityID != HMD.homeButtonID; }); - if (nonTabletEntities.length > 0) { + if (nonTabletEntities.length > 0 && !isInEditMode() && !isInVREditMode()) { Controller.triggerHapticPulse(1, 20, this.hand); } this.grabPointIntersectsEntity = true; From a24a0265f99a015c12718b921434f9dd5c9a1c4e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Jul 2017 16:24:17 +1200 Subject: [PATCH 005/504] Improve communication with hand controller script --- scripts/vr-edit/vr-edit.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6ceb4ff7ac..14d350803e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -22,14 +22,19 @@ VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in utils.js. + function updateHandControllerGrab() { + // Communicate status to handControllerGrab.js. + Settings.setValue(VR_EDIT_SETTING, isAppActive); + } + function onButtonClicked() { isAppActive = !isAppActive; - Settings.setValue(VR_EDIT_SETTING, isAppActive); + updateHandControllerGrab(); button.editProperties({ isActive: isAppActive }); } function setUp() { - Settings.setValue(VR_EDIT_SETTING, isAppActive); + updateHandControllerGrab(); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!tablet) { @@ -49,6 +54,9 @@ } function tearDown() { + isAppActive = false; + updateHandControllerGrab(); + if (!tablet) { return; } From dcbf3ceeb9b7d31ab55a82847e10c55f8f1d52bd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 12:40:45 +1200 Subject: [PATCH 006/504] Update loop --- scripts/vr-edit/vr-edit.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 14d350803e..5616fd12b5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -21,6 +21,15 @@ VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in utils.js. + UPDATE_LOOP_TIMEOUT = 16, + updateTimer = null; + + function update() { + // Main update loop. + updateTimer = null; + + updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); + } function updateHandControllerGrab() { // Communicate status to handControllerGrab.js. @@ -31,6 +40,13 @@ isAppActive = !isAppActive; updateHandControllerGrab(); button.editProperties({ isActive: isAppActive }); + + if (isAppActive) { + update(); + } else { + Script.clearTimeout(updateTimer); + updateTimer = null; + } } function setUp() { @@ -51,9 +67,17 @@ if (button) { button.clicked.connect(onButtonClicked); } + + if (isAppActive) { + update(); + } } function tearDown() { + if (updateTimer) { + Script.clearTimeout(updateTimer); + } + isAppActive = false; updateHandControllerGrab(); From bf722f084efd06dfc409d29c7290a0bc6facd911 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 12:49:36 +1200 Subject: [PATCH 007/504] A controller object for each hand --- scripts/vr-edit/vr-edit.js | 114 ++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 5616fd12b5..d474974982 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -21,13 +21,112 @@ VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in utils.js. + hands = [], + LEFT_HAND = 0, + RIGHT_HAND = 1, + UPDATE_LOOP_TIMEOUT = 16, - updateTimer = null; + updateTimer = null, + + Hand; + + Hand = function (side) { + var hand, + handController, + controllerTrigger, + controllerTriggerClicked, + + TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. + TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + + PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. + PRECISION_PICKING = true, + NO_INCLUDE_IDS = [], + NO_EXCLUDE_IDS = [], + VISIBLE_ONLY = true, + + isLaserOn = false; + + hand = side; + if (hand === LEFT_HAND) { + handController = Controller.Standard.LeftHand; + controllerTrigger = Controller.Standard.LT; + controllerTriggerClicked = Controller.Standard.LTClick; + GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; + } else { + handController = Controller.Standard.RightHand; + controllerTrigger = Controller.Standard.RT; + controllerTriggerClicked = Controller.Standard.RTClick; + } + + function update() { + var wasLaserOn, + handPose, + handPosition, + handOrientation, + deltaOrigin, + pickRay, + intersection, + distance; + + // Controller trigger. + wasLaserOn = isLaserOn; + isLaserOn = Controller.getValue(controllerTrigger) > (isLaserOn ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); + if (!isLaserOn) { + if (wasLaserOn) { + // Clear laser + } + return; + } + + // Hand position and orientation. + handPose = Controller.getPoseValue(handController); + if (!handPose.valid) { + isLaserOn = false; + if (wasLaserOn) { + // Clear laser + } + return; + } + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + + // Entity intersection, if any. + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ... + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + distance = intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE; + + // Update laser. + } + + function destroy() { + } + + if (!this instanceof Hand) { + return new Hand(); + } + + return { + update: update, + destroy: destroy + }; + }; + function update() { // Main update loop. updateTimer = null; + hands[LEFT_HAND].update(); + hands[RIGHT_HAND].update(); + updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); } @@ -68,6 +167,10 @@ button.clicked.connect(onButtonClicked); } + // Hands, each with a laser, selection, etc. + hands[LEFT_HAND] = new Hand(LEFT_HAND); + hands[RIGHT_HAND] = new Hand(RIGHT_HAND); + if (isAppActive) { update(); } @@ -91,6 +194,15 @@ button = null; } + if (hands[LEFT_HAND]) { + hands[LEFT_HAND].destroy(); + hands[LEFT_HAND] = null; + } + if (hands[RIGHT_HAND]) { + hands[RIGHT_HAND].destroy(); + hands[RIGHT_HAND] = null; + } + tablet = null; } From f9ee21525d3c88333134326ea0e54f8f240bef2c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 12:53:54 +1200 Subject: [PATCH 008/504] A laser for each hand --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 1 + scripts/vr-edit/vr-edit.js | 127 +++++++++++++++++- 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index ea91890f33..eb2306154e 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -410,6 +410,7 @@ void HmdDisplayPlugin::updateFrameData() { vec3 castDirection = glm::quat_cast(model) * laserDirection; // this offset needs to match GRAB_POINT_SPHERE_OFFSET in scripts/system/libraries/controllers.js:19 + // and in vr-edit.js static const vec3 GRAB_POINT_SPHERE_OFFSET(0.04f, 0.13f, 0.039f); // x = upward, y = forward, z = lateral // swizzle grab point so that (x = upward, y = lateral, z = forward) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d474974982..ff2f070984 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -19,7 +19,7 @@ button, isAppActive = false, - VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in utils.js. + VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. hands = [], LEFT_HAND = 0, @@ -28,7 +28,115 @@ UPDATE_LOOP_TIMEOUT = 16, updateTimer = null, - Hand; + Hand, + Laser, + + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + + Laser = function (side) { + // May intersect with entities or bounding box of other hand's selection. + + var hand, + laserLine = null, + laserSphere = null, + + searchDistance = 0.0, + + SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. + SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; // Per handControllgerGrab.js. + + hand = side; + laserLine = Overlays.addOverlay("line3d", { + lineWidth: 5, + alpha: 1.0, + glow: 1.0, + ignoreRayIntersection: true, + drawInFront: true, + parentID: AVATAR_SELF_ID, + parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND + ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" + : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), + visible: false + }); + laserSphere = Overlays.addOverlay("circle3d", { + innerAlpha: 1.0, + outerAlpha: 0.0, + solid: true, + ignoreRayIntersection: true, + drawInFront: true, + visible: false + }); + + function colorPow(color, power) { + return { + red: Math.pow(color.red / 255, power) * 255, + green: Math.pow(color.green / 255, power) * 255, + blue: Math.pow(color.blue / 255, power) * 255 + }; + } + + function updateLine(start, end, color) { + Overlays.editOverlay(laserLine, { + start: start, + end: end, + color: color, + visible: true + }); + } + + function updateSphere(location, size, color) { + var rotation, + brightColor; + + rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + brightColor = colorPow(color, 0.06); + + Overlays.editOverlay(laserSphere, { + position: location, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + outerRadius: size, + visible: true + }); + } + + function update(origin, direction, distance, isClicked) { + var searchTarget, + sphereSize, + color; + + searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; + searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); + sphereSize = SEARCH_SPHERE_SIZE * searchDistance; + color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; + + updateLine(origin, searchTarget, color); + updateSphere(searchTarget, sphereSize, color); + } + + function clear() { + Overlays.editOverlay(laserLine, { visible: false }); + Overlays.editOverlay(laserSphere, { visible: false }); + } + + function destroy() { + Overlays.deleteOverlay(laserLine); + Overlays.deleteOverlay(laserSphere); + } + + if (!this instanceof Laser) { + return new Laser(); + } + + return { + update: update, + clear: clear, + destroy: destroy + }; + }; Hand = function (side) { var hand, @@ -46,7 +154,9 @@ NO_EXCLUDE_IDS = [], VISIBLE_ONLY = true, - isLaserOn = false; + isLaserOn = false, + + laser; hand = side; if (hand === LEFT_HAND) { @@ -60,6 +170,8 @@ controllerTriggerClicked = Controller.Standard.RTClick; } + laser = new Laser(hand); + function update() { var wasLaserOn, handPose, @@ -75,7 +187,7 @@ isLaserOn = Controller.getValue(controllerTrigger) > (isLaserOn ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); if (!isLaserOn) { if (wasLaserOn) { - // Clear laser + laser.clear(); } return; } @@ -85,7 +197,7 @@ if (!handPose.valid) { isLaserOn = false; if (wasLaserOn) { - // Clear laser + laser.clear(); } return; } @@ -104,9 +216,14 @@ distance = intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE; // Update laser. + laser.update(pickRay.origin, pickRay.direction, distance, Controller.getValue(controllerTriggerClicked)); } function destroy() { + if (laser) { + laser.destroy(); + laser = null; + } } if (!this instanceof Hand) { From dd1116092e82a2bd5e0aa2ecb3f1de41a1ce7f10 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 14:20:29 +1200 Subject: [PATCH 009/504] Collect entities in parent-child tree that hovered entity belongs to --- scripts/vr-edit/vr-edit.js | 137 +++++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index ff2f070984..65472d33f6 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -28,12 +28,114 @@ UPDATE_LOOP_TIMEOUT = 16, updateTimer = null, - Hand, + Selection, Laser, + Hand, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + + Selection = function () { + // Manages set of selected entities. Currently supports just one set of linked entities. + + var selection = [], + selectedEntityID = null, + selectionPosition = null, + selectionOrientation, + rootEntityID; + + function traverseEntityTree(id, result) { + // Recursively traverses tree of entities and their children, gather IDs and properties. + var children, + properties, + SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions"], + i, + length; + + properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES); + result.push({ + id: id, + position: properties.position, + registrationPoint: properties.registrationPoint, + rotation: properties.rotation, + dimensions: properties.dimensions + }); + + children = Entities.getChildrenIDs(id); + for (i = 0, length = children.length; i < length; i += 1) { + traverseEntityTree(children[i], result); + } + } + + function select(entityID) { + var entityProperties, + PARENT_PROPERTIES = ["parentID", "position", "rotation"]; + + // Find root parent. + rootEntityID = entityID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + while (entityProperties.parentID !== NULL_UUID) { + rootEntityID = entityProperties.parentID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + } + + // Selection position and orientation is that of the root entity. + selectionPosition = entityProperties.position; + selectionOrientation = entityProperties.rotation; + + // Find all children. + selection = []; + traverseEntityTree(rootEntityID, selection); + + selectedEntityID = entityID; + } + + function getSelection() { + return selection; + } + + function getPositionAndOrientation() { + return { + position: selectionPosition, + orientation: selectionOrientation + }; + } + + function setPositionAndOrientation(position, orientation) { + selectionPosition = position; + selectionOrientation = orientation; + Entities.editEntity(rootEntityID, { + position: position, + rotation: orientation + }); + } + + function clear() { + selection = []; + selectedEntityID = null; + rootEntityID = null; + } + + function destroy() { + clear(); + } + + if (!this instanceof Selection) { + return new Selection(); + } + + return { + select: select, + selection: getSelection, + getPositionAndOrientation: getPositionAndOrientation, + setPositionAndOrientation: setPositionAndOrientation, + clear: clear, + destroy: destroy + }; + }; Laser = function (side) { + // Draws hand lasers. // May intersect with entities or bounding box of other hand's selection. var hand, @@ -139,6 +241,9 @@ }; Hand = function (side) { + // Hand controller input. + // Each hand has a laser, an entity selection, and entity highlighter. + var hand, handController, controllerTrigger, @@ -155,8 +260,10 @@ VISIBLE_ONLY = true, isLaserOn = false, + hoveredEntityID = null, - laser; + laser, + selection; hand = side; if (hand === LEFT_HAND) { @@ -171,6 +278,7 @@ } laser = new Laser(hand); + selection = new Selection(); function update() { var wasLaserOn, @@ -188,6 +296,8 @@ if (!isLaserOn) { if (wasLaserOn) { laser.clear(); + selection.clear(); + hoveredEntityID = null; } return; } @@ -198,6 +308,8 @@ isLaserOn = false; if (wasLaserOn) { laser.clear(); + selection.clear(); + hoveredEntityID = null; } return; } @@ -215,8 +327,19 @@ VISIBLE_ONLY); distance = intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE; - // Update laser. + // Hover entities. laser.update(pickRay.origin, pickRay.direction, distance, Controller.getValue(controllerTriggerClicked)); + if (intersection.intersects) { + if (intersection.entityID !== hoveredEntityID) { + hoveredEntityID = intersection.entityID; + selection.select(hoveredEntityID); + } + } else { + if (hoveredEntityID) { + selection.clear(); + hoveredEntityID = null; + } + } } function destroy() { @@ -224,6 +347,10 @@ laser.destroy(); laser = null; } + if (selection) { + selection.destroy(); + selection = null; + } } if (!this instanceof Hand) { @@ -248,7 +375,7 @@ } function updateHandControllerGrab() { - // Communicate status to handControllerGrab.js. + // Communicate app status to handControllerGrab.js. Settings.setValue(VR_EDIT_SETTING, isAppActive); } From c85df15badf0a432c746dca4e0acb61a4fd7dd71 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 14:21:47 +1200 Subject: [PATCH 010/504] Display bounding boxes as highlights when hovering --- scripts/vr-edit/vr-edit.js | 85 +++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 65472d33f6..063734d99a 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -28,6 +28,7 @@ UPDATE_LOOP_TIMEOUT = 16, updateTimer = null, + Highlights, Selection, Laser, Hand, @@ -35,6 +36,78 @@ AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + Highlights = function () { + // Draws highlights on selected entities. + + var overlays = [], + HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + HIGHLIGHT_ALPHA = 0.8; + + function maybeAddOverlay(index) { + if (index >= overlays.length) { + overlays.push(Overlays.addOverlay("cube", { + color: HIGHLIGHT_COLOR, + alpha: HIGHLIGHT_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + })); + } + } + + function editOverlay(index, details) { + Overlays.editOverlay(overlays[index], { + position: details.position, + registrationPoint: details.registrationPoint, + rotation: details.rotation, + dimensions: details.dimensions, + visible: true + }); + } + + function display(selection) { + var i, + length; + + // Add/edit overlay. + for (i = 0, length = selection.length; i < length; i += 1) { + maybeAddOverlay(i); + editOverlay(i, selection[i]); + } + + // Delete extra overlays. + for (i = overlays.length - 1, length = selection.length; i >= length; i -= 1) { + Overlays.deleteOverlay(overlays[i]); + overlays.splice(i, 1); + } + } + + function clear() { + var i, + length; + + for (i = 0, length = overlays.length; i < length; i += 1) { + Overlays.deleteOverlay(overlays[i]); + } + overlays = []; + } + + function destroy() { + clear(); + } + + if (!this instanceof Highlights) { + return new Highlights(); + } + + return { + display: display, + clear: clear, + destroy: destroy + }; + }; + Selection = function () { // Manages set of selected entities. Currently supports just one set of linked entities. @@ -263,7 +336,8 @@ hoveredEntityID = null, laser, - selection; + selection, + highlights; hand = side; if (hand === LEFT_HAND) { @@ -279,6 +353,7 @@ laser = new Laser(hand); selection = new Selection(); + highlights = new Highlights(); function update() { var wasLaserOn, @@ -297,6 +372,7 @@ if (wasLaserOn) { laser.clear(); selection.clear(); + highlights.clear(); hoveredEntityID = null; } return; @@ -309,6 +385,7 @@ if (wasLaserOn) { laser.clear(); selection.clear(); + highlights.clear(); hoveredEntityID = null; } return; @@ -333,10 +410,12 @@ if (intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; selection.select(hoveredEntityID); + highlights.display(selection.selection()); } } else { if (hoveredEntityID) { selection.clear(); + highlights.clear(); hoveredEntityID = null; } } @@ -351,6 +430,10 @@ selection.destroy(); selection = null; } + if (highlights) { + highlights.destroy(); + highlights = null; + } } if (!this instanceof Hand) { From 75bc0f8b3f820be6a2c1a67c5a5b0bbe46a3c3f0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 15:47:08 +1200 Subject: [PATCH 011/504] Translate and rotate selected entities --- scripts/vr-edit/vr-edit.js | 98 ++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 063734d99a..8cea7ed614 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -333,7 +333,13 @@ VISIBLE_ONLY = true, isLaserOn = false, - hoveredEntityID = null, + laseredEntityID = null, + + isEditing = false, + initialHandPosition, + initialHandOrientationInverse, + initialHandToSelectionVector, + initialSelectionOrientation, laser, selection, @@ -355,6 +361,40 @@ selection = new Selection(); highlights = new Highlights(); + function startEditing(handPose) { + var selectionPositionAndOrientation; + + initialHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation)); + + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, initialHandPosition); + initialSelectionOrientation = selectionPositionAndOrientation.orientation; + + isEditing = true; + } + + function applyEdit(handPose) { + var handPosition, + handOrientation, + deltaOrientation, + selectionPosition, + selectionOrientation; + + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + + deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); + selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); + selectionOrientation = Quat.multiply(deltaOrientation, initialSelectionOrientation); + + selection.setPositionAndOrientation(selectionPosition, selectionOrientation); + } + + function stopEditing() { + isEditing = false; + } + function update() { var wasLaserOn, handPose, @@ -363,7 +403,8 @@ deltaOrigin, pickRay, intersection, - distance; + distance, + isTriggerClicked; // Controller trigger. wasLaserOn = isLaserOn; @@ -373,7 +414,7 @@ laser.clear(); selection.clear(); highlights.clear(); - hoveredEntityID = null; + laseredEntityID = null; } return; } @@ -386,7 +427,7 @@ laser.clear(); selection.clear(); highlights.clear(); - hoveredEntityID = null; + laseredEntityID = null; } return; } @@ -404,20 +445,47 @@ VISIBLE_ONLY); distance = intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE; - // Hover entities. - laser.update(pickRay.origin, pickRay.direction, distance, Controller.getValue(controllerTriggerClicked)); - if (intersection.intersects) { - if (intersection.entityID !== hoveredEntityID) { - hoveredEntityID = intersection.entityID; - selection.select(hoveredEntityID); - highlights.display(selection.selection()); + // Laser, hover, edit. + isTriggerClicked = Controller.getValue(controllerTriggerClicked); + laser.update(pickRay.origin, pickRay.direction, distance, isTriggerClicked); + + if (isTriggerClicked) { + if (isEditing) { + // Perform edit. + applyEdit(handPose); + } else if (intersection.intersects) { + // Start editing. + if (intersection.entityID !== laseredEntityID) { + laseredEntityID = intersection.entityID; + selection.select(laseredEntityID); + } else { + highlights.clear(); + } + startEditing(handPose); } } else { - if (hoveredEntityID) { - selection.clear(); - highlights.clear(); - hoveredEntityID = null; + if (isEditing) { + // Stop editing. + stopEditing(); + laseredEntityID = null; // Force highlighting entities at their new position. } + + if (intersection.intersects) { + // Hover entities. + if (intersection.entityID !== laseredEntityID) { + laseredEntityID = intersection.entityID; + selection.select(laseredEntityID); + highlights.display(selection.selection()); + } + } else { + // Unhover entities. + if (laseredEntityID) { + selection.clear(); + highlights.clear(); + laseredEntityID = null; + } + } + } } From 1e76729d592daf3775ee58409da46fa6acca1bef Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 15:52:22 +1200 Subject: [PATCH 012/504] Lock laser distance while editing --- scripts/vr-edit/vr-edit.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 8cea7ed614..3022b0242c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -340,6 +340,7 @@ initialHandOrientationInverse, initialHandToSelectionVector, initialSelectionOrientation, + editingDistance, laser, selection, @@ -443,7 +444,7 @@ }; intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - distance = intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE; + distance = isEditing ? editingDistance : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); // Laser, hover, edit. isTriggerClicked = Controller.getValue(controllerTriggerClicked); @@ -461,6 +462,7 @@ } else { highlights.clear(); } + editingDistance = distance; startEditing(handPose); } } else { From 24b19632575b8c3e28776f85756181dcfb162ad1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 16:00:42 +1200 Subject: [PATCH 013/504] Tidying --- scripts/vr-edit/vr-edit.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3022b0242c..d00e82b16b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -396,6 +396,13 @@ isEditing = false; } + function clearLaser() { + laser.clear(); + selection.clear(); + highlights.clear(); + laseredEntityID = null; + } + function update() { var wasLaserOn, handPose, @@ -412,10 +419,7 @@ isLaserOn = Controller.getValue(controllerTrigger) > (isLaserOn ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); if (!isLaserOn) { if (wasLaserOn) { - laser.clear(); - selection.clear(); - highlights.clear(); - laseredEntityID = null; + clearLaser(); } return; } @@ -425,10 +429,7 @@ if (!handPose.valid) { isLaserOn = false; if (wasLaserOn) { - laser.clear(); - selection.clear(); - highlights.clear(); - laseredEntityID = null; + clearLaser(); } return; } @@ -449,7 +450,6 @@ // Laser, hover, edit. isTriggerClicked = Controller.getValue(controllerTriggerClicked); laser.update(pickRay.origin, pickRay.direction, distance, isTriggerClicked); - if (isTriggerClicked) { if (isEditing) { // Perform edit. @@ -487,7 +487,6 @@ laseredEntityID = null; } } - } } @@ -516,7 +515,6 @@ }; }; - function update() { // Main update loop. updateTimer = null; From e6e619f0d874db046d33f9272fbde68c6aa2ad8f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 22:36:18 +1200 Subject: [PATCH 014/504] Hover only editable entities --- scripts/vr-edit/vr-edit.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d00e82b16b..619a15924d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -335,6 +335,9 @@ isLaserOn = false, laseredEntityID = null, + EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], + NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], + isEditing = false, initialHandPosition, initialHandOrientationInverse, @@ -403,6 +406,15 @@ laseredEntityID = null; } + function isEditableEntity(entityID) { + // Entity trees are moved as a group so check the root entity. + var properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + while (properties.parentID && properties.parentID !== NULL_UUID) { + properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + } + return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + } + function update() { var wasLaserOn, handPose, @@ -446,6 +458,7 @@ intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); distance = isEditing ? editingDistance : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + intersection.intersects = isEditableEntity(intersection.entityID); // Laser, hover, edit. isTriggerClicked = Controller.getValue(controllerTriggerClicked); From 38e7ee309603541e06f5c7f546c74577b3d771e6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Jul 2017 22:38:59 +1200 Subject: [PATCH 015/504] Hover and select entities that hand intersects --- scripts/vr-edit/vr-edit.js | 140 +++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 619a15924d..70e55f4e02 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -34,7 +34,8 @@ Hand, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + NULL_UUID = "{00000000-0000-0000-0000-000000000000}", + HALF_TREE_SCALE = 16384; Highlights = function () { // Draws highlights on selected entities. @@ -326,14 +327,19 @@ TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. PRECISION_PICKING = true, NO_INCLUDE_IDS = [], NO_EXCLUDE_IDS = [], VISIBLE_ONLY = true, + isTriggerPressed = false, + + isHandHover = false, isLaserOn = false, - laseredEntityID = null, + hoveredEntityID = null, EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], @@ -343,7 +349,7 @@ initialHandOrientationInverse, initialHandToSelectionVector, initialSelectionOrientation, - editingDistance, + laserEditingDistance, laser, selection, @@ -416,88 +422,130 @@ } function update() { - var wasLaserOn, + var palmPosition, + entityID, + entityIDs, + entitySize, + size, + wasLaserOn, handPose, handPosition, handOrientation, deltaOrigin, pickRay, intersection, - distance, - isTriggerClicked; - - // Controller trigger. - wasLaserOn = isLaserOn; - isLaserOn = Controller.getValue(controllerTrigger) > (isLaserOn ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); - if (!isLaserOn) { - if (wasLaserOn) { - clearLaser(); - } - return; - } + laserLength, + isTriggerClicked, + wasEditing, + i, + length; // Hand position and orientation. handPose = Controller.getPoseValue(handController); if (!handPose.valid) { isLaserOn = false; if (wasLaserOn) { - clearLaser(); + laser.clear(); + selection.clear(); + highlights.clear(); + hoveredEntityID = null; } return; } - handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); - handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); - // Entity intersection, if any. - deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); - pickRay = { - origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ... - direction: Quat.getUp(handOrientation), - length: PICK_MAX_DISTANCE - }; - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - distance = isEditing ? editingDistance : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - intersection.intersects = isEditableEntity(intersection.entityID); - - // Laser, hover, edit. + // Controller trigger. + isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed + ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); isTriggerClicked = Controller.getValue(controllerTriggerClicked); - laser.update(pickRay.origin, pickRay.direction, distance, isTriggerClicked); + + // Hand-entity intersection, if any. + entityID = null; + palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); + if (entityIDs.length > 0) { + // Find smallest, editable entity. + entitySize = HALF_TREE_SCALE; + for (i = 0, length = entityIDs.length; i < length; i += 1) { + if (isEditableEntity(entityIDs[i])) { + size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); + if (size < entitySize) { + entityID = entityIDs[i]; + entitySize = size; + } + } + } + } + intersection = { + intersects: entityID !== null, + entityID: entityID + }; + isHandHover = intersection.intersects; + + // Laser-entity intersection, if any. + wasLaserOn = isLaserOn; + if (!isHandHover && isTriggerPressed) { + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ... + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + laserLength = isEditing + ? laserEditingDistance + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + intersection.intersects = isEditableEntity(intersection.entityID); + isLaserOn = true; + } else { + isLaserOn = false; + } + + // Laser update. + if (isLaserOn) { + laser.update(pickRay.origin, pickRay.direction, laserLength, isTriggerClicked); + } else if (wasLaserOn) { + laser.clear(); + } + + // Highlight / edit. if (isTriggerClicked) { if (isEditing) { // Perform edit. applyEdit(handPose); } else if (intersection.intersects) { // Start editing. - if (intersection.entityID !== laseredEntityID) { - laseredEntityID = intersection.entityID; - selection.select(laseredEntityID); - } else { - highlights.clear(); + if (intersection.entityID !== hoveredEntityID) { + hoveredEntityID = intersection.entityID; + selection.select(hoveredEntityID); + } + highlights.clear(); + if (isLaserOn) { + laserEditingDistance = laserLength; } - editingDistance = distance; startEditing(handPose); } } else { + wasEditing = isEditing; if (isEditing) { // Stop editing. stopEditing(); - laseredEntityID = null; // Force highlighting entities at their new position. } - if (intersection.intersects) { // Hover entities. - if (intersection.entityID !== laseredEntityID) { - laseredEntityID = intersection.entityID; - selection.select(laseredEntityID); + if (wasEditing || intersection.entityID !== hoveredEntityID) { + hoveredEntityID = intersection.entityID; + selection.select(hoveredEntityID); highlights.display(selection.selection()); } } else { // Unhover entities. - if (laseredEntityID) { + if (hoveredEntityID) { selection.clear(); highlights.clear(); - laseredEntityID = null; + hoveredEntityID = null; } } } From 72529cf90ba3be1d13a7b037e155b12112d29bbe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Jul 2017 10:49:17 +1200 Subject: [PATCH 016/504] Highlight hand when it intersects an entity --- scripts/vr-edit/vr-edit.js | 85 +++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 70e55f4e02..b3f0370c92 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -37,18 +37,37 @@ NULL_UUID = "{00000000-0000-0000-0000-000000000000}", HALF_TREE_SCALE = 16384; - Highlights = function () { + Highlights = function (hand) { // Draws highlights on selected entities. - var overlays = [], + var handOverlay, + entityOverlays = [], HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, - HIGHLIGHT_ALPHA = 0.8; + HAND_HIGHLIGHT_ALPHA = 0.35, + ENTITY_HIGHLIGHT_ALPHA = 0.8, + HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, + HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }; - function maybeAddOverlay(index) { - if (index >= overlays.length) { - overlays.push(Overlays.addOverlay("cube", { + handOverlay = Overlays.addOverlay("sphere", { + dimensions: HAND_HIGHLIGHT_DIMENSIONS, + parentID: AVATAR_SELF_ID, + parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND + ? "_CONTROLLER_LEFTHAND" + : "_CONTROLLER_RIGHTHAND"), + localPosition: HAND_HIGHLIGHT_OFFSET, + color: HIGHLIGHT_COLOR, + alpha: HAND_HIGHLIGHT_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + function maybeAddEntityOverlay(index) { + if (index >= entityOverlays.length) { + entityOverlays.push(Overlays.addOverlay("cube", { color: HIGHLIGHT_COLOR, - alpha: HIGHLIGHT_ALPHA, + alpha: ENTITY_HIGHLIGHT_ALPHA, solid: false, drawInFront: true, ignoreRayIntersection: true, @@ -57,8 +76,8 @@ } } - function editOverlay(index, details) { - Overlays.editOverlay(overlays[index], { + function editEntityOverlay(index, details) { + Overlays.editOverlay(entityOverlays[index], { position: details.position, registrationPoint: details.registrationPoint, rotation: details.rotation, @@ -67,20 +86,23 @@ }); } - function display(selection) { + function display(handSelected, selection) { var i, length; - // Add/edit overlay. + // Show/hide hand overlay. + Overlays.editOverlay(handOverlay, { visible: handSelected }); + + // Add/edit entity overlay. for (i = 0, length = selection.length; i < length; i += 1) { - maybeAddOverlay(i); - editOverlay(i, selection[i]); + maybeAddEntityOverlay(i); + editEntityOverlay(i, selection[i]); } - // Delete extra overlays. - for (i = overlays.length - 1, length = selection.length; i >= length; i -= 1) { - Overlays.deleteOverlay(overlays[i]); - overlays.splice(i, 1); + // Delete extra entity overlays. + for (i = entityOverlays.length - 1, length = selection.length; i >= length; i -= 1) { + Overlays.deleteOverlay(entityOverlays[i]); + entityOverlays.splice(i, 1); } } @@ -88,14 +110,19 @@ var i, length; - for (i = 0, length = overlays.length; i < length; i += 1) { - Overlays.deleteOverlay(overlays[i]); + // Hide hand overlay. + Overlays.editOverlay(handOverlay, { visible: false }); + + // Delete entity overlays. + for (i = 0, length = entityOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(entityOverlays[i]); } - overlays = []; + entityOverlays = []; } function destroy() { clear(); + Overlays.deleteOverlay(handOverlay); } if (!this instanceof Highlights) { @@ -337,7 +364,6 @@ isTriggerPressed = false, - isHandHover = false, isLaserOn = false, hoveredEntityID = null, @@ -369,7 +395,7 @@ laser = new Laser(hand); selection = new Selection(); - highlights = new Highlights(); + highlights = new Highlights(hand); function startEditing(handPose) { var selectionPositionAndOrientation; @@ -405,13 +431,6 @@ isEditing = false; } - function clearLaser() { - laser.clear(); - selection.clear(); - highlights.clear(); - laseredEntityID = null; - } - function isEditableEntity(entityID) { // Entity trees are moved as a group so check the root entity. var properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); @@ -477,13 +496,13 @@ } intersection = { intersects: entityID !== null, - entityID: entityID + entityID: entityID, + handSelected: true }; - isHandHover = intersection.intersects; // Laser-entity intersection, if any. wasLaserOn = isLaserOn; - if (!isHandHover && isTriggerPressed) { + if (!intersection.intersects && isTriggerPressed) { handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); @@ -538,7 +557,7 @@ if (wasEditing || intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; selection.select(hoveredEntityID); - highlights.display(selection.selection()); + highlights.display(intersection.handSelected, selection.selection()); } } else { // Unhover entities. From 3e96df5436acc378984b1377b5fa54b609ff76f0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Jul 2017 16:24:15 +1200 Subject: [PATCH 017/504] Precalculate laser sphere bright color --- scripts/vr-edit/vr-edit.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index b3f0370c92..056c46e21d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -248,7 +248,21 @@ SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. - COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, + BRIGHT_POW = 0.06; // Per handControllgerGrab.js. + + function colorPow(color, power) { // Per handControllerGrab.js. + return { + red: Math.pow(color.red / 255, power) * 255, + green: Math.pow(color.green / 255, power) * 255, + blue: Math.pow(color.blue / 255, power) * 255 + }; + } + + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); hand = side; laserLine = Overlays.addOverlay("line3d", { @@ -272,14 +286,6 @@ visible: false }); - function colorPow(color, power) { - return { - red: Math.pow(color.red / 255, power) * 255, - green: Math.pow(color.green / 255, power) * 255, - blue: Math.pow(color.blue / 255, power) * 255 - }; - } - function updateLine(start, end, color) { Overlays.editOverlay(laserLine, { start: start, @@ -289,12 +295,10 @@ }); } - function updateSphere(location, size, color) { - var rotation, - brightColor; + function updateSphere(location, size, color, brightColor) { + var rotation; rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); - brightColor = colorPow(color, 0.06); Overlays.editOverlay(laserSphere, { position: location, @@ -309,15 +313,17 @@ function update(origin, direction, distance, isClicked) { var searchTarget, sphereSize, - color; + color, + brightColor; searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); sphereSize = SEARCH_SPHERE_SIZE * searchDistance; color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; + brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; updateLine(origin, searchTarget, color); - updateSphere(searchTarget, sphereSize, color); + updateSphere(searchTarget, sphereSize, color, brightColor); } function clear() { From b042f8615b8e96d7bda7ddab7fac2ada028fd096 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Jul 2017 16:36:22 +1200 Subject: [PATCH 018/504] Fix highlight position for entity with non-center registration point --- scripts/vr-edit/vr-edit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 056c46e21d..4ee6b5e9cc 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -77,8 +77,11 @@ } function editEntityOverlay(index, details) { + var offset = Vec3.multiplyQbyV(details.rotation, + Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions)); + Overlays.editOverlay(entityOverlays[index], { - position: details.position, + position: Vec3.sum(details.position, offset), registrationPoint: details.registrationPoint, rotation: details.rotation, dimensions: details.dimensions, From c2159bc52a67dafdf1c990f5f56891b5008f27c4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Jul 2017 16:44:58 +1200 Subject: [PATCH 019/504] Fix laser not disappearing when lose hand tracking --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4ee6b5e9cc..11733f3c0f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -470,6 +470,7 @@ // Hand position and orientation. handPose = Controller.getPoseValue(handController); + wasLaserOn = isLaserOn; if (!handPose.valid) { isLaserOn = false; if (wasLaserOn) { @@ -510,7 +511,6 @@ }; // Laser-entity intersection, if any. - wasLaserOn = isLaserOn; if (!intersection.intersects && isTriggerPressed) { handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); From cab2caaf276ea7babb625e1dce3e629ec01a43e9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Jul 2017 14:45:49 +1200 Subject: [PATCH 020/504] Apply edit and highlight after calculating both hands' inputs --- scripts/vr-edit/vr-edit.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 11733f3c0f..6c27cc9ca2 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -371,7 +371,8 @@ NO_EXCLUDE_IDS = [], VISIBLE_ONLY = true, - isTriggerPressed = false, + handPose, + intersection = {}, isLaserOn = false, hoveredEntityID = null, @@ -386,6 +387,9 @@ initialSelectionOrientation, laserEditingDistance, + doEdit, + doHighlight, + laser, selection, highlights; @@ -406,7 +410,7 @@ selection = new Selection(); highlights = new Highlights(hand); - function startEditing(handPose) { + function startEditing() { var selectionPositionAndOrientation; initialHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); @@ -419,7 +423,7 @@ isEditing = true; } - function applyEdit(handPose) { + function applyEdit() { var handPosition, handOrientation, deltaOrientation, @@ -456,13 +460,13 @@ entitySize, size, wasLaserOn, - handPose, handPosition, handOrientation, deltaOrigin, pickRay, intersection, laserLength, + isTriggerPressed, isTriggerClicked, wasEditing, i, @@ -539,10 +543,12 @@ } // Highlight / edit. + doEdit = false; + doHighlight = false; if (isTriggerClicked) { if (isEditing) { // Perform edit. - applyEdit(handPose); + doEdit = true; } else if (intersection.intersects) { // Start editing. if (intersection.entityID !== hoveredEntityID) { @@ -553,7 +559,7 @@ if (isLaserOn) { laserEditingDistance = laserLength; } - startEditing(handPose); + startEditing(); } } else { wasEditing = isEditing; @@ -566,7 +572,7 @@ if (wasEditing || intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; selection.select(hoveredEntityID); - highlights.display(intersection.handSelected, selection.selection()); + doHighlight = true; } } else { // Unhover entities. @@ -579,6 +585,14 @@ } } + function apply() { + if (doEdit) { + applyEdit(); + } else if (doHighlight) { + highlights.display(intersection.handSelected, selection.selection()); + } + } + function destroy() { if (laser) { laser.destroy(); @@ -600,6 +614,7 @@ return { update: update, + apply: apply, destroy: destroy }; }; @@ -610,6 +625,8 @@ hands[LEFT_HAND].update(); hands[RIGHT_HAND].update(); + hands[LEFT_HAND].apply(); + hands[RIGHT_HAND].apply(); updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); } From 0ba11ffdc93b78c432bab1723da27ae9bda216f7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Jul 2017 14:51:20 +1200 Subject: [PATCH 021/504] Make each hand aware of other --- scripts/vr-edit/vr-edit.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6c27cc9ca2..6b729df3af 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -390,6 +390,8 @@ doEdit, doHighlight, + otherHand, + laser, selection, highlights; @@ -410,6 +412,10 @@ selection = new Selection(); highlights = new Highlights(hand); + function setOtherhand(hand) { + otherHand = hand; + } + function startEditing() { var selectionPositionAndOrientation; @@ -613,6 +619,7 @@ } return { + setOtherHand: setOtherhand, update: update, apply: apply, destroy: destroy @@ -671,6 +678,8 @@ // Hands, each with a laser, selection, etc. hands[LEFT_HAND] = new Hand(LEFT_HAND); hands[RIGHT_HAND] = new Hand(RIGHT_HAND); + hands[LEFT_HAND].setOtherHand(hands[RIGHT_HAND]); + hands[RIGHT_HAND].setOtherHand(hands[LEFT_HAND]); if (isAppActive) { update(); From 998d27d66d5a4272f88f2ee02f9dc68d14698be8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Jul 2017 15:06:18 +1200 Subject: [PATCH 022/504] Logic and stub for grab versus scale --- scripts/vr-edit/vr-edit.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6b729df3af..76e5ac4f1e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -194,6 +194,10 @@ selectedEntityID = entityID; } + function getRootEntityID() { + return rootEntityID; + } + function getSelection() { return selection; } @@ -231,6 +235,7 @@ return { select: select, selection: getSelection, + rootEntityID: getRootEntityID, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, clear: clear, @@ -412,10 +417,14 @@ selection = new Selection(); highlights = new Highlights(hand); - function setOtherhand(hand) { + function setOtherHand(hand) { otherHand = hand; } + function getIsEditing(rootEntityID) { + return isEditing && rootEntityID === selection.rootEntityID(); + } + function startEditing() { var selectionPositionAndOrientation; @@ -429,7 +438,7 @@ isEditing = true; } - function applyEdit() { + function applyGrab() { var handPosition, handOrientation, deltaOrientation, @@ -446,6 +455,10 @@ selection.setPositionAndOrientation(selectionPosition, selectionOrientation); } + function applyScale() { + // TODO + } + function stopEditing() { isEditing = false; } @@ -593,7 +606,13 @@ function apply() { if (doEdit) { - applyEdit(); + if (otherHand.isEditing(selection.rootEntityID())) { + if (hand === LEFT_HAND) { + applyScale(); + } + } else { + applyGrab(); + } } else if (doHighlight) { highlights.display(intersection.handSelected, selection.selection()); } @@ -619,7 +638,8 @@ } return { - setOtherHand: setOtherhand, + setOtherHand: setOtherHand, + isEditing: getIsEditing, update: update, apply: apply, destroy: destroy From 5da8fe19deb4db2e7d74465d95b8a75a61897e73 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Jul 2017 15:10:07 +1200 Subject: [PATCH 023/504] Fix highlights not moving when entities being moved by other hand --- scripts/vr-edit/vr-edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 76e5ac4f1e..616b91334d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -81,6 +81,7 @@ Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions)); Overlays.editOverlay(entityOverlays[index], { + parentID: details.id, position: Vec3.sum(details.position, offset), registrationPoint: details.registrationPoint, rotation: details.rotation, From a35d5fe128665f0664855397e0eba591d3a43410 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Jul 2017 15:49:52 +1200 Subject: [PATCH 024/504] Different color highlight for about-to-scale --- scripts/vr-edit/vr-edit.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 616b91334d..668fceb015 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -42,7 +42,8 @@ var handOverlay, entityOverlays = [], - HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + GRAB_HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + SCALE_HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, @@ -55,7 +56,6 @@ ? "_CONTROLLER_LEFTHAND" : "_CONTROLLER_RIGHTHAND"), localPosition: HAND_HIGHLIGHT_OFFSET, - color: HIGHLIGHT_COLOR, alpha: HAND_HIGHLIGHT_ALPHA, solid: true, drawInFront: true, @@ -66,7 +66,6 @@ function maybeAddEntityOverlay(index) { if (index >= entityOverlays.length) { entityOverlays.push(Overlays.addOverlay("cube", { - color: HIGHLIGHT_COLOR, alpha: ENTITY_HIGHLIGHT_ALPHA, solid: false, drawInFront: true, @@ -76,7 +75,7 @@ } } - function editEntityOverlay(index, details) { + function editEntityOverlay(index, details, overlayColor) { var offset = Vec3.multiplyQbyV(details.rotation, Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions)); @@ -86,21 +85,26 @@ registrationPoint: details.registrationPoint, rotation: details.rotation, dimensions: details.dimensions, + color: overlayColor, visible: true }); } - function display(handSelected, selection) { - var i, + function display(handSelected, selection, isScale) { + var overlayColor = isScale ? SCALE_HIGHLIGHT_COLOR : GRAB_HIGHLIGHT_COLOR, + i, length; // Show/hide hand overlay. - Overlays.editOverlay(handOverlay, { visible: handSelected }); + Overlays.editOverlay(handOverlay, { + color: overlayColor, + visible: handSelected + }); // Add/edit entity overlay. for (i = 0, length = selection.length; i < length; i += 1) { maybeAddEntityOverlay(i); - editEntityOverlay(i, selection[i]); + editEntityOverlay(i, selection[i], overlayColor); } // Delete extra entity overlays. @@ -397,6 +401,7 @@ doHighlight, otherHand, + otherHandWasEditing, laser, selection, @@ -484,11 +489,11 @@ handOrientation, deltaOrigin, pickRay, - intersection, laserLength, isTriggerPressed, isTriggerClicked, wasEditing, + otherHandIsEditing, i, length; @@ -589,9 +594,11 @@ } if (intersection.intersects) { // Hover entities. - if (wasEditing || intersection.entityID !== hoveredEntityID) { + otherHandIsEditing = otherHand.isEditing(selection.rootEntityID()); + if (wasEditing || intersection.entityID !== hoveredEntityID || otherHandIsEditing !== otherHandWasEditing) { hoveredEntityID = intersection.entityID; selection.select(hoveredEntityID); + otherHandWasEditing = otherHandIsEditing; doHighlight = true; } } else { @@ -615,7 +622,8 @@ applyGrab(); } } else if (doHighlight) { - highlights.display(intersection.handSelected, selection.selection()); + highlights.display(intersection.handSelected, selection.selection(), + otherHand.isEditing(selection.rootEntityID())); } } From 73c6414f93dc5d16b8d4aefec44341504a7f1761 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 8 Jul 2017 12:07:20 +1200 Subject: [PATCH 025/504] Natural scale of entity selection with hands / lasers --- scripts/vr-edit/vr-edit.js | 111 +++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 668fceb015..6211d5f0dc 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -151,13 +151,15 @@ selectedEntityID = null, selectionPosition = null, selectionOrientation, - rootEntityID; + rootEntityID, + scaleCenter, + scaleRootOffset; function traverseEntityTree(id, result) { // Recursively traverses tree of entities and their children, gather IDs and properties. var children, properties, - SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions"], + SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition"], i, length; @@ -165,6 +167,7 @@ result.push({ id: id, position: properties.position, + localPosition: properties.localPosition, registrationPoint: properties.registrationPoint, rotation: properties.rotation, dimensions: properties.dimensions @@ -223,6 +226,33 @@ }); } + function startScaling(center) { + scaleCenter = center; + scaleRootOffset = Vec3.subtract(selection[0].position, center); + } + + function scale(factor, center) { + var position, + i, + length; + + // Position root. + position = Vec3.sum(center, Vec3.multiply(factor, scaleRootOffset)); + selectionPosition = position; + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiply(factor, selection[0].dimensions), + position: position + }); + + // Scale and position children. + for (i = 1, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiply(factor, selection[i].dimensions), + localPosition: Vec3.multiply(factor, selection[i].localPosition) + }); + } + } + function clear() { selection = []; selectedEntityID = null; @@ -243,6 +273,8 @@ rootEntityID: getRootEntityID, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, + startScaling: startScaling, + scale: scale, clear: clear, destroy: destroy }; @@ -391,12 +423,17 @@ NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], isEditing = false, - initialHandPosition, + isEditingWithHand = false, initialHandOrientationInverse, initialHandToSelectionVector, initialSelectionOrientation, laserEditingDistance, + isScaling = false, + initialTargetPosition, + initialTargetsCenter, + initialTargetsSeparation, + doEdit, doHighlight, @@ -431,19 +468,52 @@ return isEditing && rootEntityID === selection.rootEntityID(); } - function startEditing() { - var selectionPositionAndOrientation; + function getHandPosition() { + return Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + } + + function getTargetPosition() { + if (isEditingWithHand) { + return hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + } + return Vec3.sum(getHandPosition(), Vec3.multiply(laserEditingDistance, + Quat.getUp(Quat.multiply(MyAvatar.orientation, handPose.rotation)))); + } + + function startEditing() { + var selectionPositionAndOrientation, + initialOtherTargetPosition; - initialHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation)); + selection.select(hoveredEntityID); // Entity may have been moved by other hand so refresh position and orientation. selectionPositionAndOrientation = selection.getPositionAndOrientation(); - initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, initialHandPosition); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, getHandPosition()); initialSelectionOrientation = selectionPositionAndOrientation.orientation; + isEditingWithHand = intersection.handSelected; + + if (otherHand.isEditing(selection.rootEntityID())) { + // Store initial values for use in scaling. + initialTargetPosition = getTargetPosition(); + initialOtherTargetPosition = otherHand.getTargetPosition(); + initialTargetsCenter = Vec3.multiply(0.5, Vec3.sum(initialTargetPosition, initialOtherTargetPosition)); + initialTargetsSeparation = Vec3.distance(initialTargetPosition, initialOtherTargetPosition); + selection.startScaling(initialTargetsCenter); + isScaling = true; + } else { + isScaling = false; + } + isEditing = true; } + function updateGrabOffset(selectionPositionAndOrientation) { + initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation)); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, getHandPosition()); + initialSelectionOrientation = selectionPositionAndOrientation.orientation; + } + function applyGrab() { var handPosition, handOrientation, @@ -451,7 +521,7 @@ selectionPosition, selectionOrientation; - handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handPosition = getHandPosition(); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); @@ -462,10 +532,29 @@ } function applyScale() { - // TODO + var targetPosition, + otherTargetPosition, + targetsSeparation, + center, + scale, + selectionPositionAndOrientation; + + // Scale selection. + targetPosition = getTargetPosition(); + otherTargetPosition = otherHand.getTargetPosition(); + targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); + scale = targetsSeparation / initialTargetsSeparation; + center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); + selection.scale(scale, center); + + // Update grab offsets. + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + updateGrabOffset(selectionPositionAndOrientation); + otherHand.updateGrabOffset(selectionPositionAndOrientation); } function stopEditing() { + isScaling = false; isEditing = false; } @@ -615,7 +704,7 @@ function apply() { if (doEdit) { if (otherHand.isEditing(selection.rootEntityID())) { - if (hand === LEFT_HAND) { + if (isScaling) { applyScale(); } } else { @@ -649,6 +738,8 @@ return { setOtherHand: setOtherHand, isEditing: getIsEditing, + getTargetPosition: getTargetPosition, + updateGrabOffset: updateGrabOffset, update: update, apply: apply, destroy: destroy From e8d8a5c0a27e4a9ee5353b7e491d4c8450dee472 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 8 Jul 2017 12:27:48 +1200 Subject: [PATCH 026/504] Rotate entity selection while scaling --- scripts/vr-edit/vr-edit.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6211d5f0dc..4eeab88241 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -153,7 +153,8 @@ selectionOrientation, rootEntityID, scaleCenter, - scaleRootOffset; + scaleRootOffset, + scaleRootOrientation; function traverseEntityTree(id, result) { // Recursively traverses tree of entities and their children, gather IDs and properties. @@ -229,19 +230,24 @@ function startScaling(center) { scaleCenter = center; scaleRootOffset = Vec3.subtract(selection[0].position, center); + scaleRootOrientation = selectionOrientation; } - function scale(factor, center) { + function scale(factor, rotation, center) { var position, + orientation, i, length; // Position root. - position = Vec3.sum(center, Vec3.multiply(factor, scaleRootOffset)); + position = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); + orientation = Quat.multiply(rotation, scaleRootOrientation); selectionPosition = position; + selectionOrientation = orientation; Entities.editEntity(selection[0].id, { dimensions: Vec3.multiply(factor, selection[0].dimensions), - position: position + position: position, + rotation: orientation }); // Scale and position children. @@ -433,6 +439,7 @@ initialTargetPosition, initialTargetsCenter, initialTargetsSeparation, + initialtargetsDirection, doEdit, doHighlight, @@ -499,6 +506,7 @@ initialOtherTargetPosition = otherHand.getTargetPosition(); initialTargetsCenter = Vec3.multiply(0.5, Vec3.sum(initialTargetPosition, initialOtherTargetPosition)); initialTargetsSeparation = Vec3.distance(initialTargetPosition, initialOtherTargetPosition); + initialtargetsDirection = Vec3.subtract(initialOtherTargetPosition, initialTargetPosition); selection.startScaling(initialTargetsCenter); isScaling = true; } else { @@ -535,8 +543,9 @@ var targetPosition, otherTargetPosition, targetsSeparation, - center, scale, + rotation, + center, selectionPositionAndOrientation; // Scale selection. @@ -544,8 +553,9 @@ otherTargetPosition = otherHand.getTargetPosition(); targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); scale = targetsSeparation / initialTargetsSeparation; + rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); - selection.scale(scale, center); + selection.scale(scale, rotation, center); // Update grab offsets. selectionPositionAndOrientation = selection.getPositionAndOrientation(); From ed3c0cdced01e7cfa5a5b586c340876d8c1e6c07 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Jul 2017 17:16:38 +1200 Subject: [PATCH 027/504] Use grip click to toggle scale-with-hands / scale-with-handles --- scripts/vr-edit/vr-edit.js | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4eeab88241..b8e1182ae6 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -18,6 +18,7 @@ tablet, button, isAppActive = false, + isScaleWithHandles = false, VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. @@ -398,7 +399,7 @@ }; }; - Hand = function (side) { + Hand = function (side, gripPressedCallback) { // Hand controller input. // Each hand has a laser, an entity selection, and entity highlighter. @@ -406,6 +407,11 @@ handController, controllerTrigger, controllerTriggerClicked, + controllerGrip, + + isGripPressed = false, + GRIP_ON_VALUE = 0.99, + GRIP_OFF_VALUE = 0.95, TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. @@ -422,6 +428,8 @@ handPose, intersection = {}, + isScaleWithHandles = false, + isLaserOn = false, hoveredEntityID = null, @@ -456,11 +464,13 @@ handController = Controller.Standard.LeftHand; controllerTrigger = Controller.Standard.LT; controllerTriggerClicked = Controller.Standard.LTClick; + controllerGrip = Controller.Standard.LeftGrip; GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; } else { handController = Controller.Standard.RightHand; controllerTrigger = Controller.Standard.RT; controllerTriggerClicked = Controller.Standard.RTClick; + controllerGrip = Controller.Standard.RightGrip; } laser = new Laser(hand); @@ -471,6 +481,10 @@ otherHand = hand; } + function setScaleWithHandles(value) { + isScaleWithHandles = value; + } + function getIsEditing(rootEntityID) { return isEditing && rootEntityID === selection.rootEntityID(); } @@ -578,7 +592,8 @@ } function update() { - var palmPosition, + var gripValue, + palmPosition, entityID, entityIDs, entitySize, @@ -615,6 +630,17 @@ ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); isTriggerClicked = Controller.getValue(controllerTriggerClicked); + // Controller grip. + gripValue = Controller.getValue(controllerGrip); + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF_VALUE; + } else { + isGripPressed = gripValue > GRIP_ON_VALUE; + if (isGripPressed) { + gripPressedCallback(); + } + } + // Hand-entity intersection, if any. entityID = null; palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); @@ -747,6 +773,7 @@ return { setOtherHand: setOtherHand, + setScaleWithHandles: setScaleWithHandles, isEditing: getIsEditing, getTargetPosition: getTargetPosition, updateGrabOffset: updateGrabOffset, @@ -786,6 +813,12 @@ } } + function onGripClicked() { + isScaleWithHandles = !isScaleWithHandles; + hands[LEFT_HAND].setScaleWithHandles(isScaleWithHandles); + hands[RIGHT_HAND].setScaleWithHandles(isScaleWithHandles); + } + function setUp() { updateHandControllerGrab(); @@ -806,8 +839,8 @@ } // Hands, each with a laser, selection, etc. - hands[LEFT_HAND] = new Hand(LEFT_HAND); - hands[RIGHT_HAND] = new Hand(RIGHT_HAND); + hands[LEFT_HAND] = new Hand(LEFT_HAND, onGripClicked); + hands[RIGHT_HAND] = new Hand(RIGHT_HAND, onGripClicked); hands[LEFT_HAND].setOtherHand(hands[RIGHT_HAND]); hands[RIGHT_HAND].setOtherHand(hands[LEFT_HAND]); From 3450d64bede55d4a87c3fc28d5056c14713b38b8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Jul 2017 17:37:09 +1200 Subject: [PATCH 028/504] Hover with "scale" color if in scale-with-handles mode --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index b8e1182ae6..9b4ea750a0 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -748,7 +748,7 @@ } } else if (doHighlight) { highlights.display(intersection.handSelected, selection.selection(), - otherHand.isEditing(selection.rootEntityID())); + otherHand.isEditing(selection.rootEntityID()) || isScaleWithHandles); } } From 657ac1aaeb57049150c80561db2e3f1af491caca Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Jul 2017 21:16:21 +1200 Subject: [PATCH 029/504] Fix enumeration of entity tree --- scripts/vr-edit/vr-edit.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 9b4ea750a0..27dd7a429d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -177,7 +177,9 @@ children = Entities.getChildrenIDs(id); for (i = 0, length = children.length; i < length; i += 1) { - traverseEntityTree(children[i], result); + if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { + traverseEntityTree(children[i], result); + } } } From 68cdc235309a6801244db5d4716dd955f73c4404 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 11:04:02 +1200 Subject: [PATCH 030/504] Display selection bounding box for scaling with handles --- scripts/vr-edit/vr-edit.js | 152 ++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 27dd7a429d..2481d15aed 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -30,6 +30,7 @@ updateTimer = null, Highlights, + Handles, Selection, Laser, Hand, @@ -38,6 +39,18 @@ NULL_UUID = "{00000000-0000-0000-0000-000000000000}", HALF_TREE_SCALE = 16384; + if (typeof Vec3.min !== "function") { + Vec3.min = function (a, b) { + return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), z: Math.min(a.z, b.z) }; + }; + } + + if (typeof Vec3.max !== "function") { + Vec3.max = function (a, b) { + return { x: Math.max(a.x, b.x), y: Math.max(a.y, b.y), z: Math.max(a.z, b.z) }; + }; + } + Highlights = function (hand) { // Draws highlights on selected entities. @@ -145,9 +158,54 @@ }; }; + Handles = function () { + var boundingBoxOverlay, + //HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, + HIGHLIGHT_COLOR = { red: 255, green: 0, blue: 255 }, + BOUNDING_BOX_ALPHA = 0.8; + + boundingBoxOverlay = Overlays.addOverlay("cube", { + color: HIGHLIGHT_COLOR, + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + function display(rootEntityID, boundingBox) { + // Selection bounding box. + Overlays.editOverlay(boundingBoxOverlay, { + parentID: rootEntityID, + position: boundingBox.center, + rotation: boundingBox.orientation, + dimensions: boundingBox.dimensions, + visible: true + }); + } + + function clear() { + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + } + + function destroy() { + clear(); + Overlays.deleteOverlay(boundingBoxOverlay); + } + + if (!this instanceof Handles) { + return new Handles(); + } + + return { + display: display, + clear: clear, + destroy: destroy + }; + }; + Selection = function () { // Manages set of selected entities. Currently supports just one set of linked entities. - var selection = [], selectedEntityID = null, selectionPosition = null, @@ -155,7 +213,8 @@ rootEntityID, scaleCenter, scaleRootOffset, - scaleRootOrientation; + scaleRootOrientation, + ENTITY_TYPE = "entity"; function traverseEntityTree(id, result) { // Recursively traverses tree of entities and their children, gather IDs and properties. @@ -214,6 +273,81 @@ return selection; } + function getBoundingBox() { + var center, + orientation, + inverseOrientation, + dimensions, + min, + max, + i, + j, + length, + registration, + position, + rotation, + corners = [], + NUM_CORNERS = 8; + + if (selection.length === 1) { + if (Vec3.equal(selection[0].registrationPoint, Vec3.HALF)) { + center = selectionPosition; + } else { + center = Vec3.sum(selectionPosition, + Vec3.multiplyQbyV(selectionOrientation, + Vec3.multiplyVbyV(selection[0].dimensions, + Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); + } + orientation = selectionOrientation; + dimensions = selection[0].dimensions; + } else if (selection.length > 1) { + // Find min & max x, y, z values of entities' dimension box corners in root entity coordinate system. + // Note: Don't use entities' bounding boxes because they're in world coordinates and may make the calculated + // bounding box be larger than necessary. + min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); + max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); + inverseOrientation = Quat.inverse(selectionOrientation); + for (i = 1, length = selection.length; i < length; i += 1) { + + registration = selection[i].registrationPoint; + corners[0] = { x: -registration.x, y: -registration.y, z: -registration.z }; + corners[1] = { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }; + corners[2] = { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }; + corners[3] = { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; + corners[4] = { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }; + corners[5] = { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }; + corners[6] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }; + corners[7] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; + + position = selection[i].position; + rotation = selection[i].rotation; + dimensions = selection[i].dimensions; + + for (j = 0; j < NUM_CORNERS; j += 1) { + // Corner position in world coordinates. + corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); + // Corner position in root entity coordinates. + corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], selectionPosition)); + // Update min & max. + min = Vec3.min(corners[j], min); + max = Vec3.max(corners[j], max); + } + } + + // Calculate bounding box. + center = Vec3.sum(selectionPosition, + Vec3.multiplyQbyV(selectionOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); + orientation = selectionOrientation; + dimensions = Vec3.subtract(max, min); + } + + return { + center: center, + orientation: orientation, + dimensions: dimensions + }; + } + function getPositionAndOrientation() { return { position: selectionPosition, @@ -280,6 +414,7 @@ select: select, selection: getSelection, rootEntityID: getRootEntityID, + boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, startScaling: startScaling, @@ -459,7 +594,8 @@ laser, selection, - highlights; + highlights, + handles; hand = side; if (hand === LEFT_HAND) { @@ -478,6 +614,7 @@ laser = new Laser(hand); selection = new Selection(); highlights = new Highlights(hand); + handles = new Handles(); function setOtherHand(hand) { otherHand = hand; @@ -622,6 +759,7 @@ laser.clear(); selection.clear(); highlights.clear(); + handles.clear(); hoveredEntityID = null; } return; @@ -712,6 +850,9 @@ laserEditingDistance = laserLength; } startEditing(); + if (isScaleWithHandles) { + handles.display(selection.rootEntityID(), selection.boundingBox()); + } } } else { wasEditing = isEditing; @@ -733,6 +874,7 @@ if (hoveredEntityID) { selection.clear(); highlights.clear(); + handles.clear(); hoveredEntityID = null; } } @@ -767,6 +909,10 @@ highlights.destroy(); highlights = null; } + if (handles) { + handles.destroy(); + handles = null; + } } if (!this instanceof Hand) { From db37417ccda6d99f5085dd155c34303ea8ee8c16 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 11:15:55 +1200 Subject: [PATCH 031/504] Tidying --- scripts/vr-edit/vr-edit.js | 46 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2481d15aed..e1930c421d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -96,7 +96,6 @@ Overlays.editOverlay(entityOverlays[index], { parentID: details.id, position: Vec3.sum(details.position, offset), - registrationPoint: details.registrationPoint, rotation: details.rotation, dimensions: details.dimensions, color: overlayColor, @@ -208,9 +207,9 @@ // Manages set of selected entities. Currently supports just one set of linked entities. var selection = [], selectedEntityID = null, - selectionPosition = null, - selectionOrientation, - rootEntityID, + rootEntityID = null, + rootPosition, + rootOrientation, scaleCenter, scaleRootOffset, scaleRootOrientation, @@ -255,8 +254,8 @@ } // Selection position and orientation is that of the root entity. - selectionPosition = entityProperties.position; - selectionOrientation = entityProperties.rotation; + rootPosition = entityProperties.position; + rootOrientation = entityProperties.rotation; // Find all children. selection = []; @@ -291,14 +290,14 @@ if (selection.length === 1) { if (Vec3.equal(selection[0].registrationPoint, Vec3.HALF)) { - center = selectionPosition; + center = rootPosition; } else { - center = Vec3.sum(selectionPosition, - Vec3.multiplyQbyV(selectionOrientation, + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, Vec3.multiplyVbyV(selection[0].dimensions, Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); } - orientation = selectionOrientation; + orientation = rootOrientation; dimensions = selection[0].dimensions; } else if (selection.length > 1) { // Find min & max x, y, z values of entities' dimension box corners in root entity coordinate system. @@ -306,7 +305,7 @@ // bounding box be larger than necessary. min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); - inverseOrientation = Quat.inverse(selectionOrientation); + inverseOrientation = Quat.inverse(rootOrientation); for (i = 1, length = selection.length; i < length; i += 1) { registration = selection[i].registrationPoint; @@ -327,7 +326,7 @@ // Corner position in world coordinates. corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); // Corner position in root entity coordinates. - corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], selectionPosition)); + corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], rootPosition)); // Update min & max. min = Vec3.min(corners[j], min); max = Vec3.max(corners[j], max); @@ -335,9 +334,9 @@ } // Calculate bounding box. - center = Vec3.sum(selectionPosition, - Vec3.multiplyQbyV(selectionOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); - orientation = selectionOrientation; + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); + orientation = rootOrientation; dimensions = Vec3.subtract(max, min); } @@ -349,15 +348,17 @@ } function getPositionAndOrientation() { + // Position and orientation of root entity. return { - position: selectionPosition, - orientation: selectionOrientation + position: rootPosition, + orientation: rootOrientation }; } function setPositionAndOrientation(position, orientation) { - selectionPosition = position; - selectionOrientation = orientation; + // Position and orientation of root entity. + rootPosition = position; + rootOrientation = orientation; Entities.editEntity(rootEntityID, { position: position, rotation: orientation @@ -367,7 +368,7 @@ function startScaling(center) { scaleCenter = center; scaleRootOffset = Vec3.subtract(selection[0].position, center); - scaleRootOrientation = selectionOrientation; + scaleRootOrientation = rootOrientation; } function scale(factor, rotation, center) { @@ -379,8 +380,8 @@ // Position root. position = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); orientation = Quat.multiply(rotation, scaleRootOrientation); - selectionPosition = position; - selectionOrientation = orientation; + rootPosition = position; + rootOrientation = orientation; Entities.editEntity(selection[0].id, { dimensions: Vec3.multiply(factor, selection[0].dimensions), position: position, @@ -935,6 +936,7 @@ // Main update loop. updateTimer = null; + // Each hand's action depends on the state of the other hand, so update the states first then apply in actions. hands[LEFT_HAND].update(); hands[RIGHT_HAND].update(); hands[LEFT_HAND].apply(); From 196f5a43b106bebcecaf8164ff191d022a1dd5a4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 12:18:59 +1200 Subject: [PATCH 032/504] Don't scale with hands if scaling with handles --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e1930c421d..22674d517b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -840,7 +840,7 @@ if (isEditing) { // Perform edit. doEdit = true; - } else if (intersection.intersects) { + } else if (intersection.intersects && (!isScaleWithHandles || !otherHand.isEditing(intersection.entityID))) { // Start editing. if (intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; From 850b94220f79bd7d6a6ffb8157335c842c360be5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 15:22:44 +1200 Subject: [PATCH 033/504] Display face scale handles --- scripts/vr-edit/vr-edit.js | 88 ++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 22674d517b..cdaa793616 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -159,12 +159,37 @@ Handles = function () { var boundingBoxOverlay, - //HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, - HIGHLIGHT_COLOR = { red: 255, green: 0, blue: 255 }, - BOUNDING_BOX_ALPHA = 0.8; + faceHandleOverlays = [], + BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, + BOUNDING_BOX_ALPHA = 0.8, + HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, + HANDLE_ALPHA = 0.7, + NUM_FACE_HANDLES = 6, + FACE_HANDLE_OVERLAY_AXES, + FACE_HANDLE_OVERLAY_ROTATIONS, + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + i; + + FACE_HANDLE_OVERLAY_AXES = [ + { x: -0.5, y: 0, z: 0 }, + { x: 0.5, y: 0, z: 0 }, + { x: 0, y: -0.5, z: 0 }, + { x: 0, y: 0.5, z: 0 }, + { x: 0, y: 0, z: -0.5 }, + { x: 0, y: 0, z: 0.5 } + ]; + + FACE_HANDLE_OVERLAY_ROTATIONS = [ + Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + Quat.fromVec3Degrees({ x: 180, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + ]; boundingBoxOverlay = Overlays.addOverlay("cube", { - color: HIGHLIGHT_COLOR, + color: BOUNDING_BOX_COLOR, alpha: BOUNDING_BOX_ALPHA, solid: false, drawInFront: true, @@ -172,19 +197,54 @@ visible: false }); - function display(rootEntityID, boundingBox) { - // Selection bounding box. - Overlays.editOverlay(boundingBoxOverlay, { - parentID: rootEntityID, - position: boundingBox.center, - rotation: boundingBox.orientation, - dimensions: boundingBox.dimensions, - visible: true + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + faceHandleOverlays[i] = Overlays.addOverlay("shape", { + shape: "Cone", + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: true, + dimensions: { x: 0.1, y: 0.12, z: 0.1 }, + visible: false }); } + + function display(rootEntityID, boundingBox) { + var boundingBoxDimensions = boundingBox.dimensions, + boundingBoxLocalCenter = boundingBox.localCenter, + i; + + // Selection bounding box. + Overlays.editOverlay(boundingBoxOverlay, { + parentID: rootEntityID, + localPosition: boundingBoxLocalCenter, + localRotation: ZERO_ROTATION, + dimensions: boundingBoxDimensions, + visible: true + }); + + // Face scale handles. + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(boundingBoxDimensions, { x: 0.12, y: 0.12, z: 0.12 }))), + localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + visible: true + }); + } + } + function clear() { + var i; + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); + } } function destroy() { @@ -274,6 +334,7 @@ function getBoundingBox() { var center, + localCenter, orientation, inverseOrientation, dimensions, @@ -297,6 +358,7 @@ Vec3.multiplyVbyV(selection[0].dimensions, Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); } + localCenter = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(center, rootPosition)); orientation = rootOrientation; dimensions = selection[0].dimensions; } else if (selection.length > 1) { @@ -336,12 +398,14 @@ // Calculate bounding box. center = Vec3.sum(rootPosition, Vec3.multiplyQbyV(rootOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); + localCenter = Vec3.multiply(0.5, Vec3.sum(min, max)); orientation = rootOrientation; dimensions = Vec3.subtract(max, min); } return { center: center, + localCenter: localCenter, orientation: orientation, dimensions: dimensions }; From 2c3cd53f8dcbf4eea68e96345827165d931bc148 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 15:30:13 +1200 Subject: [PATCH 034/504] Display face scale handles only for single entities --- scripts/vr-edit/vr-edit.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index cdaa793616..47512cc4c3 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -211,7 +211,7 @@ } - function display(rootEntityID, boundingBox) { + function display(rootEntityID, boundingBox, isMultiple) { var boundingBoxDimensions = boundingBox.dimensions, boundingBoxLocalCenter = boundingBox.localCenter, i; @@ -226,15 +226,23 @@ }); // Face scale handles. - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { - parentID: rootEntityID, - localPosition: Vec3.sum(boundingBoxLocalCenter, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], - Vec3.sum(boundingBoxDimensions, { x: 0.12, y: 0.12, z: 0.12 }))), - localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], - visible: true - }); + // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity + // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. + if (!isMultiple) { + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(boundingBoxDimensions, { x: 0.12, y: 0.12, z: 0.12 }))), + localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + visible: true + }); + } + } else { + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); + } } } @@ -332,6 +340,10 @@ return selection; } + function count() { + return selection.length; + } + function getBoundingBox() { var center, localCenter, @@ -478,6 +490,7 @@ return { select: select, selection: getSelection, + count: count, rootEntityID: getRootEntityID, boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, @@ -916,7 +929,7 @@ } startEditing(); if (isScaleWithHandles) { - handles.display(selection.rootEntityID(), selection.boundingBox()); + handles.display(selection.rootEntityID(), selection.boundingBox(), selection.count() > 1); } } } else { From 972cf1a1bedb68783f77431001e1186ceb1f1e8a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 16:11:59 +1200 Subject: [PATCH 035/504] Size handles to compensate for physical distance --- scripts/vr-edit/vr-edit.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 47512cc4c3..650cd6d55b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -165,9 +165,12 @@ HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, HANDLE_ALPHA = 0.7, NUM_FACE_HANDLES = 6, + FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 }, FACE_HANDLE_OVERLAY_AXES, + FACE_HANDLE_OVERLAY_OFFSETS, FACE_HANDLE_OVERLAY_ROTATIONS, ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, i; FACE_HANDLE_OVERLAY_AXES = [ @@ -179,6 +182,12 @@ { x: 0, y: 0, z: 0.5 } ]; + FACE_HANDLE_OVERLAY_OFFSETS = { + x: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + y: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + z: FACE_HANDLE_OVERLAY_DIMENSIONS.y + }; + FACE_HANDLE_OVERLAY_ROTATIONS = [ Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }), Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), @@ -205,7 +214,6 @@ solid: true, drawInFront: true, ignoreRayIntersection: true, - dimensions: { x: 0.1, y: 0.12, z: 0.1 }, visible: false }); } @@ -214,6 +222,10 @@ function display(rootEntityID, boundingBox, isMultiple) { var boundingBoxDimensions = boundingBox.dimensions, boundingBoxLocalCenter = boundingBox.localCenter, + faceHandleDimensions, + faceHandleOffsets, + boundingBoxVector, + distanceMultiplier, i; // Selection bounding box. @@ -225,17 +237,26 @@ visible: true }); + // Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones + // display smaller in order to give comfortable depth cue. + boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); + distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER + * Vec3.dot(Quat.getForward(Camera.orientation), boundingBoxVector) + / Math.sqrt(Vec3.length(boundingBoxVector)); + // Face scale handles. // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. if (!isMultiple) { + faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); + faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); for (i = 0; i < NUM_FACE_HANDLES; i += 1) { Overlays.editOverlay(faceHandleOverlays[i], { parentID: rootEntityID, localPosition: Vec3.sum(boundingBoxLocalCenter, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], - Vec3.sum(boundingBoxDimensions, { x: 0.12, y: 0.12, z: 0.12 }))), + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + dimensions: faceHandleDimensions, visible: true }); } From 1fc2d7ed1b004caea385ff965637940315e0a019 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 18:44:08 +1200 Subject: [PATCH 036/504] Display corner scale handles --- scripts/vr-edit/vr-edit.js | 78 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 650cd6d55b..cd4470f075 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -159,11 +159,17 @@ Handles = function () { var boundingBoxOverlay, + cornerIndexes = [], + cornerHandleOverlays = [], faceHandleOverlays = [], BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, BOUNDING_BOX_ALPHA = 0.8, HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, HANDLE_ALPHA = 0.7, + NUM_CORNERS = 8, + NUM_CORNER_HANDLES = 2, + CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, + CORNER_HANDLE_OVERLAY_AXES, NUM_FACE_HANDLES = 6, FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 }, FACE_HANDLE_OVERLAY_AXES, @@ -173,6 +179,18 @@ DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, i; + CORNER_HANDLE_OVERLAY_AXES = [ + // Ordered such that items 4 apart are opposite corners - used in display(). + { x: -0.5, y: -0.5, z: -0.5 }, + { x: -0.5, y: -0.5, z: 0.5 }, + { x: -0.5, y: 0.5, z: -0.5 }, + { x: -0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: -0.5 }, + { x: 0.5, y: -0.5, z: 0.5 }, + { x: 0.5, y: -0.5, z: -0.5 } + ]; + FACE_HANDLE_OVERLAY_AXES = [ { x: -0.5, y: 0, z: 0 }, { x: 0.5, y: 0, z: 0 }, @@ -206,6 +224,17 @@ visible: false }); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + } + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { faceHandleOverlays[i] = Overlays.addOverlay("shape", { shape: "Cone", @@ -218,14 +247,24 @@ }); } - function display(rootEntityID, boundingBox, isMultiple) { var boundingBoxDimensions = boundingBox.dimensions, + boundingBoxCenter = boundingBox.center, boundingBoxLocalCenter = boundingBox.localCenter, - faceHandleDimensions, - faceHandleOffsets, + boundingBoxOrientation = boundingBox.orientation, + cameraPosition, boundingBoxVector, distanceMultiplier, + cameraUp, + cornerPosition, + cornerVector, + crossProductScale, + maxCrossProductScale, + rightCornerIndex, + leftCornerIndex, + cornerHandleDimensions, + faceHandleDimensions, + faceHandleOffsets, i; // Selection bounding box. @@ -239,11 +278,41 @@ // Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones // display smaller in order to give comfortable depth cue. + cameraPosition = Camera.position; boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER * Vec3.dot(Quat.getForward(Camera.orientation), boundingBoxVector) / Math.sqrt(Vec3.length(boundingBoxVector)); + // Corner scale handles. + // At right-most and opposite corners of bounding box. + cameraUp = Quat.getUp(Camera.orientation); + maxCrossProductScale = 0; + for (i = 0; i < NUM_CORNERS; i += 1) { + cornerPosition = Vec3.sum(boundingBoxCenter, + Vec3.multiplyQbyV(boundingBoxOrientation, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions))); + cornerVector = Vec3.subtract(cornerPosition, cameraPosition); + crossProductScale = Vec3.dot(Vec3.cross(cornerVector, boundingBoxVector), cameraUp); + if (crossProductScale > maxCrossProductScale) { + maxCrossProductScale = crossProductScale; + rightCornerIndex = i; + } + } + leftCornerIndex = (rightCornerIndex + 4) % NUM_CORNERS; + cornerIndexes[0] = leftCornerIndex; + cornerIndexes[1] = rightCornerIndex; + cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), + dimensions: cornerHandleDimensions, + visible: true + }); + } + // Face scale handles. // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. @@ -271,6 +340,9 @@ var i; Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { visible: false }); + } for (i = 0; i < NUM_FACE_HANDLES; i += 1) { Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); } From 23ebb791e847d3696a4e2299dc908fb36c0daf04 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Jul 2017 19:22:08 +1200 Subject: [PATCH 037/504] Fix don't scale with hands if scaling multiple entities with handles --- scripts/vr-edit/vr-edit.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index cd4470f075..c7dc3c8499 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -51,6 +51,21 @@ }; } + if (typeof Entities.rootOf !== "function") { + Entities.rootOf = function (entityID) { + var rootEntityID, + entityProperties, + PARENT_PROPERTIES = ["parentID"]; + rootEntityID = entityID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + while (entityProperties.parentID !== NULL_UUID) { + rootEntityID = entityProperties.parentID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + } + return rootEntityID; + }; + } + Highlights = function (hand) { // Draws highlights on selected entities. @@ -407,14 +422,10 @@ PARENT_PROPERTIES = ["parentID", "position", "rotation"]; // Find root parent. - rootEntityID = entityID; - entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - while (entityProperties.parentID !== NULL_UUID) { - rootEntityID = entityProperties.parentID; - entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - } + rootEntityID = Entities.rootOf(entityID); // Selection position and orientation is that of the root entity. + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); rootPosition = entityProperties.position; rootOrientation = entityProperties.rotation; @@ -1010,7 +1021,8 @@ if (isEditing) { // Perform edit. doEdit = true; - } else if (intersection.intersects && (!isScaleWithHandles || !otherHand.isEditing(intersection.entityID))) { + } else if (intersection.intersects && (!isScaleWithHandles + || !otherHand.isEditing(Entities.rootOf(intersection.entityID)))) { // Start editing. if (intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; From e644aabaf7a5cdccccba11c1649dcb144e567809 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 13 Jul 2017 15:04:25 +1200 Subject: [PATCH 038/504] Intersect overlays with hands --- scripts/vr-edit/vr-edit.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c7dc3c8499..d6199a162b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -915,6 +915,10 @@ function update() { var gripValue, palmPosition, + overlayID, + overlayIDs, + overlayDistance, + distance, entityID, entityIDs, entitySize, @@ -963,11 +967,34 @@ } } + // Hand-overlay intersection, if any. + overlayID = null; + palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); + if (overlayIDs.length > 0) { + // Typically, there will be only one overlay; optimize for that case. + overlayID = overlayIDs[0]; + if (overlayIDs.length > 1) { + // Find closest overlay. + overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); + for (i = 1, length = overlayIDs.length; i < length; i += 1) { + distance = + Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); + if (distance > overlayDistance) { + overlayID = overlayIDs[i]; + overlayDistance = distance; + } + } + } + } + // Hand-entity intersection, if any. + // TODO: Only test intersection if overlay not intersected? entityID = null; palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); if (entityIDs.length > 0) { + // TODO: If number of entities is often 1 in practice, optimize code for this case. // Find smallest, editable entity. entitySize = HALF_TREE_SCALE; for (i = 0, length = entityIDs.length; i < length; i += 1) { @@ -980,13 +1007,15 @@ } } } + intersection = { - intersects: entityID !== null, + intersects: overlayID !== null || entityID !== null, + overlayID: overlayID, entityID: entityID, handSelected: true }; - // Laser-entity intersection, if any. + // Laser-entity intersection, if any, if hand not intersected. if (!intersection.intersects && isTriggerPressed) { handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); From 9cbde6d99e5c7265eb54b0c5c61c31b56016904a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 13 Jul 2017 15:05:04 +1200 Subject: [PATCH 039/504] Hover intersected handle --- scripts/vr-edit/vr-edit.js | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d6199a162b..eafdd94f43 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -180,6 +180,7 @@ BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, BOUNDING_BOX_ALPHA = 0.8, HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, + HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 }, HANDLE_ALPHA = 0.7, NUM_CORNERS = 8, NUM_CORNER_HANDLES = 2, @@ -192,6 +193,7 @@ FACE_HANDLE_OVERLAY_ROTATIONS, ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, + hoveredOverlayID = null, i; CORNER_HANDLE_OVERLAY_AXES = [ @@ -245,7 +247,7 @@ alpha: HANDLE_ALPHA, solid: true, drawInFront: true, - ignoreRayIntersection: true, + ignoreRayIntersection: false, visible: false }); } @@ -257,7 +259,7 @@ alpha: HANDLE_ALPHA, solid: true, drawInFront: true, - ignoreRayIntersection: true, + ignoreRayIntersection: false, visible: false }); } @@ -351,6 +353,21 @@ } } + function hover(overlayID) { + if (overlayID !== hoveredOverlayID) { + if (hoveredOverlayID !== null) { + Overlays.editOverlay(hoveredOverlayID, { color: HANDLE_NORMAL_COLOR }); + hoveredOverlayID = null; + } + + if (overlayID !== null + && (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) { + Overlays.editOverlay(overlayID, { color: HANDLE_HOVER_COLOR }); + hoveredOverlayID = overlayID; + } + } + } + function clear() { var i; @@ -374,6 +391,7 @@ return { display: display, + hover: hover, clear: clear, destroy: destroy }; @@ -737,6 +755,7 @@ GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + NEAR_HOVER_RADIUS = 0.025, PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. PRECISION_PICKING = true, @@ -772,7 +791,6 @@ doHighlight, otherHand, - otherHandWasEditing, laser, selection, @@ -912,6 +930,10 @@ return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; } + function hoverHandle(overlayID) { + handles.hover(overlayID); + } + function update() { var gripValue, palmPosition, @@ -932,7 +954,6 @@ isTriggerPressed, isTriggerClicked, wasEditing, - otherHandIsEditing, i, length; @@ -1072,13 +1093,14 @@ // Stop editing. stopEditing(); } + if (isScaleWithHandles) { + otherHand.hoverHandle(intersection.overlayID); + } if (intersection.intersects) { // Hover entities. - otherHandIsEditing = otherHand.isEditing(selection.rootEntityID()); - if (wasEditing || intersection.entityID !== hoveredEntityID || otherHandIsEditing !== otherHandWasEditing) { + if (wasEditing || intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; selection.select(hoveredEntityID); - otherHandWasEditing = otherHandIsEditing; doHighlight = true; } } else { @@ -1096,15 +1118,17 @@ function apply() { if (doEdit) { if (otherHand.isEditing(selection.rootEntityID())) { - if (isScaling) { + if (isScaling && !isScaleWithHandles) { applyScale(); } } else { applyGrab(); } } else if (doHighlight) { - highlights.display(intersection.handSelected, selection.selection(), - otherHand.isEditing(selection.rootEntityID()) || isScaleWithHandles); + if (!isScaleWithHandles || !otherHand.isEditing(selection.rootEntityID())) { + highlights.display(intersection.handSelected, selection.selection(), + otherHand.isEditing(selection.rootEntityID()) || isScaleWithHandles); + } } } @@ -1135,6 +1159,7 @@ setOtherHand: setOtherHand, setScaleWithHandles: setScaleWithHandles, isEditing: getIsEditing, + hoverHandle: hoverHandle, getTargetPosition: getTargetPosition, updateGrabOffset: updateGrabOffset, update: update, From ab6e278a487f18c15e5bc4537d356d20ef3ebf65 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 13 Jul 2017 17:01:56 +1200 Subject: [PATCH 040/504] Intersect and hover overlay handles with laser --- scripts/vr-edit/vr-edit.js | 44 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index eafdd94f43..1aa86016ed 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -945,6 +945,8 @@ entityIDs, entitySize, size, + isHandSelected, + laserIntersection, wasLaserOn, handPosition, handOrientation, @@ -1012,7 +1014,7 @@ // Hand-entity intersection, if any. // TODO: Only test intersection if overlay not intersected? entityID = null; - palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + // palmPosition is set above. entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); if (entityIDs.length > 0) { // TODO: If number of entities is often 1 in practice, optimize code for this case. @@ -1029,15 +1031,10 @@ } } - intersection = { - intersects: overlayID !== null || entityID !== null, - overlayID: overlayID, - entityID: entityID, - handSelected: true - }; + isHandSelected = overlayID !== null || entityID !== null; - // Laser-entity intersection, if any, if hand not intersected. - if (!intersection.intersects && isTriggerPressed) { + // Laser-overlay or -entity intersection if not already intersected. + if (!isHandSelected && isTriggerPressed) { handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); @@ -1046,17 +1043,36 @@ direction: Quat.getUp(handOrientation), length: PICK_MAX_DISTANCE }; - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + + laserIntersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); + if (laserIntersection.intersects) { + overlayID = laserIntersection.overlayID; + } + if (!laserIntersection.intersects) { + laserIntersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (laserIntersection.intersects && isEditableEntity(laserIntersection.entityID)) { + entityID = laserIntersection.entityID; + } else { + laserIntersection.intersects = false; + } + } laserLength = isEditing ? laserEditingDistance - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - intersection.intersects = isEditableEntity(intersection.entityID); + : (laserIntersection.intersects ? laserIntersection.distance : PICK_MAX_DISTANCE); isLaserOn = true; } else { isLaserOn = false; } + intersection = { + intersects: overlayID !== null || entityID !== null, + overlayID: overlayID, + entityID: entityID, + handSelected: isHandSelected + }; + // Laser update. if (isLaserOn) { laser.update(pickRay.origin, pickRay.direction, laserLength, isTriggerClicked); @@ -1071,7 +1087,7 @@ if (isEditing) { // Perform edit. doEdit = true; - } else if (intersection.intersects && (!isScaleWithHandles + } else if (intersection.intersects && intersection.entityID && (!isScaleWithHandles || !otherHand.isEditing(Entities.rootOf(intersection.entityID)))) { // Start editing. if (intersection.entityID !== hoveredEntityID) { @@ -1096,7 +1112,7 @@ if (isScaleWithHandles) { otherHand.hoverHandle(intersection.overlayID); } - if (intersection.intersects) { + if (intersection.intersects && intersection.entityID) { // Hover entities. if (wasEditing || intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; From 7933280d03eb270b7e9703cfa0501063226bcc64 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 12:08:22 +1200 Subject: [PATCH 041/504] Clear highlights etc. when turn application off --- scripts/vr-edit/vr-edit.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 1aa86016ed..2e5db8024c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -17,6 +17,8 @@ APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", tablet, button, + + // Application state isAppActive = false, isScaleWithHandles = false, @@ -1148,6 +1150,18 @@ } } + function clear() { + laser.clear(); + selection.clear(); + highlights.clear(); + handles.clear(); + isLaserOn = false; + isEditing = false; + isEditingWithHand = false; + isScaling = false; + hoveredEntityID = null; + } + function destroy() { if (laser) { laser.destroy(); @@ -1180,6 +1194,7 @@ updateGrabOffset: updateGrabOffset, update: update, apply: apply, + clear: clear, destroy: destroy }; }; @@ -1202,7 +1217,8 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } - function onButtonClicked() { + function onAppButtonClicked() { + // Application tablet/toolbar button clicked. isAppActive = !isAppActive; updateHandControllerGrab(); button.editProperties({ isActive: isAppActive }); @@ -1212,6 +1228,8 @@ } else { Script.clearTimeout(updateTimer); updateTimer = null; + hands[LEFT_HAND].clear(); + hands[RIGHT_HAND].clear(); } } @@ -1237,7 +1255,7 @@ isActive: isAppActive }); if (button) { - button.clicked.connect(onButtonClicked); + button.clicked.connect(onAppButtonClicked); } // Hands, each with a laser, selection, etc. @@ -1264,7 +1282,7 @@ } if (button) { - button.clicked.disconnect(onButtonClicked); + button.clicked.disconnect(onAppButtonClicked); tablet.removeButton(button); button = null; } From ee21797fcd18e0de20a1eb66f5d17c82df4203ea Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 12:22:21 +1200 Subject: [PATCH 042/504] Simplify scale-with-handles state handling --- scripts/vr-edit/vr-edit.js | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2e5db8024c..e1ffd9772d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -20,7 +20,7 @@ // Application state isAppActive = false, - isScaleWithHandles = false, + isAppScaleWithHandles = false, VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. @@ -768,8 +768,6 @@ handPose, intersection = {}, - isScaleWithHandles = false, - isLaserOn = false, hoveredEntityID = null, @@ -822,10 +820,6 @@ otherHand = hand; } - function setScaleWithHandles(value) { - isScaleWithHandles = value; - } - function getIsEditing(rootEntityID) { return isEditing && rootEntityID === selection.rootEntityID(); } @@ -1089,7 +1083,7 @@ if (isEditing) { // Perform edit. doEdit = true; - } else if (intersection.intersects && intersection.entityID && (!isScaleWithHandles + } else if (intersection.intersects && intersection.entityID && (!isAppScaleWithHandles || !otherHand.isEditing(Entities.rootOf(intersection.entityID)))) { // Start editing. if (intersection.entityID !== hoveredEntityID) { @@ -1101,7 +1095,7 @@ laserEditingDistance = laserLength; } startEditing(); - if (isScaleWithHandles) { + if (isAppScaleWithHandles) { handles.display(selection.rootEntityID(), selection.boundingBox(), selection.count() > 1); } } @@ -1111,7 +1105,7 @@ // Stop editing. stopEditing(); } - if (isScaleWithHandles) { + if (isAppScaleWithHandles) { otherHand.hoverHandle(intersection.overlayID); } if (intersection.intersects && intersection.entityID) { @@ -1136,16 +1130,16 @@ function apply() { if (doEdit) { if (otherHand.isEditing(selection.rootEntityID())) { - if (isScaling && !isScaleWithHandles) { + if (isScaling && !isAppScaleWithHandles) { applyScale(); } } else { applyGrab(); } } else if (doHighlight) { - if (!isScaleWithHandles || !otherHand.isEditing(selection.rootEntityID())) { + if (!isAppScaleWithHandles || !otherHand.isEditing(selection.rootEntityID())) { highlights.display(intersection.handSelected, selection.selection(), - otherHand.isEditing(selection.rootEntityID()) || isScaleWithHandles); + otherHand.isEditing(selection.rootEntityID()) || isAppScaleWithHandles); } } } @@ -1187,7 +1181,6 @@ return { setOtherHand: setOtherHand, - setScaleWithHandles: setScaleWithHandles, isEditing: getIsEditing, hoverHandle: hoverHandle, getTargetPosition: getTargetPosition, @@ -1234,9 +1227,7 @@ } function onGripClicked() { - isScaleWithHandles = !isScaleWithHandles; - hands[LEFT_HAND].setScaleWithHandles(isScaleWithHandles); - hands[RIGHT_HAND].setScaleWithHandles(isScaleWithHandles); + isAppScaleWithHandles = !isAppScaleWithHandles; } function setUp() { From 85c5b8778a6045e669eeebe110becf9f312d8c0f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 12:45:19 +1200 Subject: [PATCH 043/504] Simplify left/right side value handling --- scripts/vr-edit/vr-edit.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e1ffd9772d..741ff98633 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -68,9 +68,9 @@ }; } - Highlights = function (hand) { - // Draws highlights on selected entities. + Highlights = function (side) { + // Draws highlights on selected entities. var handOverlay, entityOverlays = [], GRAB_HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, @@ -83,7 +83,7 @@ handOverlay = Overlays.addOverlay("sphere", { dimensions: HAND_HIGHLIGHT_DIMENSIONS, parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND ? "_CONTROLLER_LEFTHAND" : "_CONTROLLER_RIGHTHAND"), localPosition: HAND_HIGHLIGHT_OFFSET, @@ -630,8 +630,7 @@ // Draws hand lasers. // May intersect with entities or bounding box of other hand's selection. - var hand, - laserLine = null, + var laserLine = null, laserSphere = null, searchDistance = 0.0, @@ -655,7 +654,6 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); - hand = side; laserLine = Overlays.addOverlay("line3d", { lineWidth: 5, alpha: 1.0, @@ -663,7 +661,7 @@ ignoreRayIntersection: true, drawInFront: true, parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), visible: false @@ -742,8 +740,7 @@ // Hand controller input. // Each hand has a laser, an entity selection, and entity highlighter. - var hand, - handController, + var handController, controllerTrigger, controllerTriggerClicked, controllerGrip, @@ -797,8 +794,7 @@ highlights, handles; - hand = side; - if (hand === LEFT_HAND) { + if (side === LEFT_HAND) { handController = Controller.Standard.LeftHand; controllerTrigger = Controller.Standard.LT; controllerTriggerClicked = Controller.Standard.LTClick; @@ -811,9 +807,9 @@ controllerGrip = Controller.Standard.RightGrip; } - laser = new Laser(hand); + laser = new Laser(side); selection = new Selection(); - highlights = new Highlights(hand); + highlights = new Highlights(side); handles = new Handles(); function setOtherHand(hand) { @@ -830,7 +826,7 @@ function getTargetPosition() { if (isEditingWithHand) { - return hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); } return Vec3.sum(getHandPosition(), Vec3.multiply(laserEditingDistance, Quat.getUp(Quat.multiply(MyAvatar.orientation, handPose.rotation)))); @@ -988,7 +984,7 @@ // Hand-overlay intersection, if any. overlayID = null; - palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); if (overlayIDs.length > 0) { // Typically, there will be only one overlay; optimize for that case. From 0506f516d48b3d7185766208ff46d5e797d4b724 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 12:54:08 +1200 Subject: [PATCH 044/504] Rename Hand to Editor --- scripts/vr-edit/vr-edit.js | 75 +++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 741ff98633..df37d79ccf 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -24,7 +24,7 @@ VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. - hands = [], + editors = [], LEFT_HAND = 0, RIGHT_HAND = 1, @@ -35,12 +35,13 @@ Handles, Selection, Laser, - Hand, + Editor, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", NULL_UUID = "{00000000-0000-0000-0000-000000000000}", HALF_TREE_SCALE = 16384; + if (typeof Vec3.min !== "function") { Vec3.min = function (a, b) { return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), z: Math.min(a.z, b.z) }; @@ -174,6 +175,7 @@ }; }; + Handles = function () { var boundingBoxOverlay, cornerIndexes = [], @@ -399,6 +401,7 @@ }; }; + Selection = function () { // Manages set of selected entities. Currently supports just one set of linked entities. var selection = [], @@ -626,6 +629,7 @@ }; }; + Laser = function (side) { // Draws hand lasers. // May intersect with entities or bounding box of other hand's selection. @@ -736,7 +740,8 @@ }; }; - Hand = function (side, gripPressedCallback) { + + Editor = function (side, gripPressedCallback) { // Hand controller input. // Each hand has a laser, an entity selection, and entity highlighter. @@ -787,7 +792,7 @@ doEdit, doHighlight, - otherHand, + otherEditor, laser, selection, @@ -812,8 +817,8 @@ highlights = new Highlights(side); handles = new Handles(); - function setOtherHand(hand) { - otherHand = hand; + function setOtherEditor(editor) { + otherEditor = editor; } function getIsEditing(rootEntityID) { @@ -845,10 +850,10 @@ isEditingWithHand = intersection.handSelected; - if (otherHand.isEditing(selection.rootEntityID())) { + if (otherEditor.isEditing(selection.rootEntityID())) { // Store initial values for use in scaling. initialTargetPosition = getTargetPosition(); - initialOtherTargetPosition = otherHand.getTargetPosition(); + initialOtherTargetPosition = otherEditor.getTargetPosition(); initialTargetsCenter = Vec3.multiply(0.5, Vec3.sum(initialTargetPosition, initialOtherTargetPosition)); initialTargetsSeparation = Vec3.distance(initialTargetPosition, initialOtherTargetPosition); initialtargetsDirection = Vec3.subtract(initialOtherTargetPosition, initialTargetPosition); @@ -895,7 +900,7 @@ // Scale selection. targetPosition = getTargetPosition(); - otherTargetPosition = otherHand.getTargetPosition(); + otherTargetPosition = otherEditor.getTargetPosition(); targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); scale = targetsSeparation / initialTargetsSeparation; rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); @@ -905,7 +910,7 @@ // Update grab offsets. selectionPositionAndOrientation = selection.getPositionAndOrientation(); updateGrabOffset(selectionPositionAndOrientation); - otherHand.updateGrabOffset(selectionPositionAndOrientation); + otherEditor.updateGrabOffset(selectionPositionAndOrientation); } function stopEditing() { @@ -1080,7 +1085,7 @@ // Perform edit. doEdit = true; } else if (intersection.intersects && intersection.entityID && (!isAppScaleWithHandles - || !otherHand.isEditing(Entities.rootOf(intersection.entityID)))) { + || !otherEditor.isEditing(Entities.rootOf(intersection.entityID)))) { // Start editing. if (intersection.entityID !== hoveredEntityID) { hoveredEntityID = intersection.entityID; @@ -1102,7 +1107,7 @@ stopEditing(); } if (isAppScaleWithHandles) { - otherHand.hoverHandle(intersection.overlayID); + otherEditor.hoverHandle(intersection.overlayID); } if (intersection.intersects && intersection.entityID) { // Hover entities. @@ -1125,7 +1130,7 @@ function apply() { if (doEdit) { - if (otherHand.isEditing(selection.rootEntityID())) { + if (otherEditor.isEditing(selection.rootEntityID())) { if (isScaling && !isAppScaleWithHandles) { applyScale(); } @@ -1133,9 +1138,9 @@ applyGrab(); } } else if (doHighlight) { - if (!isAppScaleWithHandles || !otherHand.isEditing(selection.rootEntityID())) { + if (!isAppScaleWithHandles || !otherEditor.isEditing(selection.rootEntityID())) { highlights.display(intersection.handSelected, selection.selection(), - otherHand.isEditing(selection.rootEntityID()) || isAppScaleWithHandles); + otherEditor.isEditing(selection.rootEntityID()) || isAppScaleWithHandles); } } } @@ -1171,12 +1176,12 @@ } } - if (!this instanceof Hand) { - return new Hand(); + if (!this instanceof Editor) { + return new Editor(); } return { - setOtherHand: setOtherHand, + setOtherEditor: setOtherEditor, isEditing: getIsEditing, hoverHandle: hoverHandle, getTargetPosition: getTargetPosition, @@ -1188,15 +1193,16 @@ }; }; + function update() { // Main update loop. updateTimer = null; // Each hand's action depends on the state of the other hand, so update the states first then apply in actions. - hands[LEFT_HAND].update(); - hands[RIGHT_HAND].update(); - hands[LEFT_HAND].apply(); - hands[RIGHT_HAND].apply(); + editors[LEFT_HAND].update(); + editors[RIGHT_HAND].update(); + editors[LEFT_HAND].apply(); + editors[RIGHT_HAND].apply(); updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); } @@ -1217,8 +1223,8 @@ } else { Script.clearTimeout(updateTimer); updateTimer = null; - hands[LEFT_HAND].clear(); - hands[RIGHT_HAND].clear(); + editors[LEFT_HAND].clear(); + editors[RIGHT_HAND].clear(); } } @@ -1226,6 +1232,7 @@ isAppScaleWithHandles = !isAppScaleWithHandles; } + function setUp() { updateHandControllerGrab(); @@ -1246,10 +1253,10 @@ } // Hands, each with a laser, selection, etc. - hands[LEFT_HAND] = new Hand(LEFT_HAND, onGripClicked); - hands[RIGHT_HAND] = new Hand(RIGHT_HAND, onGripClicked); - hands[LEFT_HAND].setOtherHand(hands[RIGHT_HAND]); - hands[RIGHT_HAND].setOtherHand(hands[LEFT_HAND]); + editors[LEFT_HAND] = new Editor(LEFT_HAND, onGripClicked); + editors[RIGHT_HAND] = new Editor(RIGHT_HAND, onGripClicked); + editors[LEFT_HAND].setOtherEditor(editors[RIGHT_HAND]); + editors[RIGHT_HAND].setOtherEditor(editors[LEFT_HAND]); if (isAppActive) { update(); @@ -1274,13 +1281,13 @@ button = null; } - if (hands[LEFT_HAND]) { - hands[LEFT_HAND].destroy(); - hands[LEFT_HAND] = null; + if (editors[LEFT_HAND]) { + editors[LEFT_HAND].destroy(); + editors[LEFT_HAND] = null; } - if (hands[RIGHT_HAND]) { - hands[RIGHT_HAND].destroy(); - hands[RIGHT_HAND] = null; + if (editors[RIGHT_HAND]) { + editors[RIGHT_HAND].destroy(); + editors[RIGHT_HAND] = null; } tablet = null; From 2acb7335f1534480d516400890430ebc89116b57 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 14:37:54 +1200 Subject: [PATCH 045/504] Move hand functionality into new Hand object --- scripts/vr-edit/vr-edit.js | 174 +++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 55 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index df37d79ccf..a4b9ef1b77 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -35,6 +35,7 @@ Handles, Selection, Laser, + Hand, Editor, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", @@ -741,11 +742,9 @@ }; - Editor = function (side, gripPressedCallback) { + Hand = function (side, gripPressedCallback) { // Hand controller input. - // Each hand has a laser, an entity selection, and entity highlighter. - - var handController, + var handController, // ####### Rename to "controller". controllerTrigger, controllerTriggerClicked, controllerGrip, @@ -754,8 +753,102 @@ GRIP_ON_VALUE = 0.99, GRIP_OFF_VALUE = 0.95, + isTriggerPressed, + isTriggerClicked, TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + + handPose, + handPosition, + handOrientation; + + if (side === LEFT_HAND) { + handController = Controller.Standard.LeftHand; + controllerTrigger = Controller.Standard.LT; + controllerTriggerClicked = Controller.Standard.LTClick; + controllerGrip = Controller.Standard.LeftGrip; + } else { + handController = Controller.Standard.RightHand; + controllerTrigger = Controller.Standard.RT; + controllerTriggerClicked = Controller.Standard.RTClick; + controllerGrip = Controller.Standard.RightGrip; + } + + function valid() { + return handPose.valid; + } + + function position() { + return handPosition; + } + + function orientation() { + return handOrientation; + } + + function triggerPressed() { + return isTriggerPressed; + } + + function triggerClicked() { + return isTriggerClicked; + } + + function update() { + var gripValue; + + // Hand pose. + handPose = Controller.getPoseValue(handController); + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + + // Controller trigger. + isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed + ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); + isTriggerClicked = Controller.getValue(controllerTriggerClicked); + + // Controller grip. + gripValue = Controller.getValue(controllerGrip); + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF_VALUE; + } else { + isGripPressed = gripValue > GRIP_ON_VALUE; + if (isGripPressed) { + gripPressedCallback(); + } + } + } + + function clear() { + // Nothing to do. + } + + function destroy() { + // Nothing to do. + } + + if (!this instanceof Hand) { + return new Hand(); + } + + return { + valid: valid, + position: position, + orientation: orientation, + triggerPressed: triggerPressed, + triggerClicked: triggerClicked, + update: update, + clear: clear, + destroy: destroy + }; + }; + + + Editor = function (side, gripPressedCallback) { + // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. + + var intersection = {}, + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. @@ -767,9 +860,6 @@ NO_EXCLUDE_IDS = [], VISIBLE_ONLY = true, - handPose, - intersection = {}, - isLaserOn = false, hoveredEntityID = null, @@ -794,24 +884,17 @@ otherEditor, + hand, laser, selection, highlights, handles; if (side === LEFT_HAND) { - handController = Controller.Standard.LeftHand; - controllerTrigger = Controller.Standard.LT; - controllerTriggerClicked = Controller.Standard.LTClick; - controllerGrip = Controller.Standard.LeftGrip; GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; - } else { - handController = Controller.Standard.RightHand; - controllerTrigger = Controller.Standard.RT; - controllerTriggerClicked = Controller.Standard.RTClick; - controllerGrip = Controller.Standard.RightGrip; } + hand = new Hand(side, gripPressedCallback); laser = new Laser(side); selection = new Selection(); highlights = new Highlights(side); @@ -825,27 +908,22 @@ return isEditing && rootEntityID === selection.rootEntityID(); } - function getHandPosition() { - return Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); - } - function getTargetPosition() { if (isEditingWithHand) { return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); } - return Vec3.sum(getHandPosition(), Vec3.multiply(laserEditingDistance, - Quat.getUp(Quat.multiply(MyAvatar.orientation, handPose.rotation)))); + return Vec3.sum(hand.position(), Vec3.multiply(laserEditingDistance, Quat.getUp(hand.orientation()))); } function startEditing() { var selectionPositionAndOrientation, initialOtherTargetPosition; - initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation)); + initialHandOrientationInverse = Quat.inverse(hand.orientation()); selection.select(hoveredEntityID); // Entity may have been moved by other hand so refresh position and orientation. selectionPositionAndOrientation = selection.getPositionAndOrientation(); - initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, getHandPosition()); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); initialSelectionOrientation = selectionPositionAndOrientation.orientation; isEditingWithHand = intersection.handSelected; @@ -867,8 +945,8 @@ } function updateGrabOffset(selectionPositionAndOrientation) { - initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation)); - initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, getHandPosition()); + initialHandOrientationInverse = Quat.inverse(hand.orientation()); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); initialSelectionOrientation = selectionPositionAndOrientation.orientation; } @@ -879,8 +957,8 @@ selectionPosition, selectionOrientation; - handPosition = getHandPosition(); - handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + handPosition = hand.position(); + handOrientation = hand.orientation(); deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); @@ -932,8 +1010,7 @@ } function update() { - var gripValue, - palmPosition, + var palmPosition, overlayID, overlayIDs, overlayDistance, @@ -950,16 +1027,14 @@ deltaOrigin, pickRay, laserLength, - isTriggerPressed, - isTriggerClicked, wasEditing, i, length; // Hand position and orientation. - handPose = Controller.getPoseValue(handController); + hand.update(); wasLaserOn = isLaserOn; - if (!handPose.valid) { + if (!hand.valid()) { isLaserOn = false; if (wasLaserOn) { laser.clear(); @@ -971,22 +1046,6 @@ return; } - // Controller trigger. - isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed - ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); - isTriggerClicked = Controller.getValue(controllerTriggerClicked); - - // Controller grip. - gripValue = Controller.getValue(controllerGrip); - if (isGripPressed) { - isGripPressed = gripValue > GRIP_OFF_VALUE; - } else { - isGripPressed = gripValue > GRIP_ON_VALUE; - if (isGripPressed) { - gripPressedCallback(); - } - } - // Hand-overlay intersection, if any. overlayID = null; palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); @@ -1031,9 +1090,9 @@ isHandSelected = overlayID !== null || entityID !== null; // Laser-overlay or -entity intersection if not already intersected. - if (!isHandSelected && isTriggerPressed) { - handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); - handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + if (!isHandSelected && hand.triggerPressed()) { + handPosition = hand.position(); + handOrientation = hand.orientation(); deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); pickRay = { origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ... @@ -1072,7 +1131,7 @@ // Laser update. if (isLaserOn) { - laser.update(pickRay.origin, pickRay.direction, laserLength, isTriggerClicked); + laser.update(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); } else if (wasLaserOn) { laser.clear(); } @@ -1080,7 +1139,7 @@ // Highlight / edit. doEdit = false; doHighlight = false; - if (isTriggerClicked) { + if (hand.triggerClicked()) { if (isEditing) { // Perform edit. doEdit = true; @@ -1146,6 +1205,7 @@ } function clear() { + hand.clear(); laser.clear(); selection.clear(); highlights.clear(); @@ -1158,6 +1218,10 @@ } function destroy() { + if (hand) { + hand.destroy(); + hand = null; + } if (laser) { laser.destroy(); laser = null; From 35e8e7762fc42e4940249b082ca8ca209d81ba22 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 15:35:03 +1200 Subject: [PATCH 046/504] Move hand intersection into Hand object --- scripts/vr-edit/vr-edit.js | 195 ++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 91 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a4b9ef1b77..5c2fd7e55e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -70,6 +70,17 @@ }; } + function isEditableRoot(entityID) { + var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], + NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], + properties; + properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + while (properties.parentID && properties.parentID !== NULL_UUID) { + properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + } + return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + } + Highlights = function (side) { // Draws highlights on selected entities. @@ -758,9 +769,14 @@ TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + NEAR_HOVER_RADIUS = 0.025, + handPose, handPosition, - handOrientation; + handOrientation, + + intersection; if (side === LEFT_HAND) { handController = Controller.Standard.LeftHand; @@ -794,8 +810,24 @@ return isTriggerClicked; } + function getIntersection() { + return intersection; + } + function update() { - var gripValue; + var gripValue, + palmPosition, + overlayID, + overlayIDs, + overlayDistance, + distance, + entityID, + entityIDs, + entitySize, + size, + i, + length; + // Hand pose. handPose = Controller.getPoseValue(handController); @@ -817,6 +849,59 @@ gripPressedCallback(); } } + + // Hand-overlay intersection, if any. + overlayID = null; + palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); + if (overlayIDs.length > 0) { + // Typically, there will be only one overlay; optimize for that case. + overlayID = overlayIDs[0]; + if (overlayIDs.length > 1) { + // Find closest overlay. + overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); + for (i = 1, length = overlayIDs.length; i < length; i += 1) { + distance = + Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); + if (distance > overlayDistance) { + overlayID = overlayIDs[i]; + overlayDistance = distance; + } + } + } + } + + // Hand-entity intersection, if any, if overlay not intersected. + entityID = null; + if (overlayID === null) { + // palmPosition is set above. + entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); + if (entityIDs.length > 0) { + // Typically, there will be only one entity; optimize for that case. + if (isEditableRoot(entityIDs[0])) { + entityID = entityIDs[0]; + } + if (entityIDs.length > 1) { + // Find smallest, editable entity. + entitySize = HALF_TREE_SCALE; + for (i = 0, length = entityIDs.length; i < length; i += 1) { + if (isEditableRoot(entityIDs[i])) { + size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); + if (size < entitySize) { + entityID = entityIDs[i]; + entitySize = size; + } + } + } + } + } + } + + intersection = { + intersects: overlayID !== null || entityID !== null, + overlayID: overlayID, + entityID: entityID + }; } function clear() { @@ -837,6 +922,7 @@ orientation: orientation, triggerPressed: triggerPressed, triggerClicked: triggerClicked, + intersection: getIntersection, update: update, clear: clear, destroy: destroy @@ -851,8 +937,8 @@ GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. - NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. - NEAR_HOVER_RADIUS = 0.025, + //NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + //NEAR_HOVER_RADIUS = 0.025, PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. PRECISION_PICKING = true, @@ -863,9 +949,6 @@ isLaserOn = false, hoveredEntityID = null, - EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], - NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], - isEditing = false, isEditingWithHand = false, initialHandOrientationInverse, @@ -996,40 +1079,19 @@ isEditing = false; } - function isEditableEntity(entityID) { - // Entity trees are moved as a group so check the root entity. - var properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); - while (properties.parentID && properties.parentID !== NULL_UUID) { - properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); - } - return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; - } - function hoverHandle(overlayID) { handles.hover(overlayID); } function update() { - var palmPosition, - overlayID, - overlayIDs, - overlayDistance, - distance, - entityID, - entityIDs, - entitySize, - size, - isHandSelected, - laserIntersection, + var isHandSelected, wasLaserOn, handPosition, handOrientation, deltaOrigin, pickRay, laserLength, - wasEditing, - i, - length; + wasEditing; // Hand position and orientation. hand.update(); @@ -1046,88 +1108,39 @@ return; } - // Hand-overlay intersection, if any. - overlayID = null; - palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); - if (overlayIDs.length > 0) { - // Typically, there will be only one overlay; optimize for that case. - overlayID = overlayIDs[0]; - if (overlayIDs.length > 1) { - // Find closest overlay. - overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); - for (i = 1, length = overlayIDs.length; i < length; i += 1) { - distance = - Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); - if (distance > overlayDistance) { - overlayID = overlayIDs[i]; - overlayDistance = distance; - } - } - } - } + // Hand-overlay or -entity intersection. + intersection = hand.intersection(); + isHandSelected = intersection.intersects; - // Hand-entity intersection, if any. - // TODO: Only test intersection if overlay not intersected? - entityID = null; - // palmPosition is set above. - entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); - if (entityIDs.length > 0) { - // TODO: If number of entities is often 1 in practice, optimize code for this case. - // Find smallest, editable entity. - entitySize = HALF_TREE_SCALE; - for (i = 0, length = entityIDs.length; i < length; i += 1) { - if (isEditableEntity(entityIDs[i])) { - size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); - if (size < entitySize) { - entityID = entityIDs[i]; - entitySize = size; - } - } - } - } - - isHandSelected = overlayID !== null || entityID !== null; - - // Laser-overlay or -entity intersection if not already intersected. + // Laser-overlay or -entity intersection if no hand intersection. if (!isHandSelected && hand.triggerPressed()) { handPosition = hand.position(); handOrientation = hand.orientation(); deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); pickRay = { - origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ... + origin: Vec3.sum(handPosition, deltaOrigin), direction: Quat.getUp(handOrientation), length: PICK_MAX_DISTANCE }; - laserIntersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - if (laserIntersection.intersects) { - overlayID = laserIntersection.overlayID; - } - if (!laserIntersection.intersects) { - laserIntersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - if (laserIntersection.intersects && isEditableEntity(laserIntersection.entityID)) { - entityID = laserIntersection.entityID; - } else { - laserIntersection.intersects = false; + if (intersection.intersects && !isEditableRoot(intersection.entityID)) { + intersection.intersects = false; } } laserLength = isEditing ? laserEditingDistance - : (laserIntersection.intersects ? laserIntersection.distance : PICK_MAX_DISTANCE); + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); isLaserOn = true; } else { isLaserOn = false; } - intersection = { - intersects: overlayID !== null || entityID !== null, - overlayID: overlayID, - entityID: entityID, - handSelected: isHandSelected - }; + intersection.handSelected = isHandSelected; // Laser update. if (isLaserOn) { From c422eaec11272f84edee7264b8ad64ef54121a42 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 14 Jul 2017 18:12:13 +1200 Subject: [PATCH 047/504] Move laser functionality into Laser object --- scripts/vr-edit/vr-edit.js | 179 ++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 71 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 5c2fd7e55e..9f84d33052 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -646,7 +646,9 @@ // Draws hand lasers. // May intersect with entities or bounding box of other hand's selection. - var laserLine = null, + var isLaserOn = false, + + laserLine = null, laserSphere = null, searchDistance = 0.0, @@ -657,7 +659,20 @@ COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, - BRIGHT_POW = 0.06; // Per handControllgerGrab.js. + BRIGHT_POW = 0.06, // Per handControllgerGrab.js. + + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + + PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. + PRECISION_PICKING = true, + NO_INCLUDE_IDS = [], + NO_EXCLUDE_IDS = [], + VISIBLE_ONLY = true, + + laserLength, + specifiedLaserLength = null, + + intersection; function colorPow(color, power) { // Per handControllerGrab.js. return { @@ -670,6 +685,10 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); + if (side === LEFT_HAND) { + GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; + } + laserLine = Overlays.addOverlay("line3d", { lineWidth: 5, alpha: 1.0, @@ -715,7 +734,7 @@ }); } - function update(origin, direction, distance, isClicked) { + function display(origin, direction, distance, isClicked) { var searchTarget, sphereSize, color, @@ -731,11 +750,75 @@ updateSphere(searchTarget, sphereSize, color, brightColor); } - function clear() { + function hide() { Overlays.editOverlay(laserLine, { visible: false }); Overlays.editOverlay(laserSphere, { visible: false }); } + function update(hand) { + var handPosition, + handOrientation, + deltaOrigin, + pickRay; + + if (!hand.intersection().intersects && hand.triggerPressed()) { + handPosition = hand.position(); + handOrientation = hand.orientation(); + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; + + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (intersection.intersects && !isEditableRoot(intersection.entityID)) { + intersection.intersects = false; + } + } + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); + } else { + intersection = { + intersects: false + }; + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } + } + + function getIntersection() { + return intersection; + } + + function setLength(length) { + specifiedLaserLength = length; + laserLength = length; + } + + function clearLength() { + specifiedLaserLength = null; + } + + function getLength() { + return laserLength; + } + + function clear() { + isLaserOn = false; + hide(); + } + function destroy() { Overlays.deleteOverlay(laserLine); Overlays.deleteOverlay(laserSphere); @@ -747,6 +830,10 @@ return { update: update, + intersection: getIntersection, + setLength: setLength, + clearLength: clearLength, + length: getLength, clear: clear, destroy: destroy }; @@ -933,22 +1020,11 @@ Editor = function (side, gripPressedCallback) { // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. - var intersection = {}, - - GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. - - //NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. - //NEAR_HOVER_RADIUS = 0.025, - - PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. - PRECISION_PICKING = true, - NO_INCLUDE_IDS = [], - NO_EXCLUDE_IDS = [], - VISIBLE_ONLY = true, - - isLaserOn = false, + var isUpdating = false, hoveredEntityID = null, + intersection, + isEditing = false, isEditingWithHand = false, initialHandOrientationInverse, @@ -973,10 +1049,6 @@ highlights, handles; - if (side === LEFT_HAND) { - GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; - } - hand = new Hand(side, gripPressedCallback); laser = new Laser(side); selection = new Selection(); @@ -1085,69 +1157,33 @@ function update() { var isHandSelected, - wasLaserOn, - handPosition, - handOrientation, - deltaOrigin, - pickRay, - laserLength, wasEditing; - // Hand position and orientation. + // Hand update. + // Early return if it's not present. hand.update(); - wasLaserOn = isLaserOn; if (!hand.valid()) { - isLaserOn = false; - if (wasLaserOn) { + if (isUpdating) { laser.clear(); selection.clear(); highlights.clear(); handles.clear(); hoveredEntityID = null; + isUpdating = false; } return; } - - // Hand-overlay or -entity intersection. + isUpdating = true; intersection = hand.intersection(); isHandSelected = intersection.intersects; - // Laser-overlay or -entity intersection if no hand intersection. - if (!isHandSelected && hand.triggerPressed()) { - handPosition = hand.position(); - handOrientation = hand.orientation(); - deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); - pickRay = { - origin: Vec3.sum(handPosition, deltaOrigin), - direction: Quat.getUp(handOrientation), - length: PICK_MAX_DISTANCE - }; - - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (intersection.intersects && !isEditableRoot(intersection.entityID)) { - intersection.intersects = false; - } - } - laserLength = isEditing - ? laserEditingDistance - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - isLaserOn = true; - } else { - isLaserOn = false; - } - - intersection.handSelected = isHandSelected; - // Laser update. - if (isLaserOn) { - laser.update(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); - } else if (wasLaserOn) { - laser.clear(); + // Displays laser if hand has no intersection and trigger is pressed. + laser.update(hand); + if (!isHandSelected) { + intersection = laser.intersection(); } + intersection.handSelected = isHandSelected; // Highlight / edit. doEdit = false; @@ -1164,8 +1200,9 @@ selection.select(hoveredEntityID); } highlights.clear(); - if (isLaserOn) { - laserEditingDistance = laserLength; + if (!intersection.handSelected) { + laserEditingDistance = laser.length(); + laser.setLength(laserEditingDistance); } startEditing(); if (isAppScaleWithHandles) { @@ -1177,6 +1214,7 @@ if (isEditing) { // Stop editing. stopEditing(); + laser.clearLength(); } if (isAppScaleWithHandles) { otherEditor.hoverHandle(intersection.overlayID); @@ -1223,7 +1261,6 @@ selection.clear(); highlights.clear(); handles.clear(); - isLaserOn = false; isEditing = false; isEditingWithHand = false; isScaling = false; From ae1b6e20f2c3e4ced38809f4eef6d8261ea8a439 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 09:20:38 +1200 Subject: [PATCH 048/504] Clear out state code for rework --- scripts/vr-edit/vr-edit.js | 243 ++++--------------------------------- 1 file changed, 25 insertions(+), 218 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 9f84d33052..c4aee6c198 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -133,7 +133,7 @@ }); } - function display(handSelected, selection, isScale) { + function display(handIntersected, selection, isScale) { var overlayColor = isScale ? SCALE_HIGHLIGHT_COLOR : GRAB_HIGHLIGHT_COLOR, i, length; @@ -141,7 +141,7 @@ // Show/hide hand overlay. Overlays.editOverlay(handOverlay, { color: overlayColor, - visible: handSelected + visible: handIntersected }); // Add/edit entity overlay. @@ -778,8 +778,10 @@ VISIBLE_ONLY); if (intersection.intersects && !isEditableRoot(intersection.entityID)) { intersection.intersects = false; + intersection.entityID = null; } } + intersection.laserIntersected = true; laserLength = (specifiedLaserLength !== null) ? specifiedLaserLength : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); @@ -863,7 +865,7 @@ handPosition, handOrientation, - intersection; + intersection = {}; if (side === LEFT_HAND) { handController = Controller.Standard.LeftHand; @@ -918,6 +920,10 @@ // Hand pose. handPose = Controller.getPoseValue(handController); + if (!handPose.valid) { + intersection = {}; + return; + } handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); @@ -987,7 +993,8 @@ intersection = { intersects: overlayID !== null || entityID !== null, overlayID: overlayID, - entityID: entityID + entityID: entityID, + handIntersected: true }; } @@ -1020,34 +1027,16 @@ Editor = function (side, gripPressedCallback) { // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. - var isUpdating = false, - hoveredEntityID = null, - - intersection, - - isEditing = false, - isEditingWithHand = false, - initialHandOrientationInverse, - initialHandToSelectionVector, - initialSelectionOrientation, - laserEditingDistance, - - isScaling = false, - initialTargetPosition, - initialTargetsCenter, - initialTargetsSeparation, - initialtargetsDirection, - - doEdit, - doHighlight, - - otherEditor, + var otherEditor, // Other hand's Editor object. + // Primary objects. hand, laser, selection, highlights, - handles; + handles, + + intersection; hand = new Hand(side, gripPressedCallback); laser = new Laser(side); @@ -1059,200 +1048,26 @@ otherEditor = editor; } - function getIsEditing(rootEntityID) { - return isEditing && rootEntityID === selection.rootEntityID(); - } - - function getTargetPosition() { - if (isEditingWithHand) { - return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - } - return Vec3.sum(hand.position(), Vec3.multiply(laserEditingDistance, Quat.getUp(hand.orientation()))); - } - - function startEditing() { - var selectionPositionAndOrientation, - initialOtherTargetPosition; - - initialHandOrientationInverse = Quat.inverse(hand.orientation()); - - selection.select(hoveredEntityID); // Entity may have been moved by other hand so refresh position and orientation. - selectionPositionAndOrientation = selection.getPositionAndOrientation(); - initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); - initialSelectionOrientation = selectionPositionAndOrientation.orientation; - - isEditingWithHand = intersection.handSelected; - - if (otherEditor.isEditing(selection.rootEntityID())) { - // Store initial values for use in scaling. - initialTargetPosition = getTargetPosition(); - initialOtherTargetPosition = otherEditor.getTargetPosition(); - initialTargetsCenter = Vec3.multiply(0.5, Vec3.sum(initialTargetPosition, initialOtherTargetPosition)); - initialTargetsSeparation = Vec3.distance(initialTargetPosition, initialOtherTargetPosition); - initialtargetsDirection = Vec3.subtract(initialOtherTargetPosition, initialTargetPosition); - selection.startScaling(initialTargetsCenter); - isScaling = true; - } else { - isScaling = false; - } - - isEditing = true; - } - - function updateGrabOffset(selectionPositionAndOrientation) { - initialHandOrientationInverse = Quat.inverse(hand.orientation()); - initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); - initialSelectionOrientation = selectionPositionAndOrientation.orientation; - } - - function applyGrab() { - var handPosition, - handOrientation, - deltaOrientation, - selectionPosition, - selectionOrientation; - - handPosition = hand.position(); - handOrientation = hand.orientation(); - - deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); - selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); - selectionOrientation = Quat.multiply(deltaOrientation, initialSelectionOrientation); - - selection.setPositionAndOrientation(selectionPosition, selectionOrientation); - } - - function applyScale() { - var targetPosition, - otherTargetPosition, - targetsSeparation, - scale, - rotation, - center, - selectionPositionAndOrientation; - - // Scale selection. - targetPosition = getTargetPosition(); - otherTargetPosition = otherEditor.getTargetPosition(); - targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); - scale = targetsSeparation / initialTargetsSeparation; - rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); - center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); - selection.scale(scale, rotation, center); - - // Update grab offsets. - selectionPositionAndOrientation = selection.getPositionAndOrientation(); - updateGrabOffset(selectionPositionAndOrientation); - otherEditor.updateGrabOffset(selectionPositionAndOrientation); - } - - function stopEditing() { - isScaling = false; - isEditing = false; - } - - function hoverHandle(overlayID) { - handles.hover(overlayID); - } - function update() { - var isHandSelected, - wasEditing; - // Hand update. - // Early return if it's not present. hand.update(); - if (!hand.valid()) { - if (isUpdating) { - laser.clear(); - selection.clear(); - highlights.clear(); - handles.clear(); - hoveredEntityID = null; - isUpdating = false; - } - return; - } - isUpdating = true; intersection = hand.intersection(); - isHandSelected = intersection.intersects; // Laser update. // Displays laser if hand has no intersection and trigger is pressed. - laser.update(hand); - if (!isHandSelected) { - intersection = laser.intersection(); + if (hand.valid()) { + laser.update(hand); + if (!intersection.intersects) { + intersection = laser.intersection(); + } } - intersection.handSelected = isHandSelected; - // Highlight / edit. - doEdit = false; - doHighlight = false; - if (hand.triggerClicked()) { - if (isEditing) { - // Perform edit. - doEdit = true; - } else if (intersection.intersects && intersection.entityID && (!isAppScaleWithHandles - || !otherEditor.isEditing(Entities.rootOf(intersection.entityID)))) { - // Start editing. - if (intersection.entityID !== hoveredEntityID) { - hoveredEntityID = intersection.entityID; - selection.select(hoveredEntityID); - } - highlights.clear(); - if (!intersection.handSelected) { - laserEditingDistance = laser.length(); - laser.setLength(laserEditingDistance); - } - startEditing(); - if (isAppScaleWithHandles) { - handles.display(selection.rootEntityID(), selection.boundingBox(), selection.count() > 1); - } - } - } else { - wasEditing = isEditing; - if (isEditing) { - // Stop editing. - stopEditing(); - laser.clearLength(); - } - if (isAppScaleWithHandles) { - otherEditor.hoverHandle(intersection.overlayID); - } - if (intersection.intersects && intersection.entityID) { - // Hover entities. - if (wasEditing || intersection.entityID !== hoveredEntityID) { - hoveredEntityID = intersection.entityID; - selection.select(hoveredEntityID); - doHighlight = true; - } - } else { - // Unhover entities. - if (hoveredEntityID) { - selection.clear(); - highlights.clear(); - handles.clear(); - hoveredEntityID = null; - } - } - } + // State update. + // TODO } function apply() { - if (doEdit) { - if (otherEditor.isEditing(selection.rootEntityID())) { - if (isScaling && !isAppScaleWithHandles) { - applyScale(); - } - } else { - applyGrab(); - } - } else if (doHighlight) { - if (!isAppScaleWithHandles || !otherEditor.isEditing(selection.rootEntityID())) { - highlights.display(intersection.handSelected, selection.selection(), - otherEditor.isEditing(selection.rootEntityID()) || isAppScaleWithHandles); - } - } + // TODO } function clear() { @@ -1261,10 +1076,6 @@ selection.clear(); highlights.clear(); handles.clear(); - isEditing = false; - isEditingWithHand = false; - isScaling = false; - hoveredEntityID = null; } function destroy() { @@ -1296,10 +1107,6 @@ return { setOtherEditor: setOtherEditor, - isEditing: getIsEditing, - hoverHandle: hoverHandle, - getTargetPosition: getTargetPosition, - updateGrabOffset: updateGrabOffset, update: update, apply: apply, clear: clear, @@ -1312,7 +1119,7 @@ // Main update loop. updateTimer = null; - // Each hand's action depends on the state of the other hand, so update the states first then apply in actions. + // Each hand's action depends on the state of the other hand, so update the states first then apply actions. editors[LEFT_HAND].update(); editors[RIGHT_HAND].update(); editors[LEFT_HAND].apply(); From cb827d9e850eb538de63057951ae445b9f2b2987 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 11:10:50 +1200 Subject: [PATCH 049/504] Idle, searching, and highlighting states --- scripts/vr-edit/vr-edit.js | 138 ++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c4aee6c198..a7ffc49897 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -40,7 +40,9 @@ AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", NULL_UUID = "{00000000-0000-0000-0000-000000000000}", - HALF_TREE_SCALE = 16384; + HALF_TREE_SCALE = 16384, + + DEBUG = true; // TODO: Set false. if (typeof Vec3.min !== "function") { @@ -60,6 +62,9 @@ var rootEntityID, entityProperties, PARENT_PROPERTIES = ["parentID"]; + if (entityID === undefined || entityID === null) { + return null; + } rootEntityID = entityID; entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); while (entityProperties.parentID !== NULL_UUID) { @@ -70,6 +75,11 @@ }; } + + function log(message) { + print(APP_NAME + ": " + message); + } + function isEditableRoot(entityID) { var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], @@ -1029,6 +1039,18 @@ var otherEditor, // Other hand's Editor object. + // Editor states. + EDITOR_IDLE = 0, + EDITOR_SEARCHING = 1, + EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). + editorState = EDITOR_IDLE, + EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING"], + + // State machine. + STATE_MACHINE, + highlightedEntityID = null, + wasAppScaleWithHandles = false, + // Primary objects. hand, laser, @@ -1048,7 +1070,71 @@ otherEditor = editor; } + + function enterEditorIdle() { + selection.clear(); + } + + function exitEditorIdle() { + // Nothing to do. + } + + function enterEditorSearching() { + selection.clear(); + } + + function exitEditorSearching() { + } + + function enterEditorHighlighting() { + selection.select(highlightedEntityID); + highlights.display(intersection.handIntersected, selection.selection(), isAppScaleWithHandles); + } + + function updateEditorHighlighting() { + selection.select(highlightedEntityID); + highlights.display(intersection.handIntersected, selection.selection(), isAppScaleWithHandles); + } + + function exitEditorHighlighting() { + highlights.clear(); + } + + STATE_MACHINE = { + EDITOR_IDLE: { + enter: enterEditorIdle, + update: null, + exit: exitEditorIdle + }, + EDITOR_SEARCHING: { + enter: enterEditorSearching, + update: null, + exit: exitEditorSearching + }, + EDITOR_HIGHLIGHTING: { + enter: enterEditorHighlighting, + update: updateEditorHighlighting, + exit: exitEditorHighlighting + } + }; + + function setState(state) { + if (state !== editorState) { + STATE_MACHINE[EDITOR_STATE_STRINGS[editorState]].exit(); + STATE_MACHINE[EDITOR_STATE_STRINGS[state]].enter(); + editorState = state; + } else if (DEBUG) { + log("ERROR: Null state transition: " + state + "!"); + } + } + + function updateState() { + STATE_MACHINE[EDITOR_STATE_STRINGS[editorState]].update(); + } + function update() { + var previousState = editorState; + // Hand update. hand.update(); intersection = hand.intersection(); @@ -1063,7 +1149,55 @@ } // State update. - // TODO + switch (editorState) { + case EDITOR_IDLE: + if (!hand.valid()) { + // No transition. + break; + } + setState(EDITOR_SEARCHING); + break; + case EDITOR_SEARCHING: + if (hand.valid() && !intersection.entityID) { + // No transition. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (intersection.entityID) { + highlightedEntityID = intersection.entityID; + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_HIGHLIGHTING); + } else { + DEBUG("ERROR: Unexpected condition in EDITOR_SEARCHING!"); + } + break; + case EDITOR_HIGHLIGHTING: + if (hand.valid() && intersection.entityID === highlightedEntityID + && isAppScaleWithHandles === wasAppScaleWithHandles) { + // No transition. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (intersection.entityID && intersection.entityID !== highlightedEntityID) { + highlightedEntityID = intersection.entityID; + wasAppScaleWithHandles = isAppScaleWithHandles; + updateState(); + } else if (intersection.entityID && isAppScaleWithHandles !== wasAppScaleWithHandles) { + wasAppScaleWithHandles = isAppScaleWithHandles; + updateState(); + } else if (!intersection.entityID) { + setState(EDITOR_SEARCHING); + } else { + DEBUG("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); + } + break; + } + + if (DEBUG && editorState !== previousState) { + log((side = LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); + } } function apply() { From 7b3956df28dfbb116535d58f3c3143fb5e018dfb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 12:05:36 +1200 Subject: [PATCH 050/504] Grabbing state --- scripts/vr-edit/vr-edit.js | 95 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a7ffc49897..e916877196 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1043,8 +1043,9 @@ EDITOR_IDLE = 0, EDITOR_SEARCHING = 1, EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). + EDITOR_GRABBING = 3, editorState = EDITOR_IDLE, - EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING"], + EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING"], // State machine. STATE_MACHINE, @@ -1058,6 +1059,11 @@ highlights, handles, + // Position values. + initialHandOrientationInverse, + initialHandToSelectionVector, + initialSelectionOrientation, + intersection; hand = new Hand(side, gripPressedCallback); @@ -1070,6 +1076,36 @@ otherEditor = editor; } + function startEditing() { + var selectionPositionAndOrientation; + + initialHandOrientationInverse = Quat.inverse(hand.orientation()); + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); + initialSelectionOrientation = selectionPositionAndOrientation.orientation; + } + + function stopEditing() { + // Nothing to do. + } + + function applyGrab() { + var handPosition, + handOrientation, + deltaOrientation, + selectionPosition, + selectionOrientation; + + handPosition = hand.position(); + handOrientation = hand.orientation(); + + deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); + selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); + selectionOrientation = Quat.multiply(deltaOrientation, initialSelectionOrientation); + + selection.setPositionAndOrientation(selectionPosition, selectionOrientation); + } + function enterEditorIdle() { selection.clear(); @@ -1100,6 +1136,19 @@ highlights.clear(); } + function enterEditorGrabbing() { + selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + if (intersection.laserIntersected) { + laser.setLength(laser.length()); + } + startEditing(); + } + + function exitEditorGrabbing() { + stopEditing(); + laser.clearLength(); + } + STATE_MACHINE = { EDITOR_IDLE: { enter: enterEditorIdle, @@ -1115,6 +1164,11 @@ enter: enterEditorHighlighting, update: updateEditorHighlighting, exit: exitEditorHighlighting + }, + EDITOR_GRABBING: { + enter: enterEditorGrabbing, + update: null, + exit: exitEditorGrabbing } }; @@ -1132,6 +1186,7 @@ STATE_MACHINE[EDITOR_STATE_STRINGS[editorState]].update(); } + function update() { var previousState = editorState; @@ -1164,22 +1219,28 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (intersection.entityID) { + } else if (intersection.entityID && !hand.triggerClicked()) { highlightedEntityID = intersection.entityID; wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); + } else if (intersection.entityID && hand.triggerClicked()) { + highlightedEntityID = intersection.entityID; + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_GRABBING); } else { DEBUG("ERROR: Unexpected condition in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID === highlightedEntityID - && isAppScaleWithHandles === wasAppScaleWithHandles) { + && !hand.triggerClicked() && isAppScaleWithHandles === wasAppScaleWithHandles) { // No transition. break; } if (!hand.valid()) { setState(EDITOR_IDLE); + } else if (intersection.entityID && hand.triggerClicked()) { + setState(EDITOR_GRABBING); } else if (intersection.entityID && intersection.entityID !== highlightedEntityID) { highlightedEntityID = intersection.entityID; wasAppScaleWithHandles = isAppScaleWithHandles; @@ -1193,15 +1254,39 @@ DEBUG("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); } break; + case EDITOR_GRABBING: + if (hand.valid() && hand.triggerClicked()) { + // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. + // No transition. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!hand.triggerClicked()) { + if (intersection.entityID) { + highlightedEntityID = intersection.entityID; + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_HIGHLIGHTING); + } else { + setState(EDITOR_SEARCHING); + } + } else { + DEBUG("ERROR: Unexpected condition in EDITOR_GRABBING!"); + } + break; } if (DEBUG && editorState !== previousState) { - log((side = LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); + log((side === LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); } } function apply() { - // TODO + switch (editorState) { + case EDITOR_GRABBING: + applyGrab(); + break; + } } function clear() { From 1ad3041bae2c2b088b64b55673fdf9f8759b0ba3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 15:33:39 +1200 Subject: [PATCH 051/504] Direct scaling --- scripts/vr-edit/vr-edit.js | 168 ++++++++++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 13 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e916877196..871354c9f7 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -592,7 +592,7 @@ function startScaling(center) { scaleCenter = center; - scaleRootOffset = Vec3.subtract(selection[0].position, center); + scaleRootOffset = Vec3.subtract(rootPosition, center); scaleRootOrientation = rootOrientation; } @@ -1044,12 +1044,15 @@ EDITOR_SEARCHING = 1, EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). EDITOR_GRABBING = 3, + EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. editorState = EDITOR_IDLE, - EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING"], + EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", + "EDITOR_DIRECT_SCALING"], // State machine. STATE_MACHINE, highlightedEntityID = null, + isOtherEditorEditingEntityID = false, wasAppScaleWithHandles = false, // Primary objects. @@ -1064,6 +1067,13 @@ initialHandToSelectionVector, initialSelectionOrientation, + // Scaling values. + isScaling = false, // Modifies EDITOR_GRABBING state. + initialTargetsSeparation, + initialtargetsDirection, + otherTargetPosition, + isScalingWithHand = false, + intersection; hand = new Hand(side, gripPressedCallback); @@ -1076,6 +1086,12 @@ otherEditor = editor; } + function isEditing(rootEntityID) { + // rootEntityID is an optional parameter. + return editorState > EDITOR_HIGHLIGHTING + && (rootEntityID === undefined || rootEntityID === selection.rootEntityID()); + } + function startEditing() { var selectionPositionAndOrientation; @@ -1089,6 +1105,38 @@ // Nothing to do. } + function getScaleTargetPosition() { + if (isScalingWithHand) { + return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + } + return Vec3.sum(hand.position(), Vec3.multiply(laser.length(), Quat.getUp(hand.orientation()))); + } + + function startScaling(targetPosition) { + var initialTargetPosition, + initialTargetsCenter; + + isScalingWithHand = intersection.handIntersected; + + otherTargetPosition = targetPosition; + initialTargetPosition = getScaleTargetPosition(); + initialTargetsCenter = Vec3.multiply(0.5, Vec3.sum(initialTargetPosition, otherTargetPosition)); + initialTargetsSeparation = Vec3.distance(initialTargetPosition, otherTargetPosition); + initialtargetsDirection = Vec3.subtract(otherTargetPosition, initialTargetPosition); + + selection.startScaling(initialTargetsCenter); + isScaling = true; + } + + function updateScaling(targetPosition) { + otherTargetPosition = targetPosition; + } + + function stopScaling() { + isScaling = false; + } + + function applyGrab() { var handPosition, handOrientation, @@ -1106,6 +1154,29 @@ selection.setPositionAndOrientation(selectionPosition, selectionOrientation); } + function applyScale() { + var targetPosition, + targetsSeparation, + scale, + rotation, + center, + selectionPositionAndOrientation; + + // Scale selection. + targetPosition = getScaleTargetPosition(); + targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); + scale = targetsSeparation / initialTargetsSeparation; + rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); + center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); + selection.scale(scale, rotation, center); + + // Update grab offset. + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + initialHandOrientationInverse = Quat.inverse(hand.orientation()); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); + initialSelectionOrientation = selectionPositionAndOrientation.orientation; + } + function enterEditorIdle() { selection.clear(); @@ -1124,12 +1195,16 @@ function enterEditorHighlighting() { selection.select(highlightedEntityID); - highlights.display(intersection.handIntersected, selection.selection(), isAppScaleWithHandles); + highlights.display(intersection.handIntersected, selection.selection(), + isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); } function updateEditorHighlighting() { selection.select(highlightedEntityID); - highlights.display(intersection.handIntersected, selection.selection(), isAppScaleWithHandles); + highlights.display(intersection.handIntersected, selection.selection(), + isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); } function exitEditorHighlighting() { @@ -1149,6 +1224,24 @@ laser.clearLength(); } + function enterEditorDirectScaling() { + selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + isScalingWithHand = intersection.handIntersected; + if (intersection.laserIntersected) { + laser.setLength(laser.length()); + } + otherEditor.startScaling(getScaleTargetPosition()); + } + + function updateEditorDirectScaling() { + otherEditor.updateScaling(getScaleTargetPosition()); + } + + function exitEditorDirectScaling() { + otherEditor.stopScaling(); + laser.clearLength(); + } + STATE_MACHINE = { EDITOR_IDLE: { enter: enterEditorIdle, @@ -1169,6 +1262,11 @@ enter: enterEditorGrabbing, update: null, exit: exitEditorGrabbing + }, + EDITOR_DIRECT_SCALING: { + enter: enterEditorDirectScaling, + update: updateEditorDirectScaling, + exit: exitEditorDirectScaling } }; @@ -1220,29 +1318,42 @@ if (!hand.valid()) { setState(EDITOR_IDLE); } else if (intersection.entityID && !hand.triggerClicked()) { - highlightedEntityID = intersection.entityID; + highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } else if (intersection.entityID && hand.triggerClicked()) { - highlightedEntityID = intersection.entityID; + highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; - setState(EDITOR_GRABBING); + if (otherEditor.isEditing(highlightedEntityID)) { + setState(EDITOR_DIRECT_SCALING); + } else { + setState(EDITOR_GRABBING); + } } else { DEBUG("ERROR: Unexpected condition in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: - if (hand.valid() && intersection.entityID === highlightedEntityID + if (hand.valid() && Entities.rootOf(intersection.entityID) === highlightedEntityID && !hand.triggerClicked() && isAppScaleWithHandles === wasAppScaleWithHandles) { // No transition. + if (otherEditor.isEditing(highlightedEntityID) !== isOtherEditorEditingEntityID) { + updateState(); + } break; } if (!hand.valid()) { setState(EDITOR_IDLE); } else if (intersection.entityID && hand.triggerClicked()) { - setState(EDITOR_GRABBING); - } else if (intersection.entityID && intersection.entityID !== highlightedEntityID) { - highlightedEntityID = intersection.entityID; + highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. + wasAppScaleWithHandles = isAppScaleWithHandles; + if (otherEditor.isEditing(highlightedEntityID)) { + setState(EDITOR_DIRECT_SCALING); + } else { + setState(EDITOR_GRABBING); + } + } else if (intersection.entityID && Entities.rootOf(intersection.entityID) !== highlightedEntityID) { + highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; updateState(); } else if (intersection.entityID && isAppScaleWithHandles !== wasAppScaleWithHandles) { @@ -1264,7 +1375,7 @@ setState(EDITOR_IDLE); } else if (!hand.triggerClicked()) { if (intersection.entityID) { - highlightedEntityID = intersection.entityID; + highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } else { @@ -1274,6 +1385,29 @@ DEBUG("ERROR: Unexpected condition in EDITOR_GRABBING!"); } break; + case EDITOR_DIRECT_SCALING: + if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { + // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. + // No transition. + updateState(); + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!hand.triggerClicked()) { + if (!intersection.entityID) { + setState(EDITOR_SEARCHING); + } else { + highlightedEntityID = Entities.rootOf(intersection.entityID); + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_HIGHLIGHTING); + } + } else if (!otherEditor.isEditing(highlightedEntityID)) { + // Grab highlightEntityID that was scaling and has already been set. + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_GRABBING); + } + break; } if (DEBUG && editorState !== previousState) { @@ -1284,7 +1418,11 @@ function apply() { switch (editorState) { case EDITOR_GRABBING: - applyGrab(); + if (isScaling) { + applyScale(); + } else { + applyGrab(); + } break; } } @@ -1326,6 +1464,10 @@ return { setOtherEditor: setOtherEditor, + isEditing: isEditing, + startScaling: startScaling, + updateScaling: updateScaling, + stopScaling: stopScaling, update: update, apply: apply, clear: clear, From cb894ccbcb269e2795ec103339a5082f65256f15 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 18:02:35 +1200 Subject: [PATCH 052/504] Display and hover sizing handles --- scripts/vr-edit/vr-edit.js | 66 ++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 871354c9f7..799ffd3bf2 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -80,6 +80,12 @@ print(APP_NAME + ": " + message); } + function debug(message) { + if (DEBUG) { + log(message); + } + } + function isEditableRoot(entityID) { var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], @@ -1086,6 +1092,11 @@ otherEditor = editor; } + function hoverHandle(overlayID) { + // Highlights handle if overlayID is a handle, otherwise unhighlights currently highlighted handle if any. + handles.hover(overlayID); + } + function isEditing(rootEntityID) { // rootEntityID is an optional parameter. return editorState > EDITOR_HIGHLIGHTING @@ -1188,27 +1199,44 @@ function enterEditorSearching() { selection.clear(); + hoveredOverlayID = intersection.overlayID; + otherEditor.hoverHandle(hoveredOverlayID); + } + + function updateEditorSearching() { + if (isAppScaleWithHandles && intersection.overlayID !== hoveredOverlayID && otherEditor.isEditing()) { + hoveredOverlayID = intersection.overlayID; + otherEditor.hoverHandle(hoveredOverlayID); + } } function exitEditorSearching() { + otherEditor.hoverHandle(null); } function enterEditorHighlighting() { selection.select(highlightedEntityID); - highlights.display(intersection.handIntersected, selection.selection(), - isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + if (!isAppScaleWithHandles || !otherEditor.isEditing(highlightedEntityID)) { + highlights.display(intersection.handIntersected, selection.selection(), + isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); } function updateEditorHighlighting() { selection.select(highlightedEntityID); - highlights.display(intersection.handIntersected, selection.selection(), - isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); - isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); + if (!isAppScaleWithHandles || !otherEditor.isEditing(highlightedEntityID)) { + highlights.display(intersection.handIntersected, selection.selection(), + isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + } else { + highlights.clear(); + } + isOtherEditorEditingEntityID = !isOtherEditorEditingEntityID; } function exitEditorHighlighting() { highlights.clear(); + isOtherEditorEditingEntityID = false; } function enterEditorGrabbing() { @@ -1216,11 +1244,23 @@ if (intersection.laserIntersected) { laser.setLength(laser.length()); } + if (isAppScaleWithHandles) { + handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); + } startEditing(); } + function updateEditorGrabbing() { + if (isAppScaleWithHandles) { + handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); + } else { + handles.clear(); + } + } + function exitEditorGrabbing() { stopEditing(); + handles.clear(); laser.clearLength(); } @@ -1250,7 +1290,7 @@ }, EDITOR_SEARCHING: { enter: enterEditorSearching, - update: null, + update: updateEditorSearching, exit: exitEditorSearching }, EDITOR_HIGHLIGHTING: { @@ -1260,7 +1300,7 @@ }, EDITOR_GRABBING: { enter: enterEditorGrabbing, - update: null, + update: updateEditorGrabbing, exit: exitEditorGrabbing }, EDITOR_DIRECT_SCALING: { @@ -1313,6 +1353,7 @@ case EDITOR_SEARCHING: if (hand.valid() && !intersection.entityID) { // No transition. + updateState(); break; } if (!hand.valid()) { @@ -1330,7 +1371,7 @@ setState(EDITOR_GRABBING); } } else { - DEBUG("ERROR: Unexpected condition in EDITOR_SEARCHING!"); + debug("ERROR: Unexpected condition in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: @@ -1362,13 +1403,17 @@ } else if (!intersection.entityID) { setState(EDITOR_SEARCHING); } else { - DEBUG("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); + debug("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); } break; case EDITOR_GRABBING: if (hand.valid() && hand.triggerClicked()) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. + if (isAppScaleWithHandles !== wasAppScaleWithHandles) { + wasAppScaleWithHandles = isAppScaleWithHandles; + updateState(); + } break; } if (!hand.valid()) { @@ -1382,7 +1427,7 @@ setState(EDITOR_SEARCHING); } } else { - DEBUG("ERROR: Unexpected condition in EDITOR_GRABBING!"); + debug("ERROR: Unexpected condition in EDITOR_GRABBING!"); } break; case EDITOR_DIRECT_SCALING: @@ -1464,6 +1509,7 @@ return { setOtherEditor: setOtherEditor, + hoverHandle: hoverHandle, isEditing: isEditing, startScaling: startScaling, updateScaling: updateScaling, From 22422f30597f216d8645bcbdf6228eef49254d1b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 21:29:37 +1200 Subject: [PATCH 053/504] Scaling with handles state and transitions --- scripts/vr-edit/vr-edit.js | 90 +++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 799ffd3bf2..eb97ddf58a 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -296,6 +296,10 @@ }); } + function isHandle(overlayID) { + return cornerHandleOverlays.indexOf(overlayID) !== -1 || faceHandleOverlays.indexOf(overlayID) !== -1; + } + function display(rootEntityID, boundingBox, isMultiple) { var boundingBoxDimensions = boundingBox.dimensions, boundingBoxCenter = boundingBox.center, @@ -423,6 +427,7 @@ return { display: display, + isHandle: isHandle, hover: hover, clear: clear, destroy: destroy @@ -1051,15 +1056,17 @@ EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). EDITOR_GRABBING = 3, EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. + EDITOR_HANDLE_SCALING = 5, // "" editorState = EDITOR_IDLE, EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", - "EDITOR_DIRECT_SCALING"], + "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING"], // State machine. STATE_MACHINE, highlightedEntityID = null, - isOtherEditorEditingEntityID = false, wasAppScaleWithHandles = false, + isOtherEditorEditingEntityID = false, + hoveredOverlayID = null, // Primary objects. hand, @@ -1097,6 +1104,10 @@ handles.hover(overlayID); } + function isHandle(overlayID) { + return handles.isHandle(overlayID); + } + function isEditing(rootEntityID) { // rootEntityID is an optional parameter. return editorState > EDITOR_HIGHLIGHTING @@ -1116,6 +1127,7 @@ // Nothing to do. } + function getScaleTargetPosition() { if (isScalingWithHand) { return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); @@ -1282,6 +1294,27 @@ laser.clearLength(); } + function enterEditorHandleScaling() { + selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + isScalingWithHand = intersection.handIntersected; + if (intersection.laserIntersected) { + laser.setLength(laser.length()); + } + // TODO + //otherEditor.startHandleScaling(getScaleTargetPosition()); + } + + function updateEditorHandleScaling() { + // TODO + //otherEditor.updateScaling(getScaleTargetPosition()); + } + + function exitEditorHandleScaling() { + // TODO + //otherEditor.stopHandleScaling(); + laser.clearLength(); + } + STATE_MACHINE = { EDITOR_IDLE: { enter: enterEditorIdle, @@ -1307,6 +1340,11 @@ enter: enterEditorDirectScaling, update: updateEditorDirectScaling, exit: exitEditorDirectScaling + }, + EDITOR_HANDLE_SCALING: { + enter: enterEditorHandleScaling, + update: updateEditorHandleScaling, + exit: exitEditorHandleScaling } }; @@ -1351,13 +1389,17 @@ setState(EDITOR_SEARCHING); break; case EDITOR_SEARCHING: - if (hand.valid() && !intersection.entityID) { + if (hand.valid() && !intersection.entityID + && !(intersection.overlayID && hand.triggerClicked())) { // No transition. updateState(); break; } if (!hand.valid()) { setState(EDITOR_IDLE); + } else if (intersection.overlayID && hand.triggerClicked() + && otherEditor.isHandle(intersection.overlayID)) { + setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && !hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; @@ -1366,7 +1408,9 @@ highlightedEntityID = Entities.rootOf(intersection.entityID); wasAppScaleWithHandles = isAppScaleWithHandles; if (otherEditor.isEditing(highlightedEntityID)) { - setState(EDITOR_DIRECT_SCALING); + if (!isAppScaleWithHandles) { + setState(EDITOR_DIRECT_SCALING); + } } else { setState(EDITOR_GRABBING); } @@ -1385,11 +1429,16 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); + } else if (intersection.overlayID && hand.triggerClicked() + && otherEditor.isHandle(intersection.overlayID)) { + setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. wasAppScaleWithHandles = isAppScaleWithHandles; if (otherEditor.isEditing(highlightedEntityID)) { - setState(EDITOR_DIRECT_SCALING); + if (!isAppScaleWithHandles) { + setState(EDITOR_DIRECT_SCALING); + } } else { setState(EDITOR_GRABBING); } @@ -1401,6 +1450,7 @@ wasAppScaleWithHandles = isAppScaleWithHandles; updateState(); } else if (!intersection.entityID) { + // Note that this transition includes the case of highlighting a scaling handle. setState(EDITOR_SEARCHING); } else { debug("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); @@ -1433,6 +1483,33 @@ case EDITOR_DIRECT_SCALING: if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. + // Don't test isAppScaleWithHandles because this will eventually be a UI element and so not able to be + // changed while scaling with two hands. + // No transition. + updateState(); + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!hand.triggerClicked()) { + if (!intersection.entityID) { + setState(EDITOR_SEARCHING); + } else { + highlightedEntityID = Entities.rootOf(intersection.entityID); + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_HIGHLIGHTING); + } + } else if (!otherEditor.isEditing(highlightedEntityID)) { + // Grab highlightEntityID that was scaling and has already been set. + wasAppScaleWithHandles = isAppScaleWithHandles; + setState(EDITOR_GRABBING); + } + break; + case EDITOR_HANDLE_SCALING: + if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { + // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. + // Don't test isAppScaleWithHandles because this will eventually be a UI element and so not able to be + // changed while scaling with two hands. // No transition. updateState(); break; @@ -1456,7 +1533,7 @@ } if (DEBUG && editorState !== previousState) { - log((side === LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); + debug((side === LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); } } @@ -1510,6 +1587,7 @@ return { setOtherEditor: setOtherEditor, hoverHandle: hoverHandle, + isHandle: isHandle, isEditing: isEditing, startScaling: startScaling, updateScaling: updateScaling, From 74dccace6a2e8678d1cb64c8d713b463a308ea43 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Jul 2017 21:57:32 +1200 Subject: [PATCH 054/504] Grab handles state while scaling with handles --- scripts/vr-edit/vr-edit.js | 75 ++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index eb97ddf58a..b04e828ec0 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -404,6 +404,30 @@ } } + function grab(overlayID) { + var overlay, + isShowAll = overlayID === null, + color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR, + i, + length; + + for (i = 0, length = cornerHandleOverlays.length; i < length; i += 1) { + overlay = cornerHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isShowAll || overlay === overlayID, + color: color + }); + } + + for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { + overlay = faceHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isShowAll || overlay === overlayID, + color: color + }); + } + } + function clear() { var i; @@ -429,6 +453,7 @@ display: display, isHandle: isHandle, hover: hover, + grab: grab, clear: clear, destroy: destroy }; @@ -1135,7 +1160,7 @@ return Vec3.sum(hand.position(), Vec3.multiply(laser.length(), Quat.getUp(hand.orientation()))); } - function startScaling(targetPosition) { + function startDirectScaling(targetPosition) { var initialTargetPosition, initialTargetsCenter; @@ -1151,14 +1176,32 @@ isScaling = true; } - function updateScaling(targetPosition) { + function updateDirectScaling(targetPosition) { otherTargetPosition = targetPosition; } - function stopScaling() { + function stopDirectScaling() { isScaling = false; } + function startHandleScaling(targetPosition, overlayID) { + // Keep grabbed handle highlighted and hide other handles. + handles.grab(overlayID); + + // TODO + } + + function updateHandleScaling(targetPosition) { + // TODO + } + + function stopHandleScaling() { + // Stop highlighting grabbed handle and resume displaying all handles. + handles.grab(null); + + // TODO + } + function applyGrab() { var handPosition, @@ -1282,15 +1325,15 @@ if (intersection.laserIntersected) { laser.setLength(laser.length()); } - otherEditor.startScaling(getScaleTargetPosition()); + otherEditor.startDirectScaling(getScaleTargetPosition()); } function updateEditorDirectScaling() { - otherEditor.updateScaling(getScaleTargetPosition()); + otherEditor.updateDirectScaling(getScaleTargetPosition()); } function exitEditorDirectScaling() { - otherEditor.stopScaling(); + otherEditor.stopDirectScaling(); laser.clearLength(); } @@ -1300,18 +1343,15 @@ if (intersection.laserIntersected) { laser.setLength(laser.length()); } - // TODO - //otherEditor.startHandleScaling(getScaleTargetPosition()); + otherEditor.startHandleScaling(getScaleTargetPosition(), intersection.overlayID); } function updateEditorHandleScaling() { - // TODO - //otherEditor.updateScaling(getScaleTargetPosition()); + otherEditor.updateHandleScaling(getScaleTargetPosition()); } function exitEditorHandleScaling() { - // TODO - //otherEditor.stopHandleScaling(); + otherEditor.stopHandleScaling(); laser.clearLength(); } @@ -1390,7 +1430,7 @@ break; case EDITOR_SEARCHING: if (hand.valid() && !intersection.entityID - && !(intersection.overlayID && hand.triggerClicked())) { + && !(intersection.overlayID && hand.triggerClicked())) { // No transition. updateState(); break; @@ -1589,9 +1629,12 @@ hoverHandle: hoverHandle, isHandle: isHandle, isEditing: isEditing, - startScaling: startScaling, - updateScaling: updateScaling, - stopScaling: stopScaling, + startDirectScaling: startDirectScaling, + updateDirectScaling: updateDirectScaling, + stopDirectScaling: stopDirectScaling, + startHandleScaling: startHandleScaling, + updateHandleScaling: updateHandleScaling, + stopHandleScaling: stopHandleScaling, update: update, apply: apply, clear: clear, From 718d7a112003c0e47eff7051ba08cd472e3ee6da Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Jul 2017 16:59:54 +1200 Subject: [PATCH 055/504] Scale with handles first pass --- scripts/vr-edit/vr-edit.js | 192 ++++++++++++++++++++++++++++++------- 1 file changed, 160 insertions(+), 32 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index b04e828ec0..aaadf93e61 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -206,6 +206,7 @@ Handles = function () { var boundingBoxOverlay, + boundingBoxDimensions, cornerIndexes = [], cornerHandleOverlays = [], faceHandleOverlays = [], @@ -223,6 +224,7 @@ FACE_HANDLE_OVERLAY_AXES, FACE_HANDLE_OVERLAY_OFFSETS, FACE_HANDLE_OVERLAY_ROTATIONS, + FACE_HANDLE_OVERLAY_SCALE_AXES, ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, hoveredOverlayID = null, @@ -264,6 +266,15 @@ Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) ]; + FACE_HANDLE_OVERLAY_SCALE_AXES = [ + Vec3.UNIT_X, + Vec3.UNIT_X, + Vec3.UNIT_Y, + Vec3.UNIT_Y, + Vec3.UNIT_Z, + Vec3.UNIT_Z + ]; + boundingBoxOverlay = Overlays.addOverlay("cube", { color: BOUNDING_BOX_COLOR, alpha: BOUNDING_BOX_ALPHA, @@ -296,15 +307,37 @@ }); } + function isAxisHandle(overlayID) { + return faceHandleOverlays.indexOf(overlayID) !== -1; + } + + function isCornerHandle(overlayID) { + return cornerHandleOverlays.indexOf(overlayID) !== -1; + } + function isHandle(overlayID) { - return cornerHandleOverlays.indexOf(overlayID) !== -1 || faceHandleOverlays.indexOf(overlayID) !== -1; + return isAxisHandle(overlayID) || isCornerHandle(overlayID); + } + + function scalingAxis(overlayID) { + if (isCornerHandle(overlayID)) { + return Vec3.normalize(Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerHandleOverlays.indexOf(overlayID)], + boundingBoxDimensions)); + } + return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; + } + + function scalingDirections(overlayID) { + if (isCornerHandle(overlayID)) { + return Vec3.ONE; + } + return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; } function display(rootEntityID, boundingBox, isMultiple) { - var boundingBoxDimensions = boundingBox.dimensions, - boundingBoxCenter = boundingBox.center, - boundingBoxLocalCenter = boundingBox.localCenter, - boundingBoxOrientation = boundingBox.orientation, + var boundingBoxCenter, + boundingBoxLocalCenter, + boundingBoxOrientation, cameraPosition, boundingBoxVector, distanceMultiplier, @@ -320,6 +353,11 @@ faceHandleOffsets, i; + boundingBoxDimensions = boundingBox.dimensions; + boundingBoxCenter = boundingBox.center; + boundingBoxLocalCenter = boundingBox.localCenter; + boundingBoxOrientation = boundingBox.orientation; + // Selection bounding box. Overlays.editOverlay(boundingBoxOverlay, { parentID: rootEntityID, @@ -452,6 +490,8 @@ return { display: display, isHandle: isHandle, + scalingAxis: scalingAxis, + scalingDirections: scalingDirections, hover: hover, grab: grab, clear: clear, @@ -467,9 +507,13 @@ rootEntityID = null, rootPosition, rootOrientation, + scaleRootPosition, + scaleRootRegistrationPoint, + scaleRootRegistrationOffset, scaleCenter, scaleRootOffset, scaleRootOrientation, + isRootCenterRegistration, ENTITY_TYPE = "entity"; function traverseEntityTree(id, result) { @@ -626,27 +670,25 @@ }); } - function startScaling(center) { + function startDirectScaling(center) { + // Save initial position and orientation so that can scale relative to these without accumulating float errors. scaleCenter = center; scaleRootOffset = Vec3.subtract(rootPosition, center); scaleRootOrientation = rootOrientation; } - function scale(factor, rotation, center) { - var position, - orientation, - i, + function directScale(factor, rotation, center) { + // Scale, position, and rotate selection. + var i, length; - // Position root. - position = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); - orientation = Quat.multiply(rotation, scaleRootOrientation); - rootPosition = position; - rootOrientation = orientation; + // Scale, position, and orient root. + rootPosition = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); + rootOrientation = Quat.multiply(rotation, scaleRootOrientation); Entities.editEntity(selection[0].id, { dimensions: Vec3.multiply(factor, selection[0].dimensions), - position: position, - rotation: orientation + position: rootPosition, + rotation: rootOrientation }); // Scale and position children. @@ -658,6 +700,34 @@ } } + function startHandleScaling() { + // Save initial position data so that can scale relative to these without accumulating float errors. + scaleRootPosition = rootPosition; + scaleRootRegistrationPoint = selection[0].registrationPoint; + isRootCenterRegistration = Vec3.equal(scaleRootRegistrationPoint, Vec3.HALF); + scaleRootRegistrationOffset = Vec3.subtract(scaleRootRegistrationPoint, Vec3.HALF); + } + + function handleScale(factor) { + // Scale selection about bounding box center. + + // Scale and position root. + if (isRootCenterRegistration) { + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions) + }); + } else { + rootPosition = Vec3.sum(scaleRootPosition, Vec3.multiplyVbyV(factor, scaleRootRegistrationOffset)); + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), + position: rootPosition + }); + } + + // Scale and position children. + // TODO + } + function clear() { selection = []; selectedEntityID = null; @@ -680,8 +750,10 @@ boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, - startScaling: startScaling, - scale: scale, + startDirectScaling: startDirectScaling, + directScale: directScale, + startHandleScaling: startHandleScaling, + handleScale: handleScale, clear: clear, destroy: destroy }; @@ -1106,11 +1178,16 @@ initialSelectionOrientation, // Scaling values. - isScaling = false, // Modifies EDITOR_GRABBING state. + isScalingWithHand = false, + isDirectScaling = false, // Modifies EDITOR_GRABBING state. + isHandleScaling = false, // "" initialTargetsSeparation, initialtargetsDirection, otherTargetPosition, - isScalingWithHand = false, + handleUnitScaleAxis, + handleScaleDirections, + handleHandOffset, + initialHandleDistance, intersection; @@ -1172,8 +1249,8 @@ initialTargetsSeparation = Vec3.distance(initialTargetPosition, otherTargetPosition); initialtargetsDirection = Vec3.subtract(otherTargetPosition, initialTargetPosition); - selection.startScaling(initialTargetsCenter); - isScaling = true; + selection.startDirectScaling(initialTargetsCenter); + isDirectScaling = true; } function updateDirectScaling(targetPosition) { @@ -1181,29 +1258,49 @@ } function stopDirectScaling() { - isScaling = false; + isDirectScaling = false; } function startHandleScaling(targetPosition, overlayID) { + var boundingBox, + scaleAxis, + handDistance; + + isScalingWithHand = intersection.handIntersected; + // Keep grabbed handle highlighted and hide other handles. handles.grab(overlayID); - // TODO + handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. + handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. + + // Distance from handle to bounding box center. + boundingBox = selection.boundingBox(); + initialHandleDistance = Vec3.length(Vec3.multiplyVbyV(boundingBox.dimensions, handleScaleDirections)) / 2; + + // Distance from hand to handle in direction of handle. + otherTargetPosition = targetPosition; + scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); + handDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBox.center), scaleAxis)); + handleHandOffset = handDistance - initialHandleDistance; + + selection.startHandleScaling(); + isHandleScaling = true; } function updateHandleScaling(targetPosition) { - // TODO + otherTargetPosition = targetPosition; } function stopHandleScaling() { // Stop highlighting grabbed handle and resume displaying all handles. handles.grab(null); - - // TODO + isHandleScaling = false; } function applyGrab() { + // Sets position and orientation of selection per grabbing hand. var handPosition, handOrientation, deltaOrientation, @@ -1220,7 +1317,8 @@ selection.setPositionAndOrientation(selectionPosition, selectionOrientation); } - function applyScale() { + function applyDirectScale() { + // Scales, rotates, and positions selection per changing length, orientation, and position of vector between hands. var targetPosition, targetsSeparation, scale, @@ -1234,7 +1332,7 @@ scale = targetsSeparation / initialTargetsSeparation; rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); - selection.scale(scale, rotation, center); + selection.directScale(scale, rotation, center); // Update grab offset. selectionPositionAndOrientation = selection.getPositionAndOrientation(); @@ -1243,6 +1341,34 @@ initialSelectionOrientation = selectionPositionAndOrientation.orientation; } + function applyHandleScale() { + // Scales selection per changing position of scaling hand; positions and orients per grabbing hand. + var boundingBoxCenter, + scaleAxis, + handleDistance, + scale, + scale3D; + + // Position and orient selection per grabbing hand before scaling it per scaling hand. + applyGrab(); + + // Desired distance of handle from center of bounding box. + boundingBoxCenter = selection.boundingBox().center; // TODO: Too expensive for update loop? + scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); + handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBoxCenter), scaleAxis)); + handleDistance -= handleHandOffset; + + // Scale selection relative to initial dimensions. + scale = handleDistance / initialHandleDistance; + scale3D = Vec3.multiply(scale, handleScaleDirections); + scale3D = { + x: scale3D.x !== 0 ? scale3D.x : 1, + y: scale3D.y !== 0 ? scale3D.y : 1, + z: scale3D.z !== 0 ? scale3D.z : 1 + }; + selection.handleScale(scale3D); + } + function enterEditorIdle() { selection.clear(); @@ -1580,8 +1706,10 @@ function apply() { switch (editorState) { case EDITOR_GRABBING: - if (isScaling) { - applyScale(); + if (isDirectScaling) { + applyDirectScale(); + } else if (isHandleScaling) { + applyHandleScale(); } else { applyGrab(); } From 485190456da5f0d5fd73fd5fe4a7bf6fece28cb1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Jul 2017 20:29:02 +1200 Subject: [PATCH 056/504] Update handles when scale --- scripts/vr-edit/vr-edit.js | 63 +++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index aaadf93e61..d0ae3dbe7c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -207,9 +207,11 @@ Handles = function () { var boundingBoxOverlay, boundingBoxDimensions, + boundingBoxLocalCenter, cornerIndexes = [], cornerHandleOverlays = [], faceHandleOverlays = [], + faceHandleOffsets, BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, BOUNDING_BOX_ALPHA = 0.8, HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, @@ -228,6 +230,11 @@ ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, hoveredOverlayID = null, + + // Scaling. + scalingBoundingBoxDimensions, + scalingBoundingBoxLocalCenter, + i; CORNER_HANDLE_OVERLAY_AXES = [ @@ -336,7 +343,6 @@ function display(rootEntityID, boundingBox, isMultiple) { var boundingBoxCenter, - boundingBoxLocalCenter, boundingBoxOrientation, cameraPosition, boundingBoxVector, @@ -350,7 +356,6 @@ leftCornerIndex, cornerHandleDimensions, faceHandleDimensions, - faceHandleOffsets, i; boundingBoxDimensions = boundingBox.dimensions; @@ -427,6 +432,45 @@ } } + function startScaling() { + // Nothing to do. + } + + function scale(scale3D) { + // Scale relative to dimensions and positions at start of scaling. + + // Selection bounding box. + scalingBoundingBoxDimensions = Vec3.multiplyVbyV(scale3D, boundingBoxLocalCenter); + scalingBoundingBoxLocalCenter = Vec3.multiplyVbyV(scale3D, boundingBoxDimensions); + Overlays.editOverlay(boundingBoxOverlay, { + localPosition: scalingBoundingBoxDimensions, + dimensions: scalingBoundingBoxLocalCenter + }); + + // Corner scale handles. + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter)) + }); + } + + // Face scale handles. + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) + }); + } + } + + function finishScaling() { + // Adopt final scale. + boundingBoxLocalCenter = scalingBoundingBoxDimensions; + boundingBoxDimensions = scalingBoundingBoxLocalCenter; + } + function hover(overlayID) { if (overlayID !== hoveredOverlayID) { if (hoveredOverlayID !== null) { @@ -492,6 +536,9 @@ isHandle: isHandle, scalingAxis: scalingAxis, scalingDirections: scalingDirections, + startScaling: startScaling, + scale: scale, + finishScaling: finishScaling, hover: hover, grab: grab, clear: clear, @@ -728,6 +775,10 @@ // TODO } + function finishHandleScaling() { + select(selectedEntityID); // Refresh. + } + function clear() { selection = []; selectedEntityID = null; @@ -754,6 +805,7 @@ directScale: directScale, startHandleScaling: startHandleScaling, handleScale: handleScale, + finishHandleScaling: finishHandleScaling, clear: clear, destroy: destroy }; @@ -1285,6 +1337,7 @@ handleHandOffset = handDistance - initialHandleDistance; selection.startHandleScaling(); + handles.startScaling(); isHandleScaling = true; } @@ -1293,8 +1346,9 @@ } function stopHandleScaling() { - // Stop highlighting grabbed handle and resume displaying all handles. - handles.grab(null); + handles.finishScaling(); + selection.finishHandleScaling(); + handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. isHandleScaling = false; } @@ -1366,6 +1420,7 @@ y: scale3D.y !== 0 ? scale3D.y : 1, z: scale3D.z !== 0 ? scale3D.z : 1 }; + handles.scale(scale3D); selection.handleScale(scale3D); } From 3d69e240a96a3d2a0a4c550ace80a27a1fc91a8a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Jul 2017 20:29:41 +1200 Subject: [PATCH 057/504] Fix entity losing previous scale when resume direct scaling --- scripts/vr-edit/vr-edit.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d0ae3dbe7c..c339a69e40 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -747,6 +747,10 @@ } } + function finishDirectScaling() { + select(selectedEntityID); // Refresh. + } + function startHandleScaling() { // Save initial position data so that can scale relative to these without accumulating float errors. scaleRootPosition = rootPosition; @@ -803,6 +807,7 @@ setPositionAndOrientation: setPositionAndOrientation, startDirectScaling: startDirectScaling, directScale: directScale, + finishDirectScaling: finishDirectScaling, startHandleScaling: startHandleScaling, handleScale: handleScale, finishHandleScaling: finishHandleScaling, @@ -1310,6 +1315,7 @@ } function stopDirectScaling() { + selection.finishDirectScaling(); isDirectScaling = false; } From 4c98cd26a4f89df75e5244c1ec66fe6795a158bb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Jul 2017 20:48:16 +1200 Subject: [PATCH 058/504] Fix corner handle scaling --- scripts/vr-edit/vr-edit.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c339a69e40..2b789ace9b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -327,9 +327,10 @@ } function scalingAxis(overlayID) { + var axesIndex; if (isCornerHandle(overlayID)) { - return Vec3.normalize(Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerHandleOverlays.indexOf(overlayID)], - boundingBoxDimensions)); + axesIndex = CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[cornerHandleOverlays.indexOf(overlayID)]]; + return Vec3.normalize(Vec3.multiplyVbyV(axesIndex, boundingBoxDimensions)); } return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; } From 0f64da23bdef5d9bc1330543a096b8f5a8663fb0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 12:09:52 +1200 Subject: [PATCH 059/504] Improve debug --- scripts/vr-edit/vr-edit.js | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2b789ace9b..0d76aaf14c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -80,9 +80,17 @@ print(APP_NAME + ": " + message); } - function debug(message) { + function debug(side, message) { + // Optional parameter: side. + var hand = "", + HAND_LETTERS = ["L", "R"]; if (DEBUG) { - log(message); + if (side === 0 || side === 1) { + hand = HAND_LETTERS[side] + " "; + } else { + message = side; + } + log(hand + message); } } @@ -204,7 +212,7 @@ }; - Handles = function () { + Handles = function (side) { var boundingBoxOverlay, boundingBoxDimensions, boundingBoxLocalCenter, @@ -529,7 +537,7 @@ } if (!this instanceof Handles) { - return new Handles(); + return new Handles(side); } return { @@ -548,7 +556,7 @@ }; - Selection = function () { + Selection = function (side) { // Manages set of selected entities. Currently supports just one set of linked entities. var selection = [], selectedEntityID = null, @@ -795,7 +803,7 @@ } if (!this instanceof Selection) { - return new Selection(); + return new Selection(side); } return { @@ -1003,7 +1011,7 @@ } if (!this instanceof Laser) { - return new Laser(); + return new Laser(side); } return { @@ -1183,7 +1191,7 @@ } if (!this instanceof Hand) { - return new Hand(); + return new Hand(side); } return { @@ -1251,9 +1259,9 @@ hand = new Hand(side, gripPressedCallback); laser = new Laser(side); - selection = new Selection(); + selection = new Selection(side); highlights = new Highlights(side); - handles = new Handles(); + handles = new Handles(side); function setOtherEditor(editor) { otherEditor = editor; @@ -1643,7 +1651,7 @@ setState(EDITOR_GRABBING); } } else { - debug("ERROR: Unexpected condition in EDITOR_SEARCHING!"); + debug(side, "ERROR: Unexpected condition in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: @@ -1681,7 +1689,7 @@ // Note that this transition includes the case of highlighting a scaling handle. setState(EDITOR_SEARCHING); } else { - debug("ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); + debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); } break; case EDITOR_GRABBING: @@ -1705,7 +1713,7 @@ setState(EDITOR_SEARCHING); } } else { - debug("ERROR: Unexpected condition in EDITOR_GRABBING!"); + debug(side, "ERROR: Unexpected condition in EDITOR_GRABBING!"); } break; case EDITOR_DIRECT_SCALING: @@ -1761,7 +1769,7 @@ } if (DEBUG && editorState !== previousState) { - debug((side === LEFT_HAND ? "L " : "R ") + EDITOR_STATE_STRINGS[editorState]); + debug(side, EDITOR_STATE_STRINGS[editorState]); } } From ce6e711f2d7f71f75583d56c507d403d15bec32f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 12:12:20 +1200 Subject: [PATCH 060/504] Fix old scale handles displaying when switch hands --- scripts/vr-edit/vr-edit.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 0d76aaf14c..326d251659 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -238,6 +238,7 @@ ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, hoveredOverlayID = null, + isVisible = false, // Scaling. scalingBoundingBoxDimensions, @@ -367,6 +368,8 @@ faceHandleDimensions, i; + isVisible = true; + boundingBoxDimensions = boundingBox.dimensions; boundingBoxCenter = boundingBox.center; boundingBoxLocalCenter = boundingBox.localCenter; @@ -505,7 +508,7 @@ for (i = 0, length = cornerHandleOverlays.length; i < length; i += 1) { overlay = cornerHandleOverlays[i]; Overlays.editOverlay(overlay, { - visible: isShowAll || overlay === overlayID, + visible: isVisible && (isShowAll || overlay === overlayID), color: color }); } @@ -513,7 +516,7 @@ for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { overlay = faceHandleOverlays[i]; Overlays.editOverlay(overlay, { - visible: isShowAll || overlay === overlayID, + visible: isVisible && (isShowAll || overlay === overlayID), color: color }); } @@ -522,6 +525,8 @@ function clear() { var i; + isVisible = false; + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { Overlays.editOverlay(cornerHandleOverlays[i], { visible: false }); From 5cb5c71966bf79909a00c94b121f3d1562499138 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 16:33:05 +1200 Subject: [PATCH 061/504] Avoid bounding box center calcs --- scripts/vr-edit/vr-edit.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 326d251659..573446efcd 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1254,11 +1254,13 @@ isHandleScaling = false, // "" initialTargetsSeparation, initialtargetsDirection, + initialTargetToBoundingBoxCenter, otherTargetPosition, handleUnitScaleAxis, handleScaleDirections, handleHandOffset, initialHandleDistance, + initialHandleScaleOrientationInverse, intersection; @@ -1334,24 +1336,33 @@ } function startHandleScaling(targetPosition, overlayID) { - var boundingBox, + var initialTargetPosition, + boundingBox, scaleAxis, handDistance; isScalingWithHand = intersection.handIntersected; + otherTargetPosition = targetPosition; + // Keep grabbed handle highlighted and hide other handles. handles.grab(overlayID); handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. - // Distance from handle to bounding box center. + // Vector from target to bounding box center. + initialTargetPosition = getScaleTargetPosition(); boundingBox = selection.boundingBox(); + initialTargetToBoundingBoxCenter = Vec3.subtract(boundingBox.center, initialTargetPosition); + + // Initial hand orientation. + initialHandleScaleOrientationInverse = Quat.inverse(hand.orientation()); + + // Distance from handle to bounding box center. initialHandleDistance = Vec3.length(Vec3.multiplyVbyV(boundingBox.dimensions, handleScaleDirections)) / 2; // Distance from hand to handle in direction of handle. - otherTargetPosition = targetPosition; scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBox.center), scaleAxis)); handleHandOffset = handDistance - initialHandleDistance; @@ -1417,7 +1428,9 @@ function applyHandleScale() { // Scales selection per changing position of scaling hand; positions and orients per grabbing hand. - var boundingBoxCenter, + var targetPosition, + deltaOrientation, + boundingBoxCenter, scaleAxis, handleDistance, scale, @@ -1427,7 +1440,9 @@ applyGrab(); // Desired distance of handle from center of bounding box. - boundingBoxCenter = selection.boundingBox().center; // TODO: Too expensive for update loop? + targetPosition = getScaleTargetPosition(); + deltaOrientation = Quat.multiply(hand.orientation(), initialHandleScaleOrientationInverse); + boundingBoxCenter = Vec3.sum(targetPosition, Vec3.multiplyQbyV(deltaOrientation, initialTargetToBoundingBoxCenter)); scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBoxCenter), scaleAxis)); handleDistance -= handleHandOffset; From f1fd6264f3b453f422d024f05a93a5aca4b052c1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 17:51:36 +1200 Subject: [PATCH 062/504] Fix handle scaling entity with non-center registration point --- scripts/vr-edit/vr-edit.js | 85 +++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 573446efcd..d7a6779c8e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -568,13 +568,9 @@ rootEntityID = null, rootPosition, rootOrientation, - scaleRootPosition, - scaleRootRegistrationPoint, - scaleRootRegistrationOffset, scaleCenter, scaleRootOffset, scaleRootOrientation, - isRootCenterRegistration, ENTITY_TYPE = "entity"; function traverseEntityTree(id, result) { @@ -766,28 +762,20 @@ } function startHandleScaling() { - // Save initial position data so that can scale relative to these without accumulating float errors. - scaleRootPosition = rootPosition; - scaleRootRegistrationPoint = selection[0].registrationPoint; - isRootCenterRegistration = Vec3.equal(scaleRootRegistrationPoint, Vec3.HALF); - scaleRootRegistrationOffset = Vec3.subtract(scaleRootRegistrationPoint, Vec3.HALF); + // Nothing to do. } - function handleScale(factor) { - // Scale selection about bounding box center. + function handleScale(factor, position, orientation) { + // Scale and reposition and orient selection. // Scale and position root. - if (isRootCenterRegistration) { - Entities.editEntity(selection[0].id, { - dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions) - }); - } else { - rootPosition = Vec3.sum(scaleRootPosition, Vec3.multiplyVbyV(factor, scaleRootRegistrationOffset)); - Entities.editEntity(selection[0].id, { - dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), - position: rootPosition - }); - } + rootPosition = position; + rootOrientation = orientation; + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), + position: rootPosition, + rotation: rootOrientation + }); // Scale and position children. // TODO @@ -1260,7 +1248,9 @@ handleScaleDirections, handleHandOffset, initialHandleDistance, - initialHandleScaleOrientationInverse, + initialHandleOrientationInverse, + initialHandleRegistrationOffset, + initialSelectionOrientationInverse, intersection; @@ -1348,22 +1338,25 @@ // Keep grabbed handle highlighted and hide other handles. handles.grab(overlayID); - handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. - handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. - // Vector from target to bounding box center. initialTargetPosition = getScaleTargetPosition(); boundingBox = selection.boundingBox(); initialTargetToBoundingBoxCenter = Vec3.subtract(boundingBox.center, initialTargetPosition); - // Initial hand orientation. - initialHandleScaleOrientationInverse = Quat.inverse(hand.orientation()); + // Selection information. + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + initialSelectionOrientationInverse = Quat.inverse(selectionPositionAndOrientation.orientation); - // Distance from handle to bounding box center. + // Handle information. + initialHandleOrientationInverse = Quat.inverse(hand.orientation()); + handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. + handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. initialHandleDistance = Vec3.length(Vec3.multiplyVbyV(boundingBox.dimensions, handleScaleDirections)) / 2; + initialHandleRegistrationOffset = Vec3.multiplyQbyV(initialSelectionOrientationInverse, + Vec3.subtract(selectionPositionAndOrientation.position, boundingBox.center)); // Distance from hand to handle in direction of handle. - scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); + scaleAxis = Vec3.multiplyQbyV(selectionPositionAndOrientation.orientation, handleUnitScaleAxis); handDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBox.center), scaleAxis)); handleHandOffset = handDistance - initialHandleDistance; @@ -1429,20 +1422,26 @@ function applyHandleScale() { // Scales selection per changing position of scaling hand; positions and orients per grabbing hand. var targetPosition, - deltaOrientation, + deltaHandOrientation, + deltaHandleOrientation, + selectionPosition, + selectionOrientation, boundingBoxCenter, scaleAxis, handleDistance, scale, - scale3D; + scale3D, + selectionPositionAndOrientation; - // Position and orient selection per grabbing hand before scaling it per scaling hand. - applyGrab(); + // Orient selection per grabbing hand. + deltaHandOrientation = Quat.multiply(hand.orientation(), initialHandOrientationInverse); + selectionOrientation = Quat.multiply(deltaHandOrientation, initialSelectionOrientation); // Desired distance of handle from center of bounding box. targetPosition = getScaleTargetPosition(); - deltaOrientation = Quat.multiply(hand.orientation(), initialHandleScaleOrientationInverse); - boundingBoxCenter = Vec3.sum(targetPosition, Vec3.multiplyQbyV(deltaOrientation, initialTargetToBoundingBoxCenter)); + deltaHandleOrientation = Quat.multiply(hand.orientation(), initialHandleOrientationInverse); + boundingBoxCenter = Vec3.sum(targetPosition, + Vec3.multiplyQbyV(deltaHandleOrientation, initialTargetToBoundingBoxCenter)); scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBoxCenter), scaleAxis)); handleDistance -= handleHandOffset; @@ -1455,8 +1454,20 @@ y: scale3D.y !== 0 ? scale3D.y : 1, z: scale3D.z !== 0 ? scale3D.z : 1 }; + + // Reposition selection per scale. + selectionPosition = Vec3.sum(boundingBoxCenter, + Vec3.multiplyQbyV(selectionOrientation, Vec3.multiplyVbyV(scale3D, initialHandleRegistrationOffset))); + + // Scale. handles.scale(scale3D); - selection.handleScale(scale3D); + selection.handleScale(scale3D, selectionPosition, selectionOrientation); + + // Update grab offset. + selectionPositionAndOrientation = selection.getPositionAndOrientation(); + initialHandOrientationInverse = Quat.inverse(hand.orientation()); + initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); + initialSelectionOrientation = selectionPositionAndOrientation.orientation; } From e4123070c443dbbcaaf8a1a8b08eeb04c96246f5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 18:10:39 +1200 Subject: [PATCH 063/504] Add handle scaling of multiple entities --- scripts/vr-edit/vr-edit.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d7a6779c8e..89ed0756b1 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -767,6 +767,8 @@ function handleScale(factor, position, orientation) { // Scale and reposition and orient selection. + var i, + length; // Scale and position root. rootPosition = position; @@ -778,7 +780,14 @@ }); // Scale and position children. - // TODO + // Only corner handles are used for scaling multiple entities so scale factor is the same in all dimensions. + // Therefore don't need to take into account orientation relative to parent when scaling local position. + for (i = 1, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), + localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) + }); + } } function finishHandleScaling() { From 2bd3f87d73d00842b210b4a32e3f43f735f62d70 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 19 Jul 2017 18:11:45 +1200 Subject: [PATCH 064/504] Tidying --- scripts/vr-edit/vr-edit.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 89ed0756b1..99f160c6a5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1310,6 +1310,7 @@ } function startDirectScaling(targetPosition) { + // Called on grabbing hand by scaling hand. var initialTargetPosition, initialTargetsCenter; @@ -1326,17 +1327,21 @@ } function updateDirectScaling(targetPosition) { + // Called on grabbing hand by scaling hand. otherTargetPosition = targetPosition; } function stopDirectScaling() { + // Called on grabbing hand by scaling hand. selection.finishDirectScaling(); isDirectScaling = false; } function startHandleScaling(targetPosition, overlayID) { + // Called on grabbing hand by scaling hand. var initialTargetPosition, boundingBox, + selectionPositionAndOrientation, scaleAxis, handDistance; @@ -1375,10 +1380,12 @@ } function updateHandleScaling(targetPosition) { + // Called on grabbing hand by scaling hand. otherTargetPosition = targetPosition; } function stopHandleScaling() { + // Called on grabbing hand by scaling hand. handles.finishScaling(); selection.finishHandleScaling(); handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. @@ -1388,17 +1395,12 @@ function applyGrab() { // Sets position and orientation of selection per grabbing hand. - var handPosition, - handOrientation, - deltaOrientation, + var deltaOrientation, selectionPosition, selectionOrientation; - handPosition = hand.position(); - handOrientation = hand.orientation(); - - deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse); - selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); + deltaOrientation = Quat.multiply(hand.orientation(), initialHandOrientationInverse); + selectionPosition = Vec3.sum(hand.position(), Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector)); selectionOrientation = Quat.multiply(deltaOrientation, initialSelectionOrientation); selection.setPositionAndOrientation(selectionPosition, selectionOrientation); From 5551bb70d42994fbb0ab3852153b335c4f609567 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 18:33:23 +1200 Subject: [PATCH 065/504] Disable collisions and dynamic behavior while entities are being moved --- scripts/vr-edit/vr-edit.js | 56 +++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 99f160c6a5..832de4ea5e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -577,7 +577,8 @@ // Recursively traverses tree of entities and their children, gather IDs and properties. var children, properties, - SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition"], + SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition", + "dynamic", "collisionless"], i, length; @@ -588,7 +589,9 @@ localPosition: properties.localPosition, registrationPoint: properties.registrationPoint, rotation: properties.rotation, - dimensions: properties.dimensions + dimensions: properties.dimensions, + dynamic: properties.dynamic, + collisionless: properties.collisionless }); children = Entities.getChildrenIDs(id); @@ -601,7 +604,7 @@ function select(entityID) { var entityProperties, - PARENT_PROPERTIES = ["parentID", "position", "rotation"]; + PARENT_PROPERTIES = ["parentID", "position", "rotation", "dymamic", "collisionless"]; // Find root parent. rootEntityID = Entities.rootOf(entityID); @@ -709,6 +712,47 @@ }; } + function startEditing() { + var count, + i; + + // Disable entity set's physics. + for (i = 0, count = selection.length; i < count; i += 1) { + Entities.editEntity(selection[i].id, { + dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. + collisionless: true // So that entity doesn't bump us about as we resize the entity. + }); + } + } + + function finishEditing() { + var firstDynamicEntityID = null, + properties, + VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }, + count, + i; + + // Restore entity set's physics. + for (i = 0, count = selection.length; i < count; i += 1) { + if (firstDynamicEntityID === null && selection[i].dynamic) { + firstDynamicEntityID = selection[i].id; + } + Entities.editEntity(selection[i].id, { + dynamic: selection[i].dynamic, + collisionless: selection[i].collisionless + }); + } + + // If dynamic with gravity, and velocity is zero, give the entity set a little kick to set off physics. + if (firstDynamicEntityID) { + properties = Entities.getEntityProperties(firstDynamicEntityID, ["velocity", "gravity"]); + if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < VELOCITY_THRESHOLD) { + Entities.editEntity(firstDynamicEntityID, { velocity: VELOCITY_KICK }); + } + } + } + function getPositionAndOrientation() { // Position and orientation of root entity. return { @@ -816,12 +860,14 @@ boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, + startEditing: startEditing, startDirectScaling: startDirectScaling, directScale: directScale, finishDirectScaling: finishDirectScaling, startHandleScaling: startHandleScaling, handleScale: handleScale, finishHandleScaling: finishHandleScaling, + finishEditing: finishEditing, clear: clear, destroy: destroy }; @@ -1295,10 +1341,12 @@ selectionPositionAndOrientation = selection.getPositionAndOrientation(); initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, hand.position()); initialSelectionOrientation = selectionPositionAndOrientation.orientation; + + selection.startEditing(); } function stopEditing() { - // Nothing to do. + selection.finishEditing(); } From 880a711d2bf36fe43490149a6a50b3b7ecd88bd8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 18:36:02 +1200 Subject: [PATCH 066/504] Prevent invalid direct / handle scaling transition while scaling --- scripts/vr-edit/vr-edit.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 832de4ea5e..e440335be1 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1334,6 +1334,10 @@ && (rootEntityID === undefined || rootEntityID === selection.rootEntityID()); } + function isScaling() { + return editorState === EDITOR_DIRECT_SCALING || editorState === EDITOR_HANDLE_SCALING; + } + function startEditing() { var selectionPositionAndOrientation; @@ -1917,6 +1921,7 @@ hoverHandle: hoverHandle, isHandle: isHandle, isEditing: isEditing, + isScaling: isScaling, startDirectScaling: startDirectScaling, updateDirectScaling: updateDirectScaling, stopDirectScaling: stopDirectScaling, @@ -1966,7 +1971,10 @@ } function onGripClicked() { - isAppScaleWithHandles = !isAppScaleWithHandles; + // Do not change scale mode if are currently scaling. + if (!editors[LEFT_HAND].isScaling() && !editors[RIGHT_HAND].isScaling()) { + isAppScaleWithHandles = !isAppScaleWithHandles; + } } From 571d10fa89f9d513bafde01c2c44fb42970f5113 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 21:05:21 +1200 Subject: [PATCH 067/504] Improve editor state update code --- scripts/vr-edit/vr-edit.js | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e440335be1..c6807002c5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1566,6 +1566,7 @@ isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); + wasAppScaleWithHandles = isAppScaleWithHandles; } function updateEditorHighlighting() { @@ -1593,6 +1594,7 @@ handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); } startEditing(); + wasAppScaleWithHandles = isAppScaleWithHandles; } function updateEditorGrabbing() { @@ -1694,7 +1696,8 @@ function update() { - var previousState = editorState; + var previousState = editorState, + doUpdateState; // Hand update. hand.update(); @@ -1732,11 +1735,9 @@ setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && !hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } else if (intersection.entityID && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; if (otherEditor.isEditing(highlightedEntityID)) { if (!isAppScaleWithHandles) { setState(EDITOR_DIRECT_SCALING); @@ -1749,10 +1750,24 @@ } break; case EDITOR_HIGHLIGHTING: - if (hand.valid() && Entities.rootOf(intersection.entityID) === highlightedEntityID - && !hand.triggerClicked() && isAppScaleWithHandles === wasAppScaleWithHandles) { + if (hand.valid() + && intersection.entityID + && !(hand.triggerClicked() && (!otherEditor.isEditing(highlightedEntityID) || !isAppScaleWithHandles)) + && !(hand.triggerClicked() && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { // No transition. + doUpdateState = false; if (otherEditor.isEditing(highlightedEntityID) !== isOtherEditorEditingEntityID) { + doUpdateState = true; + } + if (Entities.rootOf(intersection.entityID) !== highlightedEntityID) { + highlightedEntityID = Entities.rootOf(intersection.entityID); + doUpdateState = true; + } + if (isAppScaleWithHandles !== wasAppScaleWithHandles) { + wasAppScaleWithHandles = isAppScaleWithHandles; + doUpdateState = true; + } + if (doUpdateState) { updateState(); } break; @@ -1764,26 +1779,19 @@ setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. - wasAppScaleWithHandles = isAppScaleWithHandles; if (otherEditor.isEditing(highlightedEntityID)) { if (!isAppScaleWithHandles) { setState(EDITOR_DIRECT_SCALING); + } else { + debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! A"); } } else { setState(EDITOR_GRABBING); } - } else if (intersection.entityID && Entities.rootOf(intersection.entityID) !== highlightedEntityID) { - highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; - updateState(); - } else if (intersection.entityID && isAppScaleWithHandles !== wasAppScaleWithHandles) { - wasAppScaleWithHandles = isAppScaleWithHandles; - updateState(); } else if (!intersection.entityID) { - // Note that this transition includes the case of highlighting a scaling handle. setState(EDITOR_SEARCHING); } else { - debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING!"); + debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! B"); } break; case EDITOR_GRABBING: @@ -1791,8 +1799,8 @@ // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (isAppScaleWithHandles !== wasAppScaleWithHandles) { - wasAppScaleWithHandles = isAppScaleWithHandles; updateState(); + wasAppScaleWithHandles = isAppScaleWithHandles; } break; } @@ -1801,7 +1809,6 @@ } else if (!hand.triggerClicked()) { if (intersection.entityID) { highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } else { setState(EDITOR_SEARCHING); @@ -1826,12 +1833,10 @@ setState(EDITOR_SEARCHING); } else { highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } } else if (!otherEditor.isEditing(highlightedEntityID)) { // Grab highlightEntityID that was scaling and has already been set. - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_GRABBING); } break; @@ -1851,12 +1856,10 @@ setState(EDITOR_SEARCHING); } else { highlightedEntityID = Entities.rootOf(intersection.entityID); - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_HIGHLIGHTING); } } else if (!otherEditor.isEditing(highlightedEntityID)) { // Grab highlightEntityID that was scaling and has already been set. - wasAppScaleWithHandles = isAppScaleWithHandles; setState(EDITOR_GRABBING); } break; From 3b966072a34470f2a6d7956936dcb64df277ab5e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 21:30:39 +1200 Subject: [PATCH 068/504] Fix grabbing a handle without hovering entity beforehand --- scripts/vr-edit/vr-edit.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c6807002c5..4a1ee92976 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1338,6 +1338,11 @@ return editorState === EDITOR_DIRECT_SCALING || editorState === EDITOR_HANDLE_SCALING; } + function rootEntityID() { + return selection.rootEntityID(); + } + + function startEditing() { var selectionPositionAndOrientation; @@ -1612,7 +1617,7 @@ } function enterEditorDirectScaling() { - selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(highlightedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -1630,7 +1635,7 @@ } function enterEditorHandleScaling() { - selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(highlightedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -1732,6 +1737,7 @@ setState(EDITOR_IDLE); } else if (intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID)) { + highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && !hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); @@ -1776,6 +1782,7 @@ setState(EDITOR_IDLE); } else if (intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID)) { + highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. @@ -1818,7 +1825,8 @@ } break; case EDITOR_DIRECT_SCALING: - if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { + if (hand.valid() && hand.triggerClicked() + && (otherEditor.isEditing(highlightedEntityID) || otherEditor.isHandle(intersection.overlayID))) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // Don't test isAppScaleWithHandles because this will eventually be a UI element and so not able to be // changed while scaling with two hands. @@ -1925,6 +1933,7 @@ isHandle: isHandle, isEditing: isEditing, isScaling: isScaling, + rootEntityID: rootEntityID, startDirectScaling: startDirectScaling, updateDirectScaling: updateDirectScaling, stopDirectScaling: stopDirectScaling, From 1009b676001dca98ff7f1b173d0873dde62e6061 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 21:38:03 +1200 Subject: [PATCH 069/504] Fix unhandled state condition --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4a1ee92976..b1ae749e8e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1728,7 +1728,7 @@ break; case EDITOR_SEARCHING: if (hand.valid() && !intersection.entityID - && !(intersection.overlayID && hand.triggerClicked())) { + && !(intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID))) { // No transition. updateState(); break; From da97662ee186e688dd3e597373c919ae254f4e48 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 22:23:48 +1200 Subject: [PATCH 070/504] Make hovered handles brighter --- scripts/vr-edit/vr-edit.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index b1ae749e8e..8abc742485 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -224,7 +224,8 @@ BOUNDING_BOX_ALPHA = 0.8, HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 }, - HANDLE_ALPHA = 0.7, + HANDLE_NORMAL_ALPHA = 0.7, + HANDLE_HOVER_ALPHA = 0.9, NUM_CORNERS = 8, NUM_CORNER_HANDLES = 2, CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, @@ -303,7 +304,7 @@ for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_ALPHA, + alpha: HANDLE_NORMAL_ALPHA, solid: true, drawInFront: true, ignoreRayIntersection: false, @@ -315,7 +316,7 @@ faceHandleOverlays[i] = Overlays.addOverlay("shape", { shape: "Cone", color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_ALPHA, + alpha: HANDLE_NORMAL_ALPHA, solid: true, drawInFront: true, ignoreRayIntersection: false, @@ -492,7 +493,10 @@ if (overlayID !== null && (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) { - Overlays.editOverlay(overlayID, { color: HANDLE_HOVER_COLOR }); + Overlays.editOverlay(overlayID, { + color: HANDLE_HOVER_COLOR, + alpha: HANDLE_HOVER_ALPHA + }); hoveredOverlayID = overlayID; } } @@ -502,6 +506,7 @@ var overlay, isShowAll = overlayID === null, color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR, + alpha = isShowAll ? HANDLE_NORMAL_ALPHA : HANDLE_HOVER_ALPHA, i, length; @@ -509,7 +514,8 @@ overlay = cornerHandleOverlays[i]; Overlays.editOverlay(overlay, { visible: isVisible && (isShowAll || overlay === overlayID), - color: color + color: color, + alpha: alpha }); } @@ -517,7 +523,8 @@ overlay = faceHandleOverlays[i]; Overlays.editOverlay(overlay, { visible: isVisible && (isShowAll || overlay === overlayID), - color: color + color: color, + alpha: alpha }); } } From e2cace2372fecba1c6dcfe8329bde219d0e12757 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 20 Jul 2017 22:29:13 +1200 Subject: [PATCH 071/504] Fix laser not turning off when lose controller tracking --- scripts/vr-edit/vr-edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 8abc742485..5954e36812 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1547,6 +1547,7 @@ function enterEditorIdle() { + laser.clear(); selection.clear(); } From e09113fef5386516ace7f6a7fb3861ec2adda5f8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 13:18:10 +1200 Subject: [PATCH 072/504] Fix handle scaling down to very small dimensions --- scripts/vr-edit/vr-edit.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 5954e36812..5a57323253 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1313,6 +1313,7 @@ initialHandleOrientationInverse, initialHandleRegistrationOffset, initialSelectionOrientationInverse, + MIN_SCALE = 0.001, intersection; @@ -1520,14 +1521,15 @@ scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBoxCenter), scaleAxis)); handleDistance -= handleHandOffset; + handleDistance = Math.max(handleDistance, MIN_SCALE); // Scale selection relative to initial dimensions. scale = handleDistance / initialHandleDistance; scale3D = Vec3.multiply(scale, handleScaleDirections); scale3D = { - x: scale3D.x !== 0 ? scale3D.x : 1, - y: scale3D.y !== 0 ? scale3D.y : 1, - z: scale3D.z !== 0 ? scale3D.z : 1 + x: handleScaleDirections.x !== 0 ? scale3D.x : 1, + y: handleScaleDirections.y !== 0 ? scale3D.y : 1, + z: handleScaleDirections.z !== 0 ? scale3D.z : 1 }; // Reposition selection per scale. From cf51c546d3905c75a13539fbe63557189dcb99b7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 13:43:20 +1200 Subject: [PATCH 073/504] Fix direct scaling scale and position --- scripts/vr-edit/vr-edit.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 5a57323253..268619b6e8 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1055,6 +1055,10 @@ return laserLength; } + function handOffset() { + return GRAB_POINT_SPHERE_OFFSET; + } + function clear() { isLaserOn = false; hide(); @@ -1075,6 +1079,7 @@ setLength: setLength, clearLength: clearLength, length: getLength, + handOffset: handOffset, clear: clear, destroy: destroy }; @@ -1313,6 +1318,7 @@ initialHandleOrientationInverse, initialHandleRegistrationOffset, initialSelectionOrientationInverse, + laserOffset, MIN_SCALE = 0.001, intersection; @@ -1323,6 +1329,8 @@ highlights = new Highlights(side); handles = new Handles(side); + laserOffset = laser.handOffset(); + function setOtherEditor(editor) { otherEditor = editor; } @@ -1371,7 +1379,8 @@ if (isScalingWithHand) { return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); } - return Vec3.sum(hand.position(), Vec3.multiply(laser.length(), Quat.getUp(hand.orientation()))); + return Vec3.sum(Vec3.sum(hand.position(), Vec3.multiplyQbyV(hand.orientation(), laserOffset)), + Vec3.multiply(laser.length(), Quat.getUp(hand.orientation()))); } function startDirectScaling(targetPosition) { @@ -1484,6 +1493,8 @@ targetPosition = getScaleTargetPosition(); targetsSeparation = Vec3.distance(targetPosition, otherTargetPosition); scale = targetsSeparation / initialTargetsSeparation; + scale = Math.max(scale, MIN_SCALE); + rotation = Quat.rotationBetween(initialtargetsDirection, Vec3.subtract(otherTargetPosition, targetPosition)); center = Vec3.multiply(0.5, Vec3.sum(targetPosition, otherTargetPosition)); selection.directScale(scale, rotation, center); From 1434f98dab1066595c67c17633a21ca11894e214 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 13:49:26 +1200 Subject: [PATCH 074/504] Fix multi-selection scale handles box after toggling while grabbed --- scripts/vr-edit/vr-edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 268619b6e8..d50c3b3b33 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1624,6 +1624,7 @@ } function updateEditorGrabbing() { + selection.select(highlightedEntityID); if (isAppScaleWithHandles) { handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); } else { From 218b13b0e9bb4cbd79daf89541593cee7920207f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 14:17:50 +1200 Subject: [PATCH 075/504] Turn off laser when grabbing or scaling with hand --- scripts/vr-edit/vr-edit.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d50c3b3b33..cf07bbf0f3 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -885,7 +885,8 @@ // Draws hand lasers. // May intersect with entities or bounding box of other hand's selection. - var isLaserOn = false, + var isLaserEnabled = true, + isLaserOn = false, laserLine = null, laserSphere = null, @@ -1000,6 +1001,10 @@ deltaOrigin, pickRay; + if (!isLaserEnabled) { + return; + } + if (!hand.intersection().intersects && hand.triggerPressed()) { handPosition = hand.position(); handOrientation = hand.orientation(); @@ -1064,6 +1069,18 @@ hide(); } + function enable() { + isLaserEnabled = true; + } + + function disable() { + isLaserEnabled = false; + if (isLaserOn) { + hide(); + } + isLaserOn = false; + } + function destroy() { Overlays.deleteOverlay(laserLine); Overlays.deleteOverlay(laserSphere); @@ -1079,6 +1096,8 @@ setLength: setLength, clearLength: clearLength, length: getLength, + enable: enable, + disable: disable, handOffset: handOffset, clear: clear, destroy: destroy @@ -1615,6 +1634,8 @@ selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. if (intersection.laserIntersected) { laser.setLength(laser.length()); + } else { + laser.disable(); } if (isAppScaleWithHandles) { handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); @@ -1636,6 +1657,7 @@ stopEditing(); handles.clear(); laser.clearLength(); + laser.enable(); } function enterEditorDirectScaling() { @@ -1643,6 +1665,8 @@ isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); + } else { + laser.disable(); } otherEditor.startDirectScaling(getScaleTargetPosition()); } @@ -1654,6 +1678,7 @@ function exitEditorDirectScaling() { otherEditor.stopDirectScaling(); laser.clearLength(); + laser.enable(); } function enterEditorHandleScaling() { @@ -1661,6 +1686,8 @@ isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); + } else { + laser.disable(); } otherEditor.startHandleScaling(getScaleTargetPosition(), intersection.overlayID); } @@ -1672,6 +1699,7 @@ function exitEditorHandleScaling() { otherEditor.stopHandleScaling(); laser.clearLength(); + laser.enable(); } STATE_MACHINE = { From f858abcf7cbf96c651422f7e83dcbc8fd25aa1d2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 14:31:15 +1200 Subject: [PATCH 076/504] Resume app from idle state --- scripts/vr-edit/vr-edit.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index cf07bbf0f3..29d59bd029 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1943,6 +1943,8 @@ } function clear() { + setState(EDITOR_IDLE); + hand.clear(); laser.clear(); selection.clear(); From 7e4e68ccf97fce37a9463eedc19bbc530c52b549 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 21 Jul 2017 14:52:24 +1200 Subject: [PATCH 077/504] Fix highlight overlay sometimes being out of position --- scripts/vr-edit/vr-edit.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 29d59bd029..f095b21331 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -115,7 +115,8 @@ HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, - HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }; + HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO); handOverlay = Overlays.addOverlay("sphere", { dimensions: HAND_HIGHLIGHT_DIMENSIONS, @@ -144,13 +145,12 @@ } function editEntityOverlay(index, details, overlayColor) { - var offset = Vec3.multiplyQbyV(details.rotation, - Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions)); + var offset = Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions); Overlays.editOverlay(entityOverlays[index], { parentID: details.id, - position: Vec3.sum(details.position, offset), - rotation: details.rotation, + localPosition: offset, + localRotation: ZERO_ROTATION, dimensions: details.dimensions, color: overlayColor, visible: true From 233655b76bd4056a29b8b1e8b8a2894eed18d03c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 22 Jul 2017 11:20:17 +1200 Subject: [PATCH 078/504] Split script into multiple files --- scripts/vr-edit/modules/hand.js | 200 ++++ scripts/vr-edit/modules/handles.js | 372 +++++++ scripts/vr-edit/modules/highlights.js | 123 +++ scripts/vr-edit/modules/laser.js | 241 +++++ scripts/vr-edit/modules/selection.js | 328 +++++++ scripts/vr-edit/utilities/utilities.js | 51 + scripts/vr-edit/vr-edit.js | 1246 +----------------------- 7 files changed, 1330 insertions(+), 1231 deletions(-) create mode 100644 scripts/vr-edit/modules/hand.js create mode 100644 scripts/vr-edit/modules/handles.js create mode 100644 scripts/vr-edit/modules/highlights.js create mode 100644 scripts/vr-edit/modules/laser.js create mode 100644 scripts/vr-edit/modules/selection.js create mode 100644 scripts/vr-edit/utilities/utilities.js diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js new file mode 100644 index 0000000000..a81c54e2cb --- /dev/null +++ b/scripts/vr-edit/modules/hand.js @@ -0,0 +1,200 @@ +// +// hand.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +/* global Hand */ + +Hand = function (side, gripPressedCallback) { + + "use strict"; + + // Hand controller input. + var handController, + controllerTrigger, + controllerTriggerClicked, + controllerGrip, + + isGripPressed = false, + GRIP_ON_VALUE = 0.99, + GRIP_OFF_VALUE = 0.95, + + isTriggerPressed, + isTriggerClicked, + TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. + TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + + NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + NEAR_HOVER_RADIUS = 0.025, + + LEFT_HAND = 0, + HALF_TREE_SCALE = 16384, + + handPose, + handPosition, + handOrientation, + + intersection = {}; + + if (side === LEFT_HAND) { + handController = Controller.Standard.LeftHand; + controllerTrigger = Controller.Standard.LT; + controllerTriggerClicked = Controller.Standard.LTClick; + controllerGrip = Controller.Standard.LeftGrip; + } else { + handController = Controller.Standard.RightHand; + controllerTrigger = Controller.Standard.RT; + controllerTriggerClicked = Controller.Standard.RTClick; + controllerGrip = Controller.Standard.RightGrip; + } + + function valid() { + return handPose.valid; + } + + function position() { + return handPosition; + } + + function orientation() { + return handOrientation; + } + + function triggerPressed() { + return isTriggerPressed; + } + + function triggerClicked() { + return isTriggerClicked; + } + + function getIntersection() { + return intersection; + } + + function update() { + var gripValue, + palmPosition, + overlayID, + overlayIDs, + overlayDistance, + distance, + entityID, + entityIDs, + entitySize, + size, + i, + length; + + + // Hand pose. + handPose = Controller.getPoseValue(handController); + if (!handPose.valid) { + intersection = {}; + return; + } + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + + // Controller trigger. + isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed + ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); + isTriggerClicked = Controller.getValue(controllerTriggerClicked); + + // Controller grip. + gripValue = Controller.getValue(controllerGrip); + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF_VALUE; + } else { + isGripPressed = gripValue > GRIP_ON_VALUE; + if (isGripPressed) { + gripPressedCallback(); + } + } + + // Hand-overlay intersection, if any. + overlayID = null; + palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); + if (overlayIDs.length > 0) { + // Typically, there will be only one overlay; optimize for that case. + overlayID = overlayIDs[0]; + if (overlayIDs.length > 1) { + // Find closest overlay. + overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); + for (i = 1, length = overlayIDs.length; i < length; i += 1) { + distance = + Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); + if (distance > overlayDistance) { + overlayID = overlayIDs[i]; + overlayDistance = distance; + } + } + } + } + + // Hand-entity intersection, if any, if overlay not intersected. + entityID = null; + if (overlayID === null) { + // palmPosition is set above. + entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); + if (entityIDs.length > 0) { + // Typically, there will be only one entity; optimize for that case. + if (Entities.hasEditableRoot(entityIDs[0])) { + entityID = entityIDs[0]; + } + if (entityIDs.length > 1) { + // Find smallest, editable entity. + entitySize = HALF_TREE_SCALE; + for (i = 0, length = entityIDs.length; i < length; i += 1) { + if (Entities.hasEditableRoot(entityIDs[i])) { + size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); + if (size < entitySize) { + entityID = entityIDs[i]; + entitySize = size; + } + } + } + } + } + } + + intersection = { + intersects: overlayID !== null || entityID !== null, + overlayID: overlayID, + entityID: entityID, + handIntersected: true + }; + } + + function clear() { + // Nothing to do. + } + + function destroy() { + // Nothing to do. + } + + if (!this instanceof Hand) { + return new Hand(side); + } + + return { + valid: valid, + position: position, + orientation: orientation, + triggerPressed: triggerPressed, + triggerClicked: triggerClicked, + intersection: getIntersection, + update: update, + clear: clear, + destroy: destroy + }; +}; + +Hand.prototype = {}; diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js new file mode 100644 index 0000000000..6f238ccb79 --- /dev/null +++ b/scripts/vr-edit/modules/handles.js @@ -0,0 +1,372 @@ +// +// handles.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +/* global Handles */ + +Handles = function (side) { + // Draws scaling handles. + + "use strict"; + + var boundingBoxOverlay, + boundingBoxDimensions, + boundingBoxLocalCenter, + cornerIndexes = [], + cornerHandleOverlays = [], + faceHandleOverlays = [], + faceHandleOffsets, + BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, + BOUNDING_BOX_ALPHA = 0.8, + HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, + HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 }, + HANDLE_NORMAL_ALPHA = 0.7, + HANDLE_HOVER_ALPHA = 0.9, + NUM_CORNERS = 8, + NUM_CORNER_HANDLES = 2, + CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, + CORNER_HANDLE_OVERLAY_AXES, + NUM_FACE_HANDLES = 6, + FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 }, + FACE_HANDLE_OVERLAY_AXES, + FACE_HANDLE_OVERLAY_OFFSETS, + FACE_HANDLE_OVERLAY_ROTATIONS, + FACE_HANDLE_OVERLAY_SCALE_AXES, + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, + hoveredOverlayID = null, + isVisible = false, + + // Scaling. + scalingBoundingBoxDimensions, + scalingBoundingBoxLocalCenter, + + i; + + CORNER_HANDLE_OVERLAY_AXES = [ + // Ordered such that items 4 apart are opposite corners - used in display(). + { x: -0.5, y: -0.5, z: -0.5 }, + { x: -0.5, y: -0.5, z: 0.5 }, + { x: -0.5, y: 0.5, z: -0.5 }, + { x: -0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: -0.5 }, + { x: 0.5, y: -0.5, z: 0.5 }, + { x: 0.5, y: -0.5, z: -0.5 } + ]; + + FACE_HANDLE_OVERLAY_AXES = [ + { x: -0.5, y: 0, z: 0 }, + { x: 0.5, y: 0, z: 0 }, + { x: 0, y: -0.5, z: 0 }, + { x: 0, y: 0.5, z: 0 }, + { x: 0, y: 0, z: -0.5 }, + { x: 0, y: 0, z: 0.5 } + ]; + + FACE_HANDLE_OVERLAY_OFFSETS = { + x: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + y: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + z: FACE_HANDLE_OVERLAY_DIMENSIONS.y + }; + + FACE_HANDLE_OVERLAY_ROTATIONS = [ + Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + Quat.fromVec3Degrees({ x: 180, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + ]; + + FACE_HANDLE_OVERLAY_SCALE_AXES = [ + Vec3.UNIT_X, + Vec3.UNIT_X, + Vec3.UNIT_Y, + Vec3.UNIT_Y, + Vec3.UNIT_Z, + Vec3.UNIT_Z + ]; + + boundingBoxOverlay = Overlays.addOverlay("cube", { + color: BOUNDING_BOX_COLOR, + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, + visible: false + }); + } + + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + faceHandleOverlays[i] = Overlays.addOverlay("shape", { + shape: "Cone", + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, + visible: false + }); + } + + function isAxisHandle(overlayID) { + return faceHandleOverlays.indexOf(overlayID) !== -1; + } + + function isCornerHandle(overlayID) { + return cornerHandleOverlays.indexOf(overlayID) !== -1; + } + + function isHandle(overlayID) { + return isAxisHandle(overlayID) || isCornerHandle(overlayID); + } + + function scalingAxis(overlayID) { + var axesIndex; + if (isCornerHandle(overlayID)) { + axesIndex = CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[cornerHandleOverlays.indexOf(overlayID)]]; + return Vec3.normalize(Vec3.multiplyVbyV(axesIndex, boundingBoxDimensions)); + } + return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; + } + + function scalingDirections(overlayID) { + if (isCornerHandle(overlayID)) { + return Vec3.ONE; + } + return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; + } + + function display(rootEntityID, boundingBox, isMultiple) { + var boundingBoxCenter, + boundingBoxOrientation, + cameraPosition, + boundingBoxVector, + distanceMultiplier, + cameraUp, + cornerPosition, + cornerVector, + crossProductScale, + maxCrossProductScale, + rightCornerIndex, + leftCornerIndex, + cornerHandleDimensions, + faceHandleDimensions, + i; + + isVisible = true; + + boundingBoxDimensions = boundingBox.dimensions; + boundingBoxCenter = boundingBox.center; + boundingBoxLocalCenter = boundingBox.localCenter; + boundingBoxOrientation = boundingBox.orientation; + + // Selection bounding box. + Overlays.editOverlay(boundingBoxOverlay, { + parentID: rootEntityID, + localPosition: boundingBoxLocalCenter, + localRotation: ZERO_ROTATION, + dimensions: boundingBoxDimensions, + visible: true + }); + + // Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones + // display smaller in order to give comfortable depth cue. + cameraPosition = Camera.position; + boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); + distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER + * Vec3.dot(Quat.getForward(Camera.orientation), boundingBoxVector) + / Math.sqrt(Vec3.length(boundingBoxVector)); + + // Corner scale handles. + // At right-most and opposite corners of bounding box. + cameraUp = Quat.getUp(Camera.orientation); + maxCrossProductScale = 0; + for (i = 0; i < NUM_CORNERS; i += 1) { + cornerPosition = Vec3.sum(boundingBoxCenter, + Vec3.multiplyQbyV(boundingBoxOrientation, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions))); + cornerVector = Vec3.subtract(cornerPosition, cameraPosition); + crossProductScale = Vec3.dot(Vec3.cross(cornerVector, boundingBoxVector), cameraUp); + if (crossProductScale > maxCrossProductScale) { + maxCrossProductScale = crossProductScale; + rightCornerIndex = i; + } + } + leftCornerIndex = (rightCornerIndex + 4) % NUM_CORNERS; + cornerIndexes[0] = leftCornerIndex; + cornerIndexes[1] = rightCornerIndex; + cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), + dimensions: cornerHandleDimensions, + visible: true + }); + } + + // Face scale handles. + // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity + // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. + if (!isMultiple) { + faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); + faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), + localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + dimensions: faceHandleDimensions, + visible: true + }); + } + } else { + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); + } + } + } + + function startScaling() { + // Nothing to do. + } + + function scale(scale3D) { + // Scale relative to dimensions and positions at start of scaling. + + // Selection bounding box. + scalingBoundingBoxDimensions = Vec3.multiplyVbyV(scale3D, boundingBoxLocalCenter); + scalingBoundingBoxLocalCenter = Vec3.multiplyVbyV(scale3D, boundingBoxDimensions); + Overlays.editOverlay(boundingBoxOverlay, { + localPosition: scalingBoundingBoxDimensions, + dimensions: scalingBoundingBoxLocalCenter + }); + + // Corner scale handles. + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter)) + }); + } + + // Face scale handles. + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) + }); + } + } + + function finishScaling() { + // Adopt final scale. + boundingBoxLocalCenter = scalingBoundingBoxDimensions; + boundingBoxDimensions = scalingBoundingBoxLocalCenter; + } + + function hover(overlayID) { + if (overlayID !== hoveredOverlayID) { + if (hoveredOverlayID !== null) { + Overlays.editOverlay(hoveredOverlayID, { color: HANDLE_NORMAL_COLOR }); + hoveredOverlayID = null; + } + + if (overlayID !== null + && (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) { + Overlays.editOverlay(overlayID, { + color: HANDLE_HOVER_COLOR, + alpha: HANDLE_HOVER_ALPHA + }); + hoveredOverlayID = overlayID; + } + } + } + + function grab(overlayID) { + var overlay, + isShowAll = overlayID === null, + color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR, + alpha = isShowAll ? HANDLE_NORMAL_ALPHA : HANDLE_HOVER_ALPHA, + i, + length; + + for (i = 0, length = cornerHandleOverlays.length; i < length; i += 1) { + overlay = cornerHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isVisible && (isShowAll || overlay === overlayID), + color: color, + alpha: alpha + }); + } + + for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { + overlay = faceHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isVisible && (isShowAll || overlay === overlayID), + color: color, + alpha: alpha + }); + } + } + + function clear() { + var i; + + isVisible = false; + + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.editOverlay(cornerHandleOverlays[i], { visible: false }); + } + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); + } + } + + function destroy() { + clear(); + Overlays.deleteOverlay(boundingBoxOverlay); + } + + if (!this instanceof Handles) { + return new Handles(side); + } + + return { + display: display, + isHandle: isHandle, + scalingAxis: scalingAxis, + scalingDirections: scalingDirections, + startScaling: startScaling, + scale: scale, + finishScaling: finishScaling, + hover: hover, + grab: grab, + clear: clear, + destroy: destroy + }; +}; + +Handles.prototype = {}; diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js new file mode 100644 index 0000000000..26170e9374 --- /dev/null +++ b/scripts/vr-edit/modules/highlights.js @@ -0,0 +1,123 @@ +// +// highlights.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +/* global Highlights */ + +Highlights = function (side) { + // Draws highlights on selected entities. + + "use strict"; + + var handOverlay, + entityOverlays = [], + GRAB_HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + SCALE_HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, + HAND_HIGHLIGHT_ALPHA = 0.35, + ENTITY_HIGHLIGHT_ALPHA = 0.8, + HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, + HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, + LEFT_HAND = 0, + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO); + + handOverlay = Overlays.addOverlay("sphere", { + dimensions: HAND_HIGHLIGHT_DIMENSIONS, + parentID: AVATAR_SELF_ID, + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND + ? "_CONTROLLER_LEFTHAND" + : "_CONTROLLER_RIGHTHAND"), + localPosition: HAND_HIGHLIGHT_OFFSET, + alpha: HAND_HIGHLIGHT_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + function maybeAddEntityOverlay(index) { + if (index >= entityOverlays.length) { + entityOverlays.push(Overlays.addOverlay("cube", { + alpha: ENTITY_HIGHLIGHT_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + })); + } + } + + function editEntityOverlay(index, details, overlayColor) { + var offset = Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions); + + Overlays.editOverlay(entityOverlays[index], { + parentID: details.id, + localPosition: offset, + localRotation: ZERO_ROTATION, + dimensions: details.dimensions, + color: overlayColor, + visible: true + }); + } + + function display(handIntersected, selection, isScale) { + var overlayColor = isScale ? SCALE_HIGHLIGHT_COLOR : GRAB_HIGHLIGHT_COLOR, + i, + length; + + // Show/hide hand overlay. + Overlays.editOverlay(handOverlay, { + color: overlayColor, + visible: handIntersected + }); + + // Add/edit entity overlay. + for (i = 0, length = selection.length; i < length; i += 1) { + maybeAddEntityOverlay(i); + editEntityOverlay(i, selection[i], overlayColor); + } + + // Delete extra entity overlays. + for (i = entityOverlays.length - 1, length = selection.length; i >= length; i -= 1) { + Overlays.deleteOverlay(entityOverlays[i]); + entityOverlays.splice(i, 1); + } + } + + function clear() { + var i, + length; + + // Hide hand overlay. + Overlays.editOverlay(handOverlay, { visible: false }); + + // Delete entity overlays. + for (i = 0, length = entityOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(entityOverlays[i]); + } + entityOverlays = []; + } + + function destroy() { + clear(); + Overlays.deleteOverlay(handOverlay); + } + + if (!this instanceof Highlights) { + return new Highlights(); + } + + return { + display: display, + clear: clear, + destroy: destroy + }; +}; + +Highlights.prototype = {}; diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js new file mode 100644 index 0000000000..0439baef72 --- /dev/null +++ b/scripts/vr-edit/modules/laser.js @@ -0,0 +1,241 @@ +// +// laser.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +/* global Laser */ + +Laser = function (side) { + // Draws hand lasers. + // May intersect with entities or bounding box of other hand's selection. + + "use strict"; + + var isLaserEnabled = true, + isLaserOn = false, + + laserLine = null, + laserSphere = null, + + searchDistance = 0.0, + + SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. + SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, + BRIGHT_POW = 0.06, // Per handControllgerGrab.js. + + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + + PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. + PRECISION_PICKING = true, + NO_INCLUDE_IDS = [], + NO_EXCLUDE_IDS = [], + VISIBLE_ONLY = true, + + laserLength, + specifiedLaserLength = null, + + LEFT_HAND = 0, + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + + intersection; + + function colorPow(color, power) { // Per handControllerGrab.js. + return { + red: Math.pow(color.red / 255, power) * 255, + green: Math.pow(color.green / 255, power) * 255, + blue: Math.pow(color.blue / 255, power) * 255 + }; + } + + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); + + if (side === LEFT_HAND) { + GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; + } + + laserLine = Overlays.addOverlay("line3d", { + lineWidth: 5, + alpha: 1.0, + glow: 1.0, + ignoreRayIntersection: true, + drawInFront: true, + parentID: AVATAR_SELF_ID, + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND + ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" + : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), + visible: false + }); + laserSphere = Overlays.addOverlay("circle3d", { + innerAlpha: 1.0, + outerAlpha: 0.0, + solid: true, + ignoreRayIntersection: true, + drawInFront: true, + visible: false + }); + + function updateLine(start, end, color) { + Overlays.editOverlay(laserLine, { + start: start, + end: end, + color: color, + visible: true + }); + } + + function updateSphere(location, size, color, brightColor) { + var rotation; + + rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + + Overlays.editOverlay(laserSphere, { + position: location, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + outerRadius: size, + visible: true + }); + } + + function display(origin, direction, distance, isClicked) { + var searchTarget, + sphereSize, + color, + brightColor; + + searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; + searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); + sphereSize = SEARCH_SPHERE_SIZE * searchDistance; + color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; + brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; + + updateLine(origin, searchTarget, color); + updateSphere(searchTarget, sphereSize, color, brightColor); + } + + function hide() { + Overlays.editOverlay(laserLine, { visible: false }); + Overlays.editOverlay(laserSphere, { visible: false }); + } + + function update(hand) { + var handPosition, + handOrientation, + deltaOrigin, + pickRay; + + if (!isLaserEnabled) { + return; + } + + if (!hand.intersection().intersects && hand.triggerPressed()) { + handPosition = hand.position(); + handOrientation = hand.orientation(); + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; + + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (intersection.intersects && !Entities.hasEditableRoot(intersection.entityID)) { + intersection.intersects = false; + intersection.entityID = null; + } + } + intersection.laserIntersected = true; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); + } else { + intersection = { + intersects: false + }; + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } + } + + function getIntersection() { + return intersection; + } + + function setLength(length) { + specifiedLaserLength = length; + laserLength = length; + } + + function clearLength() { + specifiedLaserLength = null; + } + + function getLength() { + return laserLength; + } + + function handOffset() { + return GRAB_POINT_SPHERE_OFFSET; + } + + function clear() { + isLaserOn = false; + hide(); + } + + function enable() { + isLaserEnabled = true; + } + + function disable() { + isLaserEnabled = false; + if (isLaserOn) { + hide(); + } + isLaserOn = false; + } + + function destroy() { + Overlays.deleteOverlay(laserLine); + Overlays.deleteOverlay(laserSphere); + } + + if (!this instanceof Laser) { + return new Laser(side); + } + + return { + update: update, + intersection: getIntersection, + setLength: setLength, + clearLength: clearLength, + length: getLength, + enable: enable, + disable: disable, + handOffset: handOffset, + clear: clear, + destroy: destroy + }; +}; + +Laser.prototype = {}; diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js new file mode 100644 index 0000000000..92a4962bce --- /dev/null +++ b/scripts/vr-edit/modules/selection.js @@ -0,0 +1,328 @@ +// +// selection.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +/* global Selection */ + +Selection = function (side) { + // Manages set of selected entities. Currently supports just one set of linked entities. + + "use strict"; + + var selection = [], + selectedEntityID = null, + rootEntityID = null, + rootPosition, + rootOrientation, + scaleCenter, + scaleRootOffset, + scaleRootOrientation, + ENTITY_TYPE = "entity"; + + function traverseEntityTree(id, result) { + // Recursively traverses tree of entities and their children, gather IDs and properties. + var children, + properties, + SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition", + "dynamic", "collisionless"], + i, + length; + + properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES); + result.push({ + id: id, + position: properties.position, + localPosition: properties.localPosition, + registrationPoint: properties.registrationPoint, + rotation: properties.rotation, + dimensions: properties.dimensions, + dynamic: properties.dynamic, + collisionless: properties.collisionless + }); + + children = Entities.getChildrenIDs(id); + for (i = 0, length = children.length; i < length; i += 1) { + if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { + traverseEntityTree(children[i], result); + } + } + } + + function select(entityID) { + var entityProperties, + PARENT_PROPERTIES = ["parentID", "position", "rotation", "dymamic", "collisionless"]; + + // Find root parent. + rootEntityID = Entities.rootOf(entityID); + + // Selection position and orientation is that of the root entity. + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + rootPosition = entityProperties.position; + rootOrientation = entityProperties.rotation; + + // Find all children. + selection = []; + traverseEntityTree(rootEntityID, selection); + + selectedEntityID = entityID; + } + + function getRootEntityID() { + return rootEntityID; + } + + function getSelection() { + return selection; + } + + function count() { + return selection.length; + } + + function getBoundingBox() { + var center, + localCenter, + orientation, + inverseOrientation, + dimensions, + min, + max, + i, + j, + length, + registration, + position, + rotation, + corners = [], + NUM_CORNERS = 8; + + if (selection.length === 1) { + if (Vec3.equal(selection[0].registrationPoint, Vec3.HALF)) { + center = rootPosition; + } else { + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, + Vec3.multiplyVbyV(selection[0].dimensions, + Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); + } + localCenter = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(center, rootPosition)); + orientation = rootOrientation; + dimensions = selection[0].dimensions; + } else if (selection.length > 1) { + // Find min & max x, y, z values of entities' dimension box corners in root entity coordinate system. + // Note: Don't use entities' bounding boxes because they're in world coordinates and may make the calculated + // bounding box be larger than necessary. + min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); + max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); + inverseOrientation = Quat.inverse(rootOrientation); + for (i = 1, length = selection.length; i < length; i += 1) { + + registration = selection[i].registrationPoint; + corners[0] = { x: -registration.x, y: -registration.y, z: -registration.z }; + corners[1] = { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }; + corners[2] = { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }; + corners[3] = { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; + corners[4] = { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }; + corners[5] = { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }; + corners[6] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }; + corners[7] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; + + position = selection[i].position; + rotation = selection[i].rotation; + dimensions = selection[i].dimensions; + + for (j = 0; j < NUM_CORNERS; j += 1) { + // Corner position in world coordinates. + corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); + // Corner position in root entity coordinates. + corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], rootPosition)); + // Update min & max. + min = Vec3.min(corners[j], min); + max = Vec3.max(corners[j], max); + } + } + + // Calculate bounding box. + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); + localCenter = Vec3.multiply(0.5, Vec3.sum(min, max)); + orientation = rootOrientation; + dimensions = Vec3.subtract(max, min); + } + + return { + center: center, + localCenter: localCenter, + orientation: orientation, + dimensions: dimensions + }; + } + + function startEditing() { + var count, + i; + + // Disable entity set's physics. + for (i = 0, count = selection.length; i < count; i += 1) { + Entities.editEntity(selection[i].id, { + dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. + collisionless: true // So that entity doesn't bump us about as we resize the entity. + }); + } + } + + function finishEditing() { + var firstDynamicEntityID = null, + properties, + VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }, + count, + i; + + // Restore entity set's physics. + for (i = 0, count = selection.length; i < count; i += 1) { + if (firstDynamicEntityID === null && selection[i].dynamic) { + firstDynamicEntityID = selection[i].id; + } + Entities.editEntity(selection[i].id, { + dynamic: selection[i].dynamic, + collisionless: selection[i].collisionless + }); + } + + // If dynamic with gravity, and velocity is zero, give the entity set a little kick to set off physics. + if (firstDynamicEntityID) { + properties = Entities.getEntityProperties(firstDynamicEntityID, ["velocity", "gravity"]); + if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < VELOCITY_THRESHOLD) { + Entities.editEntity(firstDynamicEntityID, { velocity: VELOCITY_KICK }); + } + } + } + + function getPositionAndOrientation() { + // Position and orientation of root entity. + return { + position: rootPosition, + orientation: rootOrientation + }; + } + + function setPositionAndOrientation(position, orientation) { + // Position and orientation of root entity. + rootPosition = position; + rootOrientation = orientation; + Entities.editEntity(rootEntityID, { + position: position, + rotation: orientation + }); + } + + function startDirectScaling(center) { + // Save initial position and orientation so that can scale relative to these without accumulating float errors. + scaleCenter = center; + scaleRootOffset = Vec3.subtract(rootPosition, center); + scaleRootOrientation = rootOrientation; + } + + function directScale(factor, rotation, center) { + // Scale, position, and rotate selection. + var i, + length; + + // Scale, position, and orient root. + rootPosition = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); + rootOrientation = Quat.multiply(rotation, scaleRootOrientation); + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiply(factor, selection[0].dimensions), + position: rootPosition, + rotation: rootOrientation + }); + + // Scale and position children. + for (i = 1, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiply(factor, selection[i].dimensions), + localPosition: Vec3.multiply(factor, selection[i].localPosition) + }); + } + } + + function finishDirectScaling() { + select(selectedEntityID); // Refresh. + } + + function startHandleScaling() { + // Nothing to do. + } + + function handleScale(factor, position, orientation) { + // Scale and reposition and orient selection. + var i, + length; + + // Scale and position root. + rootPosition = position; + rootOrientation = orientation; + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), + position: rootPosition, + rotation: rootOrientation + }); + + // Scale and position children. + // Only corner handles are used for scaling multiple entities so scale factor is the same in all dimensions. + // Therefore don't need to take into account orientation relative to parent when scaling local position. + for (i = 1, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), + localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) + }); + } + } + + function finishHandleScaling() { + select(selectedEntityID); // Refresh. + } + + function clear() { + selection = []; + selectedEntityID = null; + rootEntityID = null; + } + + function destroy() { + clear(); + } + + if (!this instanceof Selection) { + return new Selection(side); + } + + return { + select: select, + selection: getSelection, + count: count, + rootEntityID: getRootEntityID, + boundingBox: getBoundingBox, + getPositionAndOrientation: getPositionAndOrientation, + setPositionAndOrientation: setPositionAndOrientation, + startEditing: startEditing, + startDirectScaling: startDirectScaling, + directScale: directScale, + finishDirectScaling: finishDirectScaling, + startHandleScaling: startHandleScaling, + handleScale: handleScale, + finishHandleScaling: finishHandleScaling, + finishEditing: finishEditing, + clear: clear, + destroy: destroy + }; +}; + +Selection.prototype = {}; diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js new file mode 100644 index 0000000000..a6914b332c --- /dev/null +++ b/scripts/vr-edit/utilities/utilities.js @@ -0,0 +1,51 @@ +// +// utilities.js +// +// Created by David Rowe on 21 Jul 2017. +// Copyright 2017 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 +// + +if (typeof Vec3.min !== "function") { + Vec3.min = function (a, b) { + return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), z: Math.min(a.z, b.z) }; + }; +} + +if (typeof Vec3.max !== "function") { + Vec3.max = function (a, b) { + return { x: Math.max(a.x, b.x), y: Math.max(a.y, b.y), z: Math.max(a.z, b.z) }; + }; +} + +if (typeof Entities.rootOf !== "function") { + Entities.rootOf = function (entityID) { + var rootEntityID, + entityProperties, + PARENT_PROPERTIES = ["parentID"], + NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + rootEntityID = entityID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + while (entityProperties.parentID && entityProperties.parentID !== NULL_UUID) { + rootEntityID = entityProperties.parentID; + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + } + return rootEntityID; + }; +} + +if (typeof Entities.hasEditableRoot !== "function") { + Entities.hasEditableRoot = function (entityID) { + var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], + NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], + NULL_UUID = "{00000000-0000-0000-0000-000000000000}", + properties; + properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + while (properties.parentID && properties.parentID !== NULL_UUID) { + properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); + } + return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + }; +} diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index f095b21331..3df0f93a53 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1,5 +1,3 @@ -"use strict"; - // // vr-edit.js // @@ -12,6 +10,8 @@ (function () { + "use strict"; + var APP_NAME = "VR EDIT", // TODO: App name. APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons. APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", @@ -31,49 +31,25 @@ UPDATE_LOOP_TIMEOUT = 16, updateTimer = null, - Highlights, - Handles, - Selection, - Laser, + // Modules Hand, + Handles, + Highlights, + Laser, + Selection, Editor, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - NULL_UUID = "{00000000-0000-0000-0000-000000000000}", - HALF_TREE_SCALE = 16384, - DEBUG = true; // TODO: Set false. + // Utilities + Script.include("./utilities/utilities.js"); - if (typeof Vec3.min !== "function") { - Vec3.min = function (a, b) { - return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), z: Math.min(a.z, b.z) }; - }; - } - - if (typeof Vec3.max !== "function") { - Vec3.max = function (a, b) { - return { x: Math.max(a.x, b.x), y: Math.max(a.y, b.y), z: Math.max(a.z, b.z) }; - }; - } - - if (typeof Entities.rootOf !== "function") { - Entities.rootOf = function (entityID) { - var rootEntityID, - entityProperties, - PARENT_PROPERTIES = ["parentID"]; - if (entityID === undefined || entityID === null) { - return null; - } - rootEntityID = entityID; - entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - while (entityProperties.parentID !== NULL_UUID) { - rootEntityID = entityProperties.parentID; - entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - } - return rootEntityID; - }; - } + // Modules + Script.include("./modules/hand.js"); + Script.include("./modules/handles.js"); + Script.include("./modules/highlights.js"); + Script.include("./modules/laser.js"); + Script.include("./modules/selection.js"); function log(message) { @@ -94,1198 +70,6 @@ } } - function isEditableRoot(entityID) { - var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], - NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], - properties; - properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); - while (properties.parentID && properties.parentID !== NULL_UUID) { - properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); - } - return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; - } - - - Highlights = function (side) { - // Draws highlights on selected entities. - var handOverlay, - entityOverlays = [], - GRAB_HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, - SCALE_HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, - HAND_HIGHLIGHT_ALPHA = 0.35, - ENTITY_HIGHLIGHT_ALPHA = 0.8, - HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, - HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO); - - handOverlay = Overlays.addOverlay("sphere", { - dimensions: HAND_HIGHLIGHT_DIMENSIONS, - parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND - ? "_CONTROLLER_LEFTHAND" - : "_CONTROLLER_RIGHTHAND"), - localPosition: HAND_HIGHLIGHT_OFFSET, - alpha: HAND_HIGHLIGHT_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }); - - function maybeAddEntityOverlay(index) { - if (index >= entityOverlays.length) { - entityOverlays.push(Overlays.addOverlay("cube", { - alpha: ENTITY_HIGHLIGHT_ALPHA, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - })); - } - } - - function editEntityOverlay(index, details, overlayColor) { - var offset = Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions); - - Overlays.editOverlay(entityOverlays[index], { - parentID: details.id, - localPosition: offset, - localRotation: ZERO_ROTATION, - dimensions: details.dimensions, - color: overlayColor, - visible: true - }); - } - - function display(handIntersected, selection, isScale) { - var overlayColor = isScale ? SCALE_HIGHLIGHT_COLOR : GRAB_HIGHLIGHT_COLOR, - i, - length; - - // Show/hide hand overlay. - Overlays.editOverlay(handOverlay, { - color: overlayColor, - visible: handIntersected - }); - - // Add/edit entity overlay. - for (i = 0, length = selection.length; i < length; i += 1) { - maybeAddEntityOverlay(i); - editEntityOverlay(i, selection[i], overlayColor); - } - - // Delete extra entity overlays. - for (i = entityOverlays.length - 1, length = selection.length; i >= length; i -= 1) { - Overlays.deleteOverlay(entityOverlays[i]); - entityOverlays.splice(i, 1); - } - } - - function clear() { - var i, - length; - - // Hide hand overlay. - Overlays.editOverlay(handOverlay, { visible: false }); - - // Delete entity overlays. - for (i = 0, length = entityOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(entityOverlays[i]); - } - entityOverlays = []; - } - - function destroy() { - clear(); - Overlays.deleteOverlay(handOverlay); - } - - if (!this instanceof Highlights) { - return new Highlights(); - } - - return { - display: display, - clear: clear, - destroy: destroy - }; - }; - - - Handles = function (side) { - var boundingBoxOverlay, - boundingBoxDimensions, - boundingBoxLocalCenter, - cornerIndexes = [], - cornerHandleOverlays = [], - faceHandleOverlays = [], - faceHandleOffsets, - BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, - BOUNDING_BOX_ALPHA = 0.8, - HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, - HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 }, - HANDLE_NORMAL_ALPHA = 0.7, - HANDLE_HOVER_ALPHA = 0.9, - NUM_CORNERS = 8, - NUM_CORNER_HANDLES = 2, - CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, - CORNER_HANDLE_OVERLAY_AXES, - NUM_FACE_HANDLES = 6, - FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 }, - FACE_HANDLE_OVERLAY_AXES, - FACE_HANDLE_OVERLAY_OFFSETS, - FACE_HANDLE_OVERLAY_ROTATIONS, - FACE_HANDLE_OVERLAY_SCALE_AXES, - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, - hoveredOverlayID = null, - isVisible = false, - - // Scaling. - scalingBoundingBoxDimensions, - scalingBoundingBoxLocalCenter, - - i; - - CORNER_HANDLE_OVERLAY_AXES = [ - // Ordered such that items 4 apart are opposite corners - used in display(). - { x: -0.5, y: -0.5, z: -0.5 }, - { x: -0.5, y: -0.5, z: 0.5 }, - { x: -0.5, y: 0.5, z: -0.5 }, - { x: -0.5, y: 0.5, z: 0.5 }, - { x: 0.5, y: 0.5, z: 0.5 }, - { x: 0.5, y: 0.5, z: -0.5 }, - { x: 0.5, y: -0.5, z: 0.5 }, - { x: 0.5, y: -0.5, z: -0.5 } - ]; - - FACE_HANDLE_OVERLAY_AXES = [ - { x: -0.5, y: 0, z: 0 }, - { x: 0.5, y: 0, z: 0 }, - { x: 0, y: -0.5, z: 0 }, - { x: 0, y: 0.5, z: 0 }, - { x: 0, y: 0, z: -0.5 }, - { x: 0, y: 0, z: 0.5 } - ]; - - FACE_HANDLE_OVERLAY_OFFSETS = { - x: FACE_HANDLE_OVERLAY_DIMENSIONS.y, - y: FACE_HANDLE_OVERLAY_DIMENSIONS.y, - z: FACE_HANDLE_OVERLAY_DIMENSIONS.y - }; - - FACE_HANDLE_OVERLAY_ROTATIONS = [ - Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }), - Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), - Quat.fromVec3Degrees({ x: 180, y: 0, z: 0 }), - Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), - Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), - Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) - ]; - - FACE_HANDLE_OVERLAY_SCALE_AXES = [ - Vec3.UNIT_X, - Vec3.UNIT_X, - Vec3.UNIT_Y, - Vec3.UNIT_Y, - Vec3.UNIT_Z, - Vec3.UNIT_Z - ]; - - boundingBoxOverlay = Overlays.addOverlay("cube", { - color: BOUNDING_BOX_COLOR, - alpha: BOUNDING_BOX_ALPHA, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }); - - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { - color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_NORMAL_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: false, - visible: false - }); - } - - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - faceHandleOverlays[i] = Overlays.addOverlay("shape", { - shape: "Cone", - color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_NORMAL_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: false, - visible: false - }); - } - - function isAxisHandle(overlayID) { - return faceHandleOverlays.indexOf(overlayID) !== -1; - } - - function isCornerHandle(overlayID) { - return cornerHandleOverlays.indexOf(overlayID) !== -1; - } - - function isHandle(overlayID) { - return isAxisHandle(overlayID) || isCornerHandle(overlayID); - } - - function scalingAxis(overlayID) { - var axesIndex; - if (isCornerHandle(overlayID)) { - axesIndex = CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[cornerHandleOverlays.indexOf(overlayID)]]; - return Vec3.normalize(Vec3.multiplyVbyV(axesIndex, boundingBoxDimensions)); - } - return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; - } - - function scalingDirections(overlayID) { - if (isCornerHandle(overlayID)) { - return Vec3.ONE; - } - return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; - } - - function display(rootEntityID, boundingBox, isMultiple) { - var boundingBoxCenter, - boundingBoxOrientation, - cameraPosition, - boundingBoxVector, - distanceMultiplier, - cameraUp, - cornerPosition, - cornerVector, - crossProductScale, - maxCrossProductScale, - rightCornerIndex, - leftCornerIndex, - cornerHandleDimensions, - faceHandleDimensions, - i; - - isVisible = true; - - boundingBoxDimensions = boundingBox.dimensions; - boundingBoxCenter = boundingBox.center; - boundingBoxLocalCenter = boundingBox.localCenter; - boundingBoxOrientation = boundingBox.orientation; - - // Selection bounding box. - Overlays.editOverlay(boundingBoxOverlay, { - parentID: rootEntityID, - localPosition: boundingBoxLocalCenter, - localRotation: ZERO_ROTATION, - dimensions: boundingBoxDimensions, - visible: true - }); - - // Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones - // display smaller in order to give comfortable depth cue. - cameraPosition = Camera.position; - boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); - distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER - * Vec3.dot(Quat.getForward(Camera.orientation), boundingBoxVector) - / Math.sqrt(Vec3.length(boundingBoxVector)); - - // Corner scale handles. - // At right-most and opposite corners of bounding box. - cameraUp = Quat.getUp(Camera.orientation); - maxCrossProductScale = 0; - for (i = 0; i < NUM_CORNERS; i += 1) { - cornerPosition = Vec3.sum(boundingBoxCenter, - Vec3.multiplyQbyV(boundingBoxOrientation, - Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions))); - cornerVector = Vec3.subtract(cornerPosition, cameraPosition); - crossProductScale = Vec3.dot(Vec3.cross(cornerVector, boundingBoxVector), cameraUp); - if (crossProductScale > maxCrossProductScale) { - maxCrossProductScale = crossProductScale; - rightCornerIndex = i; - } - } - leftCornerIndex = (rightCornerIndex + 4) % NUM_CORNERS; - cornerIndexes[0] = leftCornerIndex; - cornerIndexes[1] = rightCornerIndex; - cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - Overlays.editOverlay(cornerHandleOverlays[i], { - parentID: rootEntityID, - localPosition: Vec3.sum(boundingBoxLocalCenter, - Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), - dimensions: cornerHandleDimensions, - visible: true - }); - } - - // Face scale handles. - // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity - // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. - if (!isMultiple) { - faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); - faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { - parentID: rootEntityID, - localPosition: Vec3.sum(boundingBoxLocalCenter, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), - localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], - dimensions: faceHandleDimensions, - visible: true - }); - } - } else { - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); - } - } - } - - function startScaling() { - // Nothing to do. - } - - function scale(scale3D) { - // Scale relative to dimensions and positions at start of scaling. - - // Selection bounding box. - scalingBoundingBoxDimensions = Vec3.multiplyVbyV(scale3D, boundingBoxLocalCenter); - scalingBoundingBoxLocalCenter = Vec3.multiplyVbyV(scale3D, boundingBoxDimensions); - Overlays.editOverlay(boundingBoxOverlay, { - localPosition: scalingBoundingBoxDimensions, - dimensions: scalingBoundingBoxLocalCenter - }); - - // Corner scale handles. - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - Overlays.editOverlay(cornerHandleOverlays[i], { - localPosition: Vec3.sum(scalingBoundingBoxDimensions, - Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter)) - }); - } - - // Face scale handles. - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { - localPosition: Vec3.sum(scalingBoundingBoxDimensions, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], - Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) - }); - } - } - - function finishScaling() { - // Adopt final scale. - boundingBoxLocalCenter = scalingBoundingBoxDimensions; - boundingBoxDimensions = scalingBoundingBoxLocalCenter; - } - - function hover(overlayID) { - if (overlayID !== hoveredOverlayID) { - if (hoveredOverlayID !== null) { - Overlays.editOverlay(hoveredOverlayID, { color: HANDLE_NORMAL_COLOR }); - hoveredOverlayID = null; - } - - if (overlayID !== null - && (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) { - Overlays.editOverlay(overlayID, { - color: HANDLE_HOVER_COLOR, - alpha: HANDLE_HOVER_ALPHA - }); - hoveredOverlayID = overlayID; - } - } - } - - function grab(overlayID) { - var overlay, - isShowAll = overlayID === null, - color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR, - alpha = isShowAll ? HANDLE_NORMAL_ALPHA : HANDLE_HOVER_ALPHA, - i, - length; - - for (i = 0, length = cornerHandleOverlays.length; i < length; i += 1) { - overlay = cornerHandleOverlays[i]; - Overlays.editOverlay(overlay, { - visible: isVisible && (isShowAll || overlay === overlayID), - color: color, - alpha: alpha - }); - } - - for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { - overlay = faceHandleOverlays[i]; - Overlays.editOverlay(overlay, { - visible: isVisible && (isShowAll || overlay === overlayID), - color: color, - alpha: alpha - }); - } - } - - function clear() { - var i; - - isVisible = false; - - Overlays.editOverlay(boundingBoxOverlay, { visible: false }); - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - Overlays.editOverlay(cornerHandleOverlays[i], { visible: false }); - } - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); - } - } - - function destroy() { - clear(); - Overlays.deleteOverlay(boundingBoxOverlay); - } - - if (!this instanceof Handles) { - return new Handles(side); - } - - return { - display: display, - isHandle: isHandle, - scalingAxis: scalingAxis, - scalingDirections: scalingDirections, - startScaling: startScaling, - scale: scale, - finishScaling: finishScaling, - hover: hover, - grab: grab, - clear: clear, - destroy: destroy - }; - }; - - - Selection = function (side) { - // Manages set of selected entities. Currently supports just one set of linked entities. - var selection = [], - selectedEntityID = null, - rootEntityID = null, - rootPosition, - rootOrientation, - scaleCenter, - scaleRootOffset, - scaleRootOrientation, - ENTITY_TYPE = "entity"; - - function traverseEntityTree(id, result) { - // Recursively traverses tree of entities and their children, gather IDs and properties. - var children, - properties, - SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition", - "dynamic", "collisionless"], - i, - length; - - properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES); - result.push({ - id: id, - position: properties.position, - localPosition: properties.localPosition, - registrationPoint: properties.registrationPoint, - rotation: properties.rotation, - dimensions: properties.dimensions, - dynamic: properties.dynamic, - collisionless: properties.collisionless - }); - - children = Entities.getChildrenIDs(id); - for (i = 0, length = children.length; i < length; i += 1) { - if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { - traverseEntityTree(children[i], result); - } - } - } - - function select(entityID) { - var entityProperties, - PARENT_PROPERTIES = ["parentID", "position", "rotation", "dymamic", "collisionless"]; - - // Find root parent. - rootEntityID = Entities.rootOf(entityID); - - // Selection position and orientation is that of the root entity. - entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - rootPosition = entityProperties.position; - rootOrientation = entityProperties.rotation; - - // Find all children. - selection = []; - traverseEntityTree(rootEntityID, selection); - - selectedEntityID = entityID; - } - - function getRootEntityID() { - return rootEntityID; - } - - function getSelection() { - return selection; - } - - function count() { - return selection.length; - } - - function getBoundingBox() { - var center, - localCenter, - orientation, - inverseOrientation, - dimensions, - min, - max, - i, - j, - length, - registration, - position, - rotation, - corners = [], - NUM_CORNERS = 8; - - if (selection.length === 1) { - if (Vec3.equal(selection[0].registrationPoint, Vec3.HALF)) { - center = rootPosition; - } else { - center = Vec3.sum(rootPosition, - Vec3.multiplyQbyV(rootOrientation, - Vec3.multiplyVbyV(selection[0].dimensions, - Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); - } - localCenter = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(center, rootPosition)); - orientation = rootOrientation; - dimensions = selection[0].dimensions; - } else if (selection.length > 1) { - // Find min & max x, y, z values of entities' dimension box corners in root entity coordinate system. - // Note: Don't use entities' bounding boxes because they're in world coordinates and may make the calculated - // bounding box be larger than necessary. - min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); - max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); - inverseOrientation = Quat.inverse(rootOrientation); - for (i = 1, length = selection.length; i < length; i += 1) { - - registration = selection[i].registrationPoint; - corners[0] = { x: -registration.x, y: -registration.y, z: -registration.z }; - corners[1] = { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }; - corners[2] = { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }; - corners[3] = { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; - corners[4] = { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }; - corners[5] = { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }; - corners[6] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }; - corners[7] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; - - position = selection[i].position; - rotation = selection[i].rotation; - dimensions = selection[i].dimensions; - - for (j = 0; j < NUM_CORNERS; j += 1) { - // Corner position in world coordinates. - corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); - // Corner position in root entity coordinates. - corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], rootPosition)); - // Update min & max. - min = Vec3.min(corners[j], min); - max = Vec3.max(corners[j], max); - } - } - - // Calculate bounding box. - center = Vec3.sum(rootPosition, - Vec3.multiplyQbyV(rootOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); - localCenter = Vec3.multiply(0.5, Vec3.sum(min, max)); - orientation = rootOrientation; - dimensions = Vec3.subtract(max, min); - } - - return { - center: center, - localCenter: localCenter, - orientation: orientation, - dimensions: dimensions - }; - } - - function startEditing() { - var count, - i; - - // Disable entity set's physics. - for (i = 0, count = selection.length; i < count; i += 1) { - Entities.editEntity(selection[i].id, { - dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. - collisionless: true // So that entity doesn't bump us about as we resize the entity. - }); - } - } - - function finishEditing() { - var firstDynamicEntityID = null, - properties, - VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }, - count, - i; - - // Restore entity set's physics. - for (i = 0, count = selection.length; i < count; i += 1) { - if (firstDynamicEntityID === null && selection[i].dynamic) { - firstDynamicEntityID = selection[i].id; - } - Entities.editEntity(selection[i].id, { - dynamic: selection[i].dynamic, - collisionless: selection[i].collisionless - }); - } - - // If dynamic with gravity, and velocity is zero, give the entity set a little kick to set off physics. - if (firstDynamicEntityID) { - properties = Entities.getEntityProperties(firstDynamicEntityID, ["velocity", "gravity"]); - if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < VELOCITY_THRESHOLD) { - Entities.editEntity(firstDynamicEntityID, { velocity: VELOCITY_KICK }); - } - } - } - - function getPositionAndOrientation() { - // Position and orientation of root entity. - return { - position: rootPosition, - orientation: rootOrientation - }; - } - - function setPositionAndOrientation(position, orientation) { - // Position and orientation of root entity. - rootPosition = position; - rootOrientation = orientation; - Entities.editEntity(rootEntityID, { - position: position, - rotation: orientation - }); - } - - function startDirectScaling(center) { - // Save initial position and orientation so that can scale relative to these without accumulating float errors. - scaleCenter = center; - scaleRootOffset = Vec3.subtract(rootPosition, center); - scaleRootOrientation = rootOrientation; - } - - function directScale(factor, rotation, center) { - // Scale, position, and rotate selection. - var i, - length; - - // Scale, position, and orient root. - rootPosition = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); - rootOrientation = Quat.multiply(rotation, scaleRootOrientation); - Entities.editEntity(selection[0].id, { - dimensions: Vec3.multiply(factor, selection[0].dimensions), - position: rootPosition, - rotation: rootOrientation - }); - - // Scale and position children. - for (i = 1, length = selection.length; i < length; i += 1) { - Entities.editEntity(selection[i].id, { - dimensions: Vec3.multiply(factor, selection[i].dimensions), - localPosition: Vec3.multiply(factor, selection[i].localPosition) - }); - } - } - - function finishDirectScaling() { - select(selectedEntityID); // Refresh. - } - - function startHandleScaling() { - // Nothing to do. - } - - function handleScale(factor, position, orientation) { - // Scale and reposition and orient selection. - var i, - length; - - // Scale and position root. - rootPosition = position; - rootOrientation = orientation; - Entities.editEntity(selection[0].id, { - dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), - position: rootPosition, - rotation: rootOrientation - }); - - // Scale and position children. - // Only corner handles are used for scaling multiple entities so scale factor is the same in all dimensions. - // Therefore don't need to take into account orientation relative to parent when scaling local position. - for (i = 1, length = selection.length; i < length; i += 1) { - Entities.editEntity(selection[i].id, { - dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), - localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) - }); - } - } - - function finishHandleScaling() { - select(selectedEntityID); // Refresh. - } - - function clear() { - selection = []; - selectedEntityID = null; - rootEntityID = null; - } - - function destroy() { - clear(); - } - - if (!this instanceof Selection) { - return new Selection(side); - } - - return { - select: select, - selection: getSelection, - count: count, - rootEntityID: getRootEntityID, - boundingBox: getBoundingBox, - getPositionAndOrientation: getPositionAndOrientation, - setPositionAndOrientation: setPositionAndOrientation, - startEditing: startEditing, - startDirectScaling: startDirectScaling, - directScale: directScale, - finishDirectScaling: finishDirectScaling, - startHandleScaling: startHandleScaling, - handleScale: handleScale, - finishHandleScaling: finishHandleScaling, - finishEditing: finishEditing, - clear: clear, - destroy: destroy - }; - }; - - - Laser = function (side) { - // Draws hand lasers. - // May intersect with entities or bounding box of other hand's selection. - - var isLaserEnabled = true, - isLaserOn = false, - - laserLine = null, - laserSphere = null, - - searchDistance = 0.0, - - SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. - SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. - COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. - COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. - COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, - COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, - BRIGHT_POW = 0.06, // Per handControllgerGrab.js. - - GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. - - PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. - PRECISION_PICKING = true, - NO_INCLUDE_IDS = [], - NO_EXCLUDE_IDS = [], - VISIBLE_ONLY = true, - - laserLength, - specifiedLaserLength = null, - - intersection; - - function colorPow(color, power) { // Per handControllerGrab.js. - return { - red: Math.pow(color.red / 255, power) * 255, - green: Math.pow(color.green / 255, power) * 255, - blue: Math.pow(color.blue / 255, power) * 255 - }; - } - - COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); - COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); - - if (side === LEFT_HAND) { - GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; - } - - laserLine = Overlays.addOverlay("line3d", { - lineWidth: 5, - alpha: 1.0, - glow: 1.0, - ignoreRayIntersection: true, - drawInFront: true, - parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND - ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" - : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), - visible: false - }); - laserSphere = Overlays.addOverlay("circle3d", { - innerAlpha: 1.0, - outerAlpha: 0.0, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, - visible: false - }); - - function updateLine(start, end, color) { - Overlays.editOverlay(laserLine, { - start: start, - end: end, - color: color, - visible: true - }); - } - - function updateSphere(location, size, color, brightColor) { - var rotation; - - rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); - - Overlays.editOverlay(laserSphere, { - position: location, - rotation: rotation, - innerColor: brightColor, - outerColor: color, - outerRadius: size, - visible: true - }); - } - - function display(origin, direction, distance, isClicked) { - var searchTarget, - sphereSize, - color, - brightColor; - - searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; - searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); - sphereSize = SEARCH_SPHERE_SIZE * searchDistance; - color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; - brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; - - updateLine(origin, searchTarget, color); - updateSphere(searchTarget, sphereSize, color, brightColor); - } - - function hide() { - Overlays.editOverlay(laserLine, { visible: false }); - Overlays.editOverlay(laserSphere, { visible: false }); - } - - function update(hand) { - var handPosition, - handOrientation, - deltaOrigin, - pickRay; - - if (!isLaserEnabled) { - return; - } - - if (!hand.intersection().intersects && hand.triggerPressed()) { - handPosition = hand.position(); - handOrientation = hand.orientation(); - deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); - pickRay = { - origin: Vec3.sum(handPosition, deltaOrigin), - direction: Quat.getUp(handOrientation), - length: PICK_MAX_DISTANCE - }; - - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (intersection.intersects && !isEditableRoot(intersection.entityID)) { - intersection.intersects = false; - intersection.entityID = null; - } - } - intersection.laserIntersected = true; - laserLength = (specifiedLaserLength !== null) - ? specifiedLaserLength - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - - isLaserOn = true; - display(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); - } else { - intersection = { - intersects: false - }; - if (isLaserOn) { - isLaserOn = false; - hide(); - } - } - } - - function getIntersection() { - return intersection; - } - - function setLength(length) { - specifiedLaserLength = length; - laserLength = length; - } - - function clearLength() { - specifiedLaserLength = null; - } - - function getLength() { - return laserLength; - } - - function handOffset() { - return GRAB_POINT_SPHERE_OFFSET; - } - - function clear() { - isLaserOn = false; - hide(); - } - - function enable() { - isLaserEnabled = true; - } - - function disable() { - isLaserEnabled = false; - if (isLaserOn) { - hide(); - } - isLaserOn = false; - } - - function destroy() { - Overlays.deleteOverlay(laserLine); - Overlays.deleteOverlay(laserSphere); - } - - if (!this instanceof Laser) { - return new Laser(side); - } - - return { - update: update, - intersection: getIntersection, - setLength: setLength, - clearLength: clearLength, - length: getLength, - enable: enable, - disable: disable, - handOffset: handOffset, - clear: clear, - destroy: destroy - }; - }; - - - Hand = function (side, gripPressedCallback) { - // Hand controller input. - var handController, // ####### Rename to "controller". - controllerTrigger, - controllerTriggerClicked, - controllerGrip, - - isGripPressed = false, - GRIP_ON_VALUE = 0.99, - GRIP_OFF_VALUE = 0.95, - - isTriggerPressed, - isTriggerClicked, - TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. - TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. - - NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. - NEAR_HOVER_RADIUS = 0.025, - - handPose, - handPosition, - handOrientation, - - intersection = {}; - - if (side === LEFT_HAND) { - handController = Controller.Standard.LeftHand; - controllerTrigger = Controller.Standard.LT; - controllerTriggerClicked = Controller.Standard.LTClick; - controllerGrip = Controller.Standard.LeftGrip; - } else { - handController = Controller.Standard.RightHand; - controllerTrigger = Controller.Standard.RT; - controllerTriggerClicked = Controller.Standard.RTClick; - controllerGrip = Controller.Standard.RightGrip; - } - - function valid() { - return handPose.valid; - } - - function position() { - return handPosition; - } - - function orientation() { - return handOrientation; - } - - function triggerPressed() { - return isTriggerPressed; - } - - function triggerClicked() { - return isTriggerClicked; - } - - function getIntersection() { - return intersection; - } - - function update() { - var gripValue, - palmPosition, - overlayID, - overlayIDs, - overlayDistance, - distance, - entityID, - entityIDs, - entitySize, - size, - i, - length; - - - // Hand pose. - handPose = Controller.getPoseValue(handController); - if (!handPose.valid) { - intersection = {}; - return; - } - handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); - handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); - - // Controller trigger. - isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed - ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); - isTriggerClicked = Controller.getValue(controllerTriggerClicked); - - // Controller grip. - gripValue = Controller.getValue(controllerGrip); - if (isGripPressed) { - isGripPressed = gripValue > GRIP_OFF_VALUE; - } else { - isGripPressed = gripValue > GRIP_ON_VALUE; - if (isGripPressed) { - gripPressedCallback(); - } - } - - // Hand-overlay intersection, if any. - overlayID = null; - palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); - if (overlayIDs.length > 0) { - // Typically, there will be only one overlay; optimize for that case. - overlayID = overlayIDs[0]; - if (overlayIDs.length > 1) { - // Find closest overlay. - overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); - for (i = 1, length = overlayIDs.length; i < length; i += 1) { - distance = - Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); - if (distance > overlayDistance) { - overlayID = overlayIDs[i]; - overlayDistance = distance; - } - } - } - } - - // Hand-entity intersection, if any, if overlay not intersected. - entityID = null; - if (overlayID === null) { - // palmPosition is set above. - entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); - if (entityIDs.length > 0) { - // Typically, there will be only one entity; optimize for that case. - if (isEditableRoot(entityIDs[0])) { - entityID = entityIDs[0]; - } - if (entityIDs.length > 1) { - // Find smallest, editable entity. - entitySize = HALF_TREE_SCALE; - for (i = 0, length = entityIDs.length; i < length; i += 1) { - if (isEditableRoot(entityIDs[i])) { - size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); - if (size < entitySize) { - entityID = entityIDs[i]; - entitySize = size; - } - } - } - } - } - } - - intersection = { - intersects: overlayID !== null || entityID !== null, - overlayID: overlayID, - entityID: entityID, - handIntersected: true - }; - } - - function clear() { - // Nothing to do. - } - - function destroy() { - // Nothing to do. - } - - if (!this instanceof Hand) { - return new Hand(side); - } - - return { - valid: valid, - position: position, - orientation: orientation, - triggerPressed: triggerPressed, - triggerClicked: triggerClicked, - intersection: getIntersection, - update: update, - clear: clear, - destroy: destroy - }; - }; - Editor = function (side, gripPressedCallback) { // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. From 44fe60ddda3081533d8c84ad822fea99c90d8c49 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 22 Jul 2017 11:47:14 +1200 Subject: [PATCH 079/504] Stub dominant hand setting support --- scripts/vr-edit/vr-edit.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3df0f93a53..c47deb9961 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -18,11 +18,12 @@ tablet, button, + VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. + // Application state isAppActive = false, isAppScaleWithHandles = false, - - VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. + dominantHand, editors = [], LEFT_HAND = 0, @@ -825,6 +826,13 @@ } } + function onDominantHandChanged() { + /* + // TODO: API coming. + dominantHand = TODO; + */ + } + function setUp() { updateHandControllerGrab(); @@ -851,6 +859,14 @@ editors[LEFT_HAND].setOtherEditor(editors[RIGHT_HAND]); editors[RIGHT_HAND].setOtherEditor(editors[LEFT_HAND]); + // Dominant hand from settings. + // TODO: API coming. + dominantHand = RIGHT_HAND; + /* + dominantHand = TODO; + TODO.change.connect(onDominantHandChanged); + */ + if (isAppActive) { update(); } From 9cb8aea4fde9f3469ac2dc2e1196e319cf8246ce Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 22 Jul 2017 13:06:15 +1200 Subject: [PATCH 080/504] Make grip click delete selection --- scripts/vr-edit/modules/selection.js | 8 +++++++ scripts/vr-edit/vr-edit.js | 34 ++++++++++++++++------------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 92a4962bce..bf1d519941 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -296,6 +296,13 @@ Selection = function (side) { rootEntityID = null; } + function deleteEntities() { + if (rootEntityID) { + Entities.deleteEntity(rootEntityID); // Children are automatically deleted. + clear(); + } + } + function destroy() { clear(); } @@ -320,6 +327,7 @@ Selection = function (side) { handleScale: handleScale, finishHandleScaling: finishHandleScaling, finishEditing: finishEditing, + deleteEntities: deleteEntities, clear: clear, destroy: destroy }; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c47deb9961..48a066ea56 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -72,7 +72,7 @@ } - Editor = function (side, gripPressedCallback) { + Editor = function (side) { // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. var otherEditor, // Other hand's Editor object. @@ -84,16 +84,17 @@ EDITOR_GRABBING = 3, EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. EDITOR_HANDLE_SCALING = 5, // "" - editorState = EDITOR_IDLE, EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING"], + editorState = EDITOR_IDLE, // State machine. STATE_MACHINE, - highlightedEntityID = null, + highlightedEntityID = null, // Root entity of highlighted entity set. wasAppScaleWithHandles = false, isOtherEditorEditingEntityID = false, hoveredOverlayID = null, + doDeleteEntities = false, // Primary objects. hand, @@ -127,7 +128,14 @@ intersection; - hand = new Hand(side, gripPressedCallback); + function onGripClicked() { + // Delete entity set grabbed by this hand. + if (editorState === EDITOR_GRABBING) { + doDeleteEntities = true; + } + } + + hand = new Hand(side, onGripClicked); laser = new Laser(side); selection = new Selection(side); highlights = new Highlights(side); @@ -135,6 +143,7 @@ laserOffset = laser.handOffset(); + function setOtherEditor(editor) { otherEditor = editor; } @@ -427,6 +436,7 @@ } startEditing(); wasAppScaleWithHandles = isAppScaleWithHandles; + doDeleteEntities = false; } function updateEditorGrabbing() { @@ -637,7 +647,7 @@ } break; case EDITOR_GRABBING: - if (hand.valid() && hand.triggerClicked()) { + if (hand.valid() && hand.triggerClicked() && !doDeleteEntities) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (isAppScaleWithHandles !== wasAppScaleWithHandles) { @@ -655,6 +665,9 @@ } else { setState(EDITOR_SEARCHING); } + } else if (doDeleteEntities) { + selection.deleteEntities(); + setState(EDITOR_SEARCHING); } else { debug(side, "ERROR: Unexpected condition in EDITOR_GRABBING!"); } @@ -819,13 +832,6 @@ } } - function onGripClicked() { - // Do not change scale mode if are currently scaling. - if (!editors[LEFT_HAND].isScaling() && !editors[RIGHT_HAND].isScaling()) { - isAppScaleWithHandles = !isAppScaleWithHandles; - } - } - function onDominantHandChanged() { /* // TODO: API coming. @@ -854,8 +860,8 @@ } // Hands, each with a laser, selection, etc. - editors[LEFT_HAND] = new Editor(LEFT_HAND, onGripClicked); - editors[RIGHT_HAND] = new Editor(RIGHT_HAND, onGripClicked); + editors[LEFT_HAND] = new Editor(LEFT_HAND); + editors[RIGHT_HAND] = new Editor(RIGHT_HAND); editors[LEFT_HAND].setOtherEditor(editors[RIGHT_HAND]); editors[RIGHT_HAND].setOtherEditor(editors[LEFT_HAND]); From a7f7a2c401c1fefa8086cf47f2a3634b24475b92 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 22 Jul 2017 17:47:49 +1200 Subject: [PATCH 081/504] Add representative Tool menu UI elements --- scripts/vr-edit/modules/toolMenu.js | 136 ++++++++++++++++++++++++++++ scripts/vr-edit/vr-edit.js | 29 ++++-- 2 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 scripts/vr-edit/modules/toolMenu.js diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js new file mode 100644 index 0000000000..d67a9349e4 --- /dev/null +++ b/scripts/vr-edit/modules/toolMenu.js @@ -0,0 +1,136 @@ +// +// toolMenu.js +// +// Created by David Rowe on 22 Jul 2017. +// Copyright 2017 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 +// + +/* global ToolMenu */ + +ToolMenu = function (side, scaleModeChangedCallback) { + // Tool menu displayed on top of forearm. + + "use strict"; + + var panelEntity, + buttonEntity, + + LEFT_HAND = 0, + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + + FOREARM_JOINT_NAME = side === LEFT_HAND ? "LeftForeArm" : "RightForeArm", + HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + + ROOT_X_OFFSET = side === LEFT_HAND ? -0.05 : 0.05, + ROOT_Z_OFFSET = side === LEFT_HAND ? -0.05 : 0.05, + ROOT_ROTATION = side === LEFT_HAND + ? Quat.fromVec3Degrees({ x: 180, y: -90, z: 0 }) + : Quat.fromVec3Degrees({ x: 180, y: 90, z: 0 }), + + PANEL_ENTITY_PROPERTIES = { + type: "Box", + dimensions: { x: 0.1, y: 0.2, z: 0.01 }, + color: { red: 192, green: 192, blue: 192 }, + parentID: AVATAR_SELF_ID, + registrationPoint: { x: 0, y: 0.0, z: 0.0 }, + localRotation: ROOT_ROTATION, + ignoreRayIntersection: false, + lifetime: 3600, + visible: true + }, + + BUTTON_ENTITY_PROPERTIES = { + type: "Box", + dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + registrationPoint: { x: 0, y: 0.0, z: 0.0 }, + localPosition: { x: 0.005, y: 0.005, z: -0.005 }, // Relative to the root panel entity. + color: { red: 240, green: 0, blue: 0 }, + ignoreRayIntersection: false, + lifetime: 3600, + visible: true + }, + + isDisplaying = false, + + SCALE_MODE_DIRECT = 0, + SCALE_MODE_HANDLES = 1; + + function setHand(hand) { + side = hand; + + if (isDisplaying) { + // TODO: Move UI to other hand. + } + } + + function update() { + // TODO + } + + function display() { + // Creates and shows menu entities. + var forearmJointIndex, + handJointIndex, + forearmLength, + rootOffset; + + if (isDisplaying) { + return; + } + + // Joint indexes. + forearmJointIndex = MyAvatar.getJointIndex(FOREARM_JOINT_NAME); + handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + if (forearmJointIndex === -1 || handJointIndex === -1) { + // Don't display if joint isn't available (yet) to attach to. + // User can clear this condition by toggling the app off and back on once avatar finishes loading. + // TODO: Log error. + return; + } + + // Calculate position to put menu. + forearmLength = Vec3.distance(MyAvatar.getJointPosition(forearmJointIndex), MyAvatar.getJointPosition(handJointIndex)); + rootOffset = { x: ROOT_X_OFFSET, y: forearmLength, z: ROOT_Z_OFFSET }; + PANEL_ENTITY_PROPERTIES.parentJointIndex = forearmJointIndex; + PANEL_ENTITY_PROPERTIES.localPosition = rootOffset; + + // Create menu items. + panelEntity = Entities.addEntity(PANEL_ENTITY_PROPERTIES, true); + BUTTON_ENTITY_PROPERTIES.parentID = panelEntity; + buttonEntity = Entities.addEntity(BUTTON_ENTITY_PROPERTIES, true); + + isDisplaying = true; + } + + function clear() { + // Deletes menu entities. + if (!isDisplaying) { + return; + } + + Entities.deleteEntity(buttonEntity); + Entities.deleteEntity(panelEntity); + isDisplaying = false; + } + + function destroy() { + clear(); + } + + if (!this instanceof ToolMenu) { + return new ToolMenu(); + } + + return { + setHand: setHand, + update: update, + display: display, + clear: clear, + destroy: destroy + }; +}; + +ToolMenu.prototype = {}; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 48a066ea56..39c6dd9c04 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -15,9 +15,6 @@ var APP_NAME = "VR EDIT", // TODO: App name. APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons. APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", - tablet, - button, - VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. // Application state @@ -25,12 +22,11 @@ isAppScaleWithHandles = false, dominantHand, + // Primary objects editors = [], LEFT_HAND = 0, RIGHT_HAND = 1, - - UPDATE_LOOP_TIMEOUT = 16, - updateTimer = null, + toolMenu, // Modules Hand, @@ -38,8 +34,15 @@ Highlights, Laser, Selection, + ToolMenu, Editor, + // Miscellaneous + UPDATE_LOOP_TIMEOUT = 16, + updateTimer = null, + tablet, + button, + DEBUG = true; // TODO: Set false. // Utilities @@ -51,6 +54,7 @@ Script.include("./modules/highlights.js"); Script.include("./modules/laser.js"); Script.include("./modules/selection.js"); + Script.include("./modules/toolMenu.js"); function log(message) { @@ -823,20 +827,27 @@ button.editProperties({ isActive: isAppActive }); if (isAppActive) { + toolMenu.display(); update(); } else { Script.clearTimeout(updateTimer); updateTimer = null; editors[LEFT_HAND].clear(); editors[RIGHT_HAND].clear(); + toolMenu.clear(); } } + function otherHand(hand) { + return (hand + 1) % 2; + } + function onDominantHandChanged() { /* // TODO: API coming. dominantHand = TODO; */ + toolMenu.setHand(otherHand(dominantHand)); } @@ -872,6 +883,7 @@ dominantHand = TODO; TODO.change.connect(onDominantHandChanged); */ + toolMenu = new ToolMenu(otherHand(dominantHand)); if (isAppActive) { update(); @@ -896,6 +908,11 @@ button = null; } + if (toolMenu) { + toolMenu.destroy(); + toolMenu = null; + } + if (editors[LEFT_HAND]) { editors[LEFT_HAND].destroy(); editors[LEFT_HAND] = null; From 4353400d23482371586c4594c6fc97f9b4030c31 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 22 Jul 2017 17:55:00 +1200 Subject: [PATCH 082/504] Fix null state transition --- scripts/vr-edit/vr-edit.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 39c6dd9c04..7ca637497f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -745,7 +745,9 @@ } function clear() { - setState(EDITOR_IDLE); + if (editorState !== EDITOR_IDLE) { + setState(EDITOR_IDLE); + } hand.clear(); laser.clear(); From 1f244fb487080e26a4c2b607bf1f52bda9bc0395 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 24 Jul 2017 15:25:00 +1200 Subject: [PATCH 083/504] Better way of handling grip click --- scripts/vr-edit/modules/hand.js | 10 ++++++---- scripts/vr-edit/vr-edit.js | 15 +++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index a81c54e2cb..c8a473f623 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -10,7 +10,7 @@ /* global Hand */ -Hand = function (side, gripPressedCallback) { +Hand = function (side) { "use strict"; @@ -73,6 +73,10 @@ Hand = function (side, gripPressedCallback) { return isTriggerClicked; } + function gripPressed() { + return isGripPressed; + } + function getIntersection() { return intersection; } @@ -112,9 +116,6 @@ Hand = function (side, gripPressedCallback) { isGripPressed = gripValue > GRIP_OFF_VALUE; } else { isGripPressed = gripValue > GRIP_ON_VALUE; - if (isGripPressed) { - gripPressedCallback(); - } } // Hand-overlay intersection, if any. @@ -190,6 +191,7 @@ Hand = function (side, gripPressedCallback) { orientation: orientation, triggerPressed: triggerPressed, triggerClicked: triggerClicked, + gripPressed: gripPressed, intersection: getIntersection, update: update, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7ca637497f..a9dcde9242 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -98,7 +98,6 @@ wasAppScaleWithHandles = false, isOtherEditorEditingEntityID = false, hoveredOverlayID = null, - doDeleteEntities = false, // Primary objects. hand, @@ -132,14 +131,7 @@ intersection; - function onGripClicked() { - // Delete entity set grabbed by this hand. - if (editorState === EDITOR_GRABBING) { - doDeleteEntities = true; - } - } - - hand = new Hand(side, onGripClicked); + hand = new Hand(side); laser = new Laser(side); selection = new Selection(side); highlights = new Highlights(side); @@ -440,7 +432,6 @@ } startEditing(); wasAppScaleWithHandles = isAppScaleWithHandles; - doDeleteEntities = false; } function updateEditorGrabbing() { @@ -651,7 +642,7 @@ } break; case EDITOR_GRABBING: - if (hand.valid() && hand.triggerClicked() && !doDeleteEntities) { + if (hand.valid() && hand.triggerClicked() && !hand.gripPressed()) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (isAppScaleWithHandles !== wasAppScaleWithHandles) { @@ -669,7 +660,7 @@ } else { setState(EDITOR_SEARCHING); } - } else if (doDeleteEntities) { + } else if (hand.gripPressed()) { selection.deleteEntities(); setState(EDITOR_SEARCHING); } else { From 51522edd9445211b874ac3cb89bd604f1c2b6ce3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 24 Jul 2017 16:32:10 +1200 Subject: [PATCH 084/504] Separate out input objects from editor object --- scripts/vr-edit/vr-edit.js | 144 +++++++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 37 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a9dcde9242..3842fbcc23 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -23,6 +23,9 @@ dominantHand, // Primary objects + Inputs, + inputs = [], + Editor, editors = [], LEFT_HAND = 0, RIGHT_HAND = 1, @@ -35,7 +38,6 @@ Laser, Selection, ToolMenu, - Editor, // Miscellaneous UPDATE_LOOP_TIMEOUT = 16, @@ -76,8 +78,75 @@ } + Inputs = function (side) { + // A hand plus a laser. + + var + // Primary objects. + hand, + laser, + + intersection = { x: "hello" }; + + hand = new Hand(side); + laser = new Laser(side); + + function getHand() { + return hand; + } + + function getLaser() { + return laser; + } + + function getIntersection() { + return intersection; + } + + function update() { + // Hand update. + hand.update(); + intersection = hand.intersection(); + + // Laser update. + // Displays laser if hand has no intersection and trigger is pressed. + if (hand.valid()) { + laser.update(hand); + if (!intersection.intersects) { + intersection = laser.intersection(); + } + } + } + + function clear() { + hand.clear(); + laser.clear(); + } + + function destroy() { + if (hand) { + hand.destroy(); + hand = null; + } + if (laser) { + laser.destroy(); + laser = null; + } + } + + return { + hand: getHand, + laser: getLaser, + getIntersection: getIntersection, + update: update, + clear: clear, + destroy: destroy + }; + }; + + Editor = function (side) { - // Each controller has a hand, laser, an entity selection, entity highlighter, and entity handles. + // An entity selection, entity highlights, and entity handles. var otherEditor, // Other hand's Editor object. @@ -100,12 +169,14 @@ hoveredOverlayID = null, // Primary objects. - hand, - laser, selection, highlights, handles, + // Input objects. + hand, + laser, + // Position values. initialHandOrientationInverse, initialHandToSelectionVector, @@ -129,19 +200,20 @@ laserOffset, MIN_SCALE = 0.001, + getIntersection, // Function. intersection; - hand = new Hand(side); - laser = new Laser(side); selection = new Selection(side); highlights = new Highlights(side); handles = new Handles(side); - laserOffset = laser.handOffset(); + function setReferences(inputs, editor) { + hand = inputs.hand(); // Object. + laser = inputs.laser(); // Object. + getIntersection = inputs.getIntersection; // Function. + otherEditor = editor; // Object. - - function setOtherEditor(editor) { - otherEditor = editor; + laserOffset = laser.handOffset(); // Value. } function hoverHandle(overlayID) { @@ -544,18 +616,7 @@ var previousState = editorState, doUpdateState; - // Hand update. - hand.update(); - intersection = hand.intersection(); - - // Laser update. - // Displays laser if hand has no intersection and trigger is pressed. - if (hand.valid()) { - laser.update(hand); - if (!intersection.intersects) { - intersection = laser.intersection(); - } - } + intersection = getIntersection(); // State update. switch (editorState) { @@ -740,22 +801,12 @@ setState(EDITOR_IDLE); } - hand.clear(); - laser.clear(); selection.clear(); highlights.clear(); handles.clear(); } function destroy() { - if (hand) { - hand.destroy(); - hand = null; - } - if (laser) { - laser.destroy(); - laser = null; - } if (selection) { selection.destroy(); selection = null; @@ -775,7 +826,7 @@ } return { - setOtherEditor: setOtherEditor, + setReferences: setReferences, hoverHandle: hoverHandle, isHandle: isHandle, isEditing: isEditing, @@ -799,7 +850,11 @@ // Main update loop. updateTimer = null; - // Each hand's action depends on the state of the other hand, so update the states first then apply actions. + // Update inputs - hands and lasers. + inputs[LEFT_HAND].update(); + inputs[RIGHT_HAND].update(); + + // Each hand's edit action depends on the state of the other hand, so update the states first then apply actions. editors[LEFT_HAND].update(); editors[RIGHT_HAND].update(); editors[LEFT_HAND].apply(); @@ -825,6 +880,8 @@ } else { Script.clearTimeout(updateTimer); updateTimer = null; + inputs[LEFT_HAND].clear(); + inputs[RIGHT_HAND].clear(); editors[LEFT_HAND].clear(); editors[RIGHT_HAND].clear(); toolMenu.clear(); @@ -863,11 +920,15 @@ button.clicked.connect(onAppButtonClicked); } - // Hands, each with a laser, selection, etc. + // Input objects. + inputs[LEFT_HAND] = new Inputs(LEFT_HAND); + inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); + + // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); editors[RIGHT_HAND] = new Editor(RIGHT_HAND); - editors[LEFT_HAND].setOtherEditor(editors[RIGHT_HAND]); - editors[RIGHT_HAND].setOtherEditor(editors[LEFT_HAND]); + editors[LEFT_HAND].setReferences(inputs[LEFT_HAND], editors[RIGHT_HAND]); + editors[RIGHT_HAND].setReferences(inputs[RIGHT_HAND], editors[LEFT_HAND]); // Dominant hand from settings. // TODO: API coming. @@ -915,6 +976,15 @@ editors[RIGHT_HAND] = null; } + if (inputs[LEFT_HAND]) { + inputs[LEFT_HAND].destroy(); + inputs[LEFT_HAND] = null; + } + if (inputs[RIGHT_HAND]) { + inputs[RIGHT_HAND].destroy(); + inputs[RIGHT_HAND] = null; + } + tablet = null; } From 72e3c9a881ff75b5af82a952a8083cb508ee2716 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 24 Jul 2017 16:59:34 +1200 Subject: [PATCH 085/504] Insert UI handling between input updates and editor actions --- scripts/vr-edit/vr-edit.js | 76 +++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3842fbcc23..d9c17bcfb5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -25,11 +25,12 @@ // Primary objects Inputs, inputs = [], + UI, + ui, Editor, editors = [], LEFT_HAND = 0, RIGHT_HAND = 1, - toolMenu, // Modules Hand, @@ -134,6 +135,10 @@ } } + if (!this instanceof Inputs) { + return new Inputs(); + } + return { hand: getHand, laser: getLaser, @@ -145,6 +150,52 @@ }; + UI = function (side) { + // Tool menu and Create palette. + + var // Primary objects. + toolMenu; + + toolMenu = new ToolMenu(side); + + + function setHand(side) { + toolMenu.setHand(side); + } + + function display() { + toolMenu.display(); + } + + function update() { + // TODO + } + + function clear() { + toolMenu.clear(); + } + + function destroy() { + if (toolMenu) { + toolMenu.destroy(); + toolMenu = null; + } + } + + if (!this instanceof UI) { + return new UI(); + } + + return { + setHand: setHand, + display: display, + update: update, + clear: clear, + destroy: destroy + }; + }; + + Editor = function (side) { // An entity selection, entity highlights, and entity handles. @@ -854,6 +905,9 @@ inputs[LEFT_HAND].update(); inputs[RIGHT_HAND].update(); + // UI has first dibs on handling inputs. + ui.update(); + // Each hand's edit action depends on the state of the other hand, so update the states first then apply actions. editors[LEFT_HAND].update(); editors[RIGHT_HAND].update(); @@ -875,16 +929,16 @@ button.editProperties({ isActive: isAppActive }); if (isAppActive) { - toolMenu.display(); + ui.display(); update(); } else { Script.clearTimeout(updateTimer); updateTimer = null; inputs[LEFT_HAND].clear(); inputs[RIGHT_HAND].clear(); + ui.clear(); editors[LEFT_HAND].clear(); editors[RIGHT_HAND].clear(); - toolMenu.clear(); } } @@ -897,7 +951,7 @@ // TODO: API coming. dominantHand = TODO; */ - toolMenu.setHand(otherHand(dominantHand)); + ui.setHand(otherHand(dominantHand)); } @@ -924,6 +978,9 @@ inputs[LEFT_HAND] = new Inputs(LEFT_HAND); inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); + // UI object. + ui = new UI(otherHand(dominantHand)); + // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); editors[RIGHT_HAND] = new Editor(RIGHT_HAND); @@ -937,7 +994,6 @@ dominantHand = TODO; TODO.change.connect(onDominantHandChanged); */ - toolMenu = new ToolMenu(otherHand(dominantHand)); if (isAppActive) { update(); @@ -962,11 +1018,6 @@ button = null; } - if (toolMenu) { - toolMenu.destroy(); - toolMenu = null; - } - if (editors[LEFT_HAND]) { editors[LEFT_HAND].destroy(); editors[LEFT_HAND] = null; @@ -976,6 +1027,11 @@ editors[RIGHT_HAND] = null; } + if (ui) { + ui.destroy(); + ui = null; + } + if (inputs[LEFT_HAND]) { inputs[LEFT_HAND].destroy(); inputs[LEFT_HAND] = null; From bf69402219b99b0cb93539b1f0ddc2983e0d28be Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 15:31:54 +1200 Subject: [PATCH 086/504] Laser intersect but don't highlight non-editable entities --- scripts/vr-edit/modules/hand.js | 5 +++-- scripts/vr-edit/modules/laser.js | 5 +---- scripts/vr-edit/vr-edit.js | 18 +++++++++--------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index c8a473f623..c4f5a6efc9 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -139,7 +139,7 @@ Hand = function (side) { } } - // Hand-entity intersection, if any, if overlay not intersected. + // Hand-entity intersection, if any editable, if overlay not intersected. entityID = null; if (overlayID === null) { // palmPosition is set above. @@ -169,7 +169,8 @@ Hand = function (side) { intersects: overlayID !== null || entityID !== null, overlayID: overlayID, entityID: entityID, - handIntersected: true + handIntersected: true, + editableEntity: entityID !== null }; } diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 0439baef72..e7016af315 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -154,10 +154,7 @@ Laser = function (side) { if (!intersection.intersects) { intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - if (intersection.intersects && !Entities.hasEditableRoot(intersection.entityID)) { - intersection.intersects = false; - intersection.entityID = null; - } + intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); } intersection.laserIntersected = true; laserLength = (specifiedLaserLength !== null) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d9c17bcfb5..99b813c4fc 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -679,7 +679,7 @@ setState(EDITOR_SEARCHING); break; case EDITOR_SEARCHING: - if (hand.valid() && !intersection.entityID + if (hand.valid() && (!intersection.entityID || !intersection.editableEntity) && !(intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID))) { // No transition. updateState(); @@ -691,10 +691,10 @@ && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && !hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && !hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); if (otherEditor.isEditing(highlightedEntityID)) { if (!isAppScaleWithHandles) { @@ -709,7 +709,7 @@ break; case EDITOR_HIGHLIGHTING: if (hand.valid() - && intersection.entityID + && intersection.entityID && intersection.editableEntity && !(hand.triggerClicked() && (!otherEditor.isEditing(highlightedEntityID) || !isAppScaleWithHandles)) && !(hand.triggerClicked() && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { // No transition. @@ -736,7 +736,7 @@ && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. if (otherEditor.isEditing(highlightedEntityID)) { if (!isAppScaleWithHandles) { @@ -747,7 +747,7 @@ } else { setState(EDITOR_GRABBING); } - } else if (!intersection.entityID) { + } else if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! B"); @@ -766,7 +766,7 @@ if (!hand.valid()) { setState(EDITOR_IDLE); } else if (!hand.triggerClicked()) { - if (intersection.entityID) { + if (intersection.entityID && intersection.editableEntity) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); } else { @@ -792,7 +792,7 @@ if (!hand.valid()) { setState(EDITOR_IDLE); } else if (!hand.triggerClicked()) { - if (!intersection.entityID) { + if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { highlightedEntityID = Entities.rootOf(intersection.entityID); @@ -815,7 +815,7 @@ if (!hand.valid()) { setState(EDITOR_IDLE); } else if (!hand.triggerClicked()) { - if (!intersection.entityID) { + if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { highlightedEntityID = Entities.rootOf(intersection.entityID); From 0f2176127e6ec5bd7899cb09204fc6e01dc903c0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 15:41:16 +1200 Subject: [PATCH 087/504] Fix locations of dominant hand settings and update setup --- scripts/vr-edit/vr-edit.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 99b813c4fc..dcd8145472 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -963,6 +963,13 @@ return; } + // Settings values. + // TODO: API coming. + dominantHand = RIGHT_HAND; + /* + dominantHand = TODO; + */ + // Tablet/toolbar button. button = tablet.addButton({ icon: APP_ICON_INACTIVE, @@ -987,11 +994,9 @@ editors[LEFT_HAND].setReferences(inputs[LEFT_HAND], editors[RIGHT_HAND]); editors[RIGHT_HAND].setReferences(inputs[RIGHT_HAND], editors[LEFT_HAND]); - // Dominant hand from settings. - // TODO: API coming. - dominantHand = RIGHT_HAND; + // Settings changes. /* - dominantHand = TODO; + // TODO: API coming. TODO.change.connect(onDominantHandChanged); */ From 194a82974bc3da7c3277745ed9cd51e57fae3dc8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 15:53:34 +1200 Subject: [PATCH 088/504] Make laser dot a minimum size commensurate with near laser beam width --- scripts/vr-edit/modules/laser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index e7016af315..2a765cfb4d 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -25,6 +25,7 @@ Laser = function (side) { searchDistance = 0.0, SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. + MINUMUM_SEARCH_SPHERE_SIZE = 0.006, SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. @@ -116,7 +117,7 @@ Laser = function (side) { searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); - sphereSize = SEARCH_SPHERE_SIZE * searchDistance; + sphereSize = Math.max(SEARCH_SPHERE_SIZE * searchDistance, MINUMUM_SEARCH_SPHERE_SIZE); color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; From 8dceb3cc6c0c2d6aee62fec5f5aa667748a48b44 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 17:26:43 +1200 Subject: [PATCH 089/504] Show laser dot on UI even if trigger isn't squeezed --- scripts/vr-edit/modules/laser.js | 73 ++++++++++++++++++++++------- scripts/vr-edit/modules/toolMenu.js | 5 ++ scripts/vr-edit/vr-edit.js | 26 +++++++++- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 2a765cfb4d..22d7b5794f 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -12,7 +12,8 @@ Laser = function (side) { // Draws hand lasers. - // May intersect with entities or bounding box of other hand's selection. + // May intersect with overlays or entities, or bounding box of other hand's selection. + // Laser dot is always drawn on UI entities. "use strict"; @@ -47,6 +48,8 @@ Laser = function (side) { LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + uiEntityIDs = [], + intersection; function colorPow(color, power) { // Per handControllerGrab.js. @@ -109,7 +112,7 @@ Laser = function (side) { }); } - function display(origin, direction, distance, isClicked) { + function display(origin, direction, distance, isPressed, isClicked) { var searchTarget, sphereSize, color, @@ -121,7 +124,11 @@ Laser = function (side) { color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; - updateLine(origin, searchTarget, color); + if (isPressed) { + updateLine(origin, searchTarget, color); + } else { + Overlays.editOverlay(laserLine, { visible: false }); + } updateSphere(searchTarget, sphereSize, color, brightColor); } @@ -130,6 +137,10 @@ Laser = function (side) { Overlays.editOverlay(laserSphere, { visible: false }); } + function setUIEntities(entityIDs) { + uiEntityIDs = entityIDs; + } + function update(hand) { var handPosition, handOrientation, @@ -140,7 +151,7 @@ Laser = function (side) { return; } - if (!hand.intersection().intersects && hand.triggerPressed()) { + if (!hand.intersection().intersects) { handPosition = hand.position(); handOrientation = hand.orientation(); deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); @@ -150,20 +161,47 @@ Laser = function (side) { length: PICK_MAX_DISTANCE }; - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); - } - intersection.laserIntersected = true; - laserLength = (specifiedLaserLength !== null) - ? specifiedLaserLength - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + if (hand.triggerPressed()) { - isLaserOn = true; - display(pickRay.origin, pickRay.direction, laserLength, hand.triggerClicked()); + // Normal laser operation with trigger. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); + } + intersection.laserIntersected = true; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); + + } else { + + // Special hovering of UI. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); // Check for overlay intersections in case they occlude the UI entities. + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + } + if (intersection.intersects && intersection.entityID) { + intersection.laserIntersected = true; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, false, false); + } else { + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } + + } } else { intersection = { intersects: false @@ -223,6 +261,7 @@ Laser = function (side) { } return { + setUIEntities: setUIEntities, update: update, intersection: getIntersection, setLength: setLength, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d67a9349e4..6e81ef7634 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -66,6 +66,10 @@ ToolMenu = function (side, scaleModeChangedCallback) { } } + function getEntityIDs() { + return [panelEntity, buttonEntity]; + } + function update() { // TODO } @@ -126,6 +130,7 @@ ToolMenu = function (side, scaleModeChangedCallback) { return { setHand: setHand, + getEntityIDs: getEntityIDs, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index dcd8145472..c03e40284e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -92,6 +92,10 @@ hand = new Hand(side); laser = new Laser(side); + function setUIEntities(entityIDs) { + laser.setUIEntities(entityIDs); + } + function getHand() { return hand; } @@ -140,6 +144,7 @@ } return { + setUIEntities: setUIEntities, hand: getHand, laser: getLaser, getIntersection: getIntersection, @@ -154,17 +159,32 @@ // Tool menu and Create palette. var // Primary objects. - toolMenu; + toolMenu, + + // References. + leftInputs, + rightInputs; toolMenu = new ToolMenu(side); + function setReferences(left, right) { + leftInputs = left; + rightInputs = right; + } + function setHand(side) { toolMenu.setHand(side); } function display() { + var uiEntityIDs; + toolMenu.display(); + + uiEntityIDs = toolMenu.getEntityIDs(); + leftInputs.setUIEntities(uiEntityIDs); + rightInputs.setUIEntities(uiEntityIDs); } function update() { @@ -172,6 +192,8 @@ } function clear() { + leftInputs.setUIEntities([]); + rightInputs.setUIEntities([]); toolMenu.clear(); } @@ -187,6 +209,7 @@ } return { + setReferences: setReferences, setHand: setHand, display: display, update: update, @@ -987,6 +1010,7 @@ // UI object. ui = new UI(otherHand(dominantHand)); + ui.setReferences(inputs[LEFT_HAND], inputs[RIGHT_HAND]); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From 52759be61836ad93e1c14c5e30874a0afc34d287 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 20:40:12 +1200 Subject: [PATCH 090/504] Don't transition laser length when start intersecting UI --- scripts/vr-edit/modules/laser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 22d7b5794f..3f875ccf06 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -192,6 +192,10 @@ Laser = function (side) { laserLength = (specifiedLaserLength !== null) ? specifiedLaserLength : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + if (!isLaserOn) { + // Start laser dot at UI distance. + searchDistance = laserLength; + } isLaserOn = true; display(pickRay.origin, pickRay.direction, laserLength, false, false); } else { From 3566609173c53d356b977c0335981a631b1e51a0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 21:34:13 +1200 Subject: [PATCH 091/504] Highlight button as it is hovered --- scripts/vr-edit/modules/toolMenu.js | 28 ++++++++++++++++++++++++++-- scripts/vr-edit/vr-edit.js | 20 +++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6e81ef7634..2ffb044e3a 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -17,6 +17,7 @@ ToolMenu = function (side, scaleModeChangedCallback) { var panelEntity, buttonEntity, + buttonHighlightOverlay, LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", @@ -30,6 +31,8 @@ ToolMenu = function (side, scaleModeChangedCallback) { ? Quat.fromVec3Degrees({ x: 180, y: -90, z: 0 }) : Quat.fromVec3Degrees({ x: 180, y: 90, z: 0 }), + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + PANEL_ENTITY_PROPERTIES = { type: "Box", dimensions: { x: 0.1, y: 0.2, z: 0.01 }, @@ -53,7 +56,20 @@ ToolMenu = function (side, scaleModeChangedCallback) { visible: true }, + BUTTON_HIGHLIGHT_PROPERTIES = { + dimensions: { x: 0.034, y: 0.034, z: 0.001 }, + color: { red: 240, green: 240, blue: 0 }, + alpha: 0.8, + localPosition: { x: 0.0155, y: 0.0155, z: 0.003 }, + localRotation: ZERO_ROTATION, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }, + isDisplaying = false, + isHighlightingButton = false, SCALE_MODE_DIRECT = 0, SCALE_MODE_HANDLES = 1; @@ -70,8 +86,12 @@ ToolMenu = function (side, scaleModeChangedCallback) { return [panelEntity, buttonEntity]; } - function update() { - // TODO + function update(intersectionEntityID) { + // Highlight button. + if (intersectionEntityID === buttonEntity !== isHighlightingButton) { + isHighlightingButton = !isHighlightingButton; + Overlays.editOverlay(buttonHighlightOverlay, { visible: isHighlightingButton }); + } } function display() { @@ -106,6 +126,10 @@ ToolMenu = function (side, scaleModeChangedCallback) { BUTTON_ENTITY_PROPERTIES.parentID = panelEntity; buttonEntity = Entities.addEntity(BUTTON_ENTITY_PROPERTIES, true); + // Prepare highlight overlay. + BUTTON_HIGHLIGHT_PROPERTIES.parentID = buttonEntity; + buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); + isDisplaying = true; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c03e40284e..7cff8fc9f6 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -87,7 +87,7 @@ hand, laser, - intersection = { x: "hello" }; + intersection = {}; hand = new Hand(side); laser = new Laser(side); @@ -163,7 +163,12 @@ // References. leftInputs, - rightInputs; + rightInputs, + + isDisplaying = false, + + getIntersection, // Function. + intersection; toolMenu = new ToolMenu(side); @@ -171,10 +176,12 @@ function setReferences(left, right) { leftInputs = left; rightInputs = right; + getIntersection = side === LEFT_HAND ? rightInputs.getIntersection : leftInputs.getIntersection; } function setHand(side) { toolMenu.setHand(side); + getIntersection = side === LEFT_HAND ? rightInputs.getIntersection : leftInputs.getIntersection; } function display() { @@ -185,16 +192,23 @@ uiEntityIDs = toolMenu.getEntityIDs(); leftInputs.setUIEntities(uiEntityIDs); rightInputs.setUIEntities(uiEntityIDs); + + isDisplaying = true; } function update() { - // TODO + if (isDisplaying) { + intersection = getIntersection(); + toolMenu.update(intersection.entityID); + } } function clear() { leftInputs.setUIEntities([]); rightInputs.setUIEntities([]); toolMenu.clear(); + + isDisplaying = false; } function destroy() { From d4b872d9e1cacca55dcaebafa85e02301f2129d5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 22:30:52 +1200 Subject: [PATCH 092/504] Detect button press --- scripts/vr-edit/modules/hand.js | 3 ++- scripts/vr-edit/modules/toolMenu.js | 25 ++++++++++++++++++++++--- scripts/vr-edit/vr-edit.js | 12 +++++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index c4f5a6efc9..48e44069ed 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -28,6 +28,7 @@ Hand = function (side) { isTriggerClicked, TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + TRIGGER_CLICKED_VALUE = 1.0, NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. NEAR_HOVER_RADIUS = 0.025, @@ -108,7 +109,7 @@ Hand = function (side) { // Controller trigger. isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); - isTriggerClicked = Controller.getValue(controllerTriggerClicked); + isTriggerClicked = Controller.getValue(controllerTriggerClicked) === TRIGGER_CLICKED_VALUE; // Controller grip. gripValue = Controller.getValue(controllerGrip); diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 2ffb044e3a..089fb3f3e3 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -70,12 +70,25 @@ ToolMenu = function (side, scaleModeChangedCallback) { isDisplaying = false, isHighlightingButton = false, + isButtonPressed = false, SCALE_MODE_DIRECT = 0, - SCALE_MODE_HANDLES = 1; + SCALE_MODE_HANDLES = 1, - function setHand(hand) { - side = hand; + // References. + leftInputs, + rightInputs, + controlHand; + + function setReferences(left, right) { + leftInputs = left; + rightInputs = right; + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + } + + function setHand(uiSide) { + side = uiSide; + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); if (isDisplaying) { // TODO: Move UI to other hand. @@ -92,6 +105,11 @@ ToolMenu = function (side, scaleModeChangedCallback) { isHighlightingButton = !isHighlightingButton; Overlays.editOverlay(buttonHighlightOverlay, { visible: isHighlightingButton }); } + + // Button click. + if (isHighlightingButton && controlHand.triggerClicked() !== isButtonPressed) { + isButtonPressed = controlHand.triggerClicked(); + } } function display() { @@ -153,6 +171,7 @@ ToolMenu = function (side, scaleModeChangedCallback) { } return { + setReferences: setReferences, setHand: setHand, getEntityIDs: getEntityIDs, update: update, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7cff8fc9f6..c8fa86fcad 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -155,7 +155,7 @@ }; - UI = function (side) { + UI = function (side, setAppScaleWithHandlesCallback) { // Tool menu and Create palette. var // Primary objects. @@ -170,13 +170,15 @@ getIntersection, // Function. intersection; - toolMenu = new ToolMenu(side); + toolMenu = new ToolMenu(side, setAppScaleWithHandlesCallback); function setReferences(left, right) { leftInputs = left; rightInputs = right; getIntersection = side === LEFT_HAND ? rightInputs.getIntersection : leftInputs.getIntersection; + + toolMenu.setReferences(left, right); } function setHand(side) { @@ -959,6 +961,10 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } + function setAppScaleWithHandles(appScaleWithHandles) { + isAppScaleWithHandles = appScaleWithHandles; + } + function onAppButtonClicked() { // Application tablet/toolbar button clicked. isAppActive = !isAppActive; @@ -1023,7 +1029,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(otherHand(dominantHand)); + ui = new UI(otherHand(dominantHand), setAppScaleWithHandles); ui.setReferences(inputs[LEFT_HAND], inputs[RIGHT_HAND]); // Editor objects. From 6203a1ad68a54f2bb7178b5c9188b9eaace8ef1f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 26 Jul 2017 22:41:38 +1200 Subject: [PATCH 093/504] Wire up button to toggle scale-with-hands / scale-with-handles --- scripts/vr-edit/modules/toolMenu.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 089fb3f3e3..d64926ce5e 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,12 +10,18 @@ /* global ToolMenu */ -ToolMenu = function (side, scaleModeChangedCallback) { +ToolMenu = function (side, setAppScaleWithHandlesCallback) { // Tool menu displayed on top of forearm. "use strict"; - var panelEntity, + var SCALE_MODE_DIRECT = 0, + SCALE_MODE_HANDLES = 1, + scaleMode = SCALE_MODE_DIRECT, + SCALE_MODE_DIRECT_COLOR = { red: 240, green: 240, blue: 0 }, + SCALE_MODE_HANDLES_COLOR = { red: 0, green: 240, blue: 240 }, + + panelEntity, buttonEntity, buttonHighlightOverlay, @@ -50,7 +56,7 @@ ToolMenu = function (side, scaleModeChangedCallback) { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, registrationPoint: { x: 0, y: 0.0, z: 0.0 }, localPosition: { x: 0.005, y: 0.005, z: -0.005 }, // Relative to the root panel entity. - color: { red: 240, green: 0, blue: 0 }, + color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR, ignoreRayIntersection: false, lifetime: 3600, visible: true @@ -72,9 +78,6 @@ ToolMenu = function (side, scaleModeChangedCallback) { isHighlightingButton = false, isButtonPressed = false, - SCALE_MODE_DIRECT = 0, - SCALE_MODE_HANDLES = 1, - // References. leftInputs, rightInputs, @@ -109,6 +112,14 @@ ToolMenu = function (side, scaleModeChangedCallback) { // Button click. if (isHighlightingButton && controlHand.triggerClicked() !== isButtonPressed) { isButtonPressed = controlHand.triggerClicked(); + + if (isButtonPressed) { + scaleMode = scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_HANDLES : SCALE_MODE_DIRECT; + Entities.editEntity(buttonEntity, { + color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR + }); + setAppScaleWithHandlesCallback(scaleMode === SCALE_MODE_HANDLES); + } } } From 136ea78873f27da6fe1dd1d6e8477b61124dd576 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 27 Jul 2017 13:47:35 +1200 Subject: [PATCH 094/504] Use overlays instead of entities --- scripts/vr-edit/modules/laser.js | 12 ++---- scripts/vr-edit/modules/toolMenu.js | 67 +++++++++++++++++------------ scripts/vr-edit/vr-edit.js | 2 +- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 3f875ccf06..3130fbbb25 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -180,14 +180,10 @@ Laser = function (side) { } else { - // Special hovering of UI. - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); // Check for overlay intersections in case they occlude the UI entities. - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - } - if (intersection.intersects && intersection.entityID) { + // Special UI cursor. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (intersection.intersects) { intersection.laserIntersected = true; laserLength = (specifiedLaserLength !== null) ? specifiedLaserLength diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d64926ce5e..f87f3a5301 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -21,8 +21,9 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { SCALE_MODE_DIRECT_COLOR = { red: 240, green: 240, blue: 0 }, SCALE_MODE_HANDLES_COLOR = { red: 0, green: 240, blue: 240 }, - panelEntity, - buttonEntity, + originOverlay, + panelOverlay, + buttonOverlay, buttonHighlightOverlay, LEFT_HAND = 0, @@ -39,35 +40,44 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - PANEL_ENTITY_PROPERTIES = { - type: "Box", - dimensions: { x: 0.1, y: 0.2, z: 0.01 }, - color: { red: 192, green: 192, blue: 192 }, + ORIGIN_PROPERTIES = { + dimensions: { x: 0.005, y: 0.005, z: 0.005 }, + color: { red: 255, blue: 0, green: 0 }, + alpha: 1.0, parentID: AVATAR_SELF_ID, - registrationPoint: { x: 0, y: 0.0, z: 0.0 }, localRotation: ROOT_ROTATION, + ignoreRayIntersection: true, + visible: false + }, + + PANEL_PROPERTIES = { + dimensions: { x: 0.1, y: 0.2, z: 0.01 }, + localPosition: { x: 0.05, y: 0.1, z: 0.005 }, + localRotation: ZERO_ROTATION, + color: { red: 192, green: 192, blue: 192 }, + alpha: 1.0, + solid: true, ignoreRayIntersection: false, - lifetime: 3600, visible: true }, - BUTTON_ENTITY_PROPERTIES = { - type: "Box", + BUTTON_PROPERTIES = { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, - registrationPoint: { x: 0, y: 0.0, z: 0.0 }, - localPosition: { x: 0.005, y: 0.005, z: -0.005 }, // Relative to the root panel entity. + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: ZERO_ROTATION, color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR, + alpha: 1.0, + solid: true, ignoreRayIntersection: false, - lifetime: 3600, visible: true }, BUTTON_HIGHLIGHT_PROPERTIES = { dimensions: { x: 0.034, y: 0.034, z: 0.001 }, + localPosition: { x: 0, y: 0, z: -0.002 }, + localRotation: ZERO_ROTATION, color: { red: 240, green: 240, blue: 0 }, alpha: 0.8, - localPosition: { x: 0.0155, y: 0.0155, z: 0.003 }, - localRotation: ZERO_ROTATION, solid: false, drawInFront: true, ignoreRayIntersection: true, @@ -99,12 +109,12 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { } function getEntityIDs() { - return [panelEntity, buttonEntity]; + return [panelOverlay, buttonOverlay]; } - function update(intersectionEntityID) { + function update(intersectionOverlayID) { // Highlight button. - if (intersectionEntityID === buttonEntity !== isHighlightingButton) { + if (intersectionOverlayID === buttonOverlay !== isHighlightingButton) { isHighlightingButton = !isHighlightingButton; Overlays.editOverlay(buttonHighlightOverlay, { visible: isHighlightingButton }); } @@ -115,7 +125,7 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { if (isButtonPressed) { scaleMode = scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_HANDLES : SCALE_MODE_DIRECT; - Entities.editEntity(buttonEntity, { + Overlays.editOverlay(buttonOverlay, { color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR }); setAppScaleWithHandlesCallback(scaleMode === SCALE_MODE_HANDLES); @@ -147,16 +157,18 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { // Calculate position to put menu. forearmLength = Vec3.distance(MyAvatar.getJointPosition(forearmJointIndex), MyAvatar.getJointPosition(handJointIndex)); rootOffset = { x: ROOT_X_OFFSET, y: forearmLength, z: ROOT_Z_OFFSET }; - PANEL_ENTITY_PROPERTIES.parentJointIndex = forearmJointIndex; - PANEL_ENTITY_PROPERTIES.localPosition = rootOffset; + ORIGIN_PROPERTIES.parentJointIndex = forearmJointIndex; + ORIGIN_PROPERTIES.localPosition = rootOffset; + originOverlay = Overlays.addOverlay("sphere", ORIGIN_PROPERTIES); // Create menu items. - panelEntity = Entities.addEntity(PANEL_ENTITY_PROPERTIES, true); - BUTTON_ENTITY_PROPERTIES.parentID = panelEntity; - buttonEntity = Entities.addEntity(BUTTON_ENTITY_PROPERTIES, true); + PANEL_PROPERTIES.parentID = originOverlay; + panelOverlay = Overlays.addOverlay("cube", PANEL_PROPERTIES); + BUTTON_PROPERTIES.parentID = originOverlay; + buttonOverlay = Overlays.addOverlay("cube", BUTTON_PROPERTIES); // Prepare highlight overlay. - BUTTON_HIGHLIGHT_PROPERTIES.parentID = buttonEntity; + BUTTON_HIGHLIGHT_PROPERTIES.parentID = buttonOverlay; buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); isDisplaying = true; @@ -168,8 +180,9 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { return; } - Entities.deleteEntity(buttonEntity); - Entities.deleteEntity(panelEntity); + Overlays.deleteOverlay(buttonHighlightOverlay); + Overlays.deleteOverlay(buttonOverlay); + Overlays.deleteOverlay(panelOverlay); isDisplaying = false; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c8fa86fcad..e38640c593 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -201,7 +201,7 @@ function update() { if (isDisplaying) { intersection = getIntersection(); - toolMenu.update(intersection.entityID); + toolMenu.update(intersection.overlayID); } } From 573a49853ad866e79a85c6e47114d9829078d2dc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 27 Jul 2017 15:09:22 +1200 Subject: [PATCH 095/504] Tidying --- scripts/vr-edit/modules/hand.js | 8 +-- scripts/vr-edit/modules/handles.js | 8 +-- scripts/vr-edit/modules/highlights.js | 8 +-- scripts/vr-edit/modules/laser.js | 10 +-- scripts/vr-edit/modules/selection.js | 8 +-- scripts/vr-edit/modules/toolMenu.js | 20 +++--- scripts/vr-edit/vr-edit.js | 87 ++++++++++++--------------- 7 files changed, 67 insertions(+), 82 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 48e44069ed..0430067eb9 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -42,6 +42,10 @@ Hand = function (side) { intersection = {}; + if (!this instanceof Hand) { + return new Hand(side); + } + if (side === LEFT_HAND) { handController = Controller.Standard.LeftHand; controllerTrigger = Controller.Standard.LT; @@ -183,10 +187,6 @@ Hand = function (side) { // Nothing to do. } - if (!this instanceof Hand) { - return new Hand(side); - } - return { valid: valid, position: position, diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index 6f238ccb79..e124e5f945 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -49,6 +49,10 @@ Handles = function (side) { i; + if (!this instanceof Handles) { + return new Handles(side); + } + CORNER_HANDLE_OVERLAY_AXES = [ // Ordered such that items 4 apart are opposite corners - used in display(). { x: -0.5, y: -0.5, z: -0.5 }, @@ -350,10 +354,6 @@ Handles = function (side) { Overlays.deleteOverlay(boundingBoxOverlay); } - if (!this instanceof Handles) { - return new Handles(side); - } - return { display: display, isHandle: isHandle, diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 26170e9374..ecde8ffa3e 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -27,6 +27,10 @@ Highlights = function (side) { AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO); + if (!this instanceof Highlights) { + return new Highlights(); + } + handOverlay = Overlays.addOverlay("sphere", { dimensions: HAND_HIGHLIGHT_DIMENSIONS, parentID: AVATAR_SELF_ID, @@ -109,10 +113,6 @@ Highlights = function (side) { Overlays.deleteOverlay(handOverlay); } - if (!this instanceof Highlights) { - return new Highlights(); - } - return { display: display, clear: clear, diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 3130fbbb25..5ef0a223cf 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -32,7 +32,7 @@ Laser = function (side) { COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, - BRIGHT_POW = 0.06, // Per handControllgerGrab.js. + BRIGHT_POW = 0.06, // Per handControllerGrab.js. GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. @@ -52,6 +52,10 @@ Laser = function (side) { intersection; + if (!this instanceof Laser) { + return new Laser(side); + } + function colorPow(color, power) { // Per handControllerGrab.js. return { red: Math.pow(color.red / 255, power) * 255, @@ -256,10 +260,6 @@ Laser = function (side) { Overlays.deleteOverlay(laserSphere); } - if (!this instanceof Laser) { - return new Laser(side); - } - return { setUIEntities: setUIEntities, update: update, diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index bf1d519941..5437e49bf3 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -25,6 +25,10 @@ Selection = function (side) { scaleRootOrientation, ENTITY_TYPE = "entity"; + if (!this instanceof Selection) { + return new Selection(side); + } + function traverseEntityTree(id, result) { // Recursively traverses tree of entities and their children, gather IDs and properties. var children, @@ -307,10 +311,6 @@ Selection = function (side) { clear(); } - if (!this instanceof Selection) { - return new Selection(side); - } - return { select: select, selection: getSelection, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index f87f3a5301..26adbf5a77 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,7 +10,7 @@ /* global ToolMenu */ -ToolMenu = function (side, setAppScaleWithHandlesCallback) { +ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -89,16 +89,15 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { isButtonPressed = false, // References. - leftInputs, - rightInputs, controlHand; - function setReferences(left, right) { - leftInputs = left; - rightInputs = right; - controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + + if (!this instanceof ToolMenu) { + return new ToolMenu(); } + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + function setHand(uiSide) { side = uiSide; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); @@ -190,14 +189,9 @@ ToolMenu = function (side, setAppScaleWithHandlesCallback) { clear(); } - if (!this instanceof ToolMenu) { - return new ToolMenu(); - } - return { - setReferences: setReferences, setHand: setHand, - getEntityIDs: getEntityIDs, + entityIDs: getEntityIDs, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e38640c593..bd8af9dabd 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -89,9 +89,15 @@ intersection = {}; + + if (!this instanceof Inputs) { + return new Inputs(); + } + hand = new Hand(side); laser = new Laser(side); + function setUIEntities(entityIDs) { laser.setUIEntities(entityIDs); } @@ -139,15 +145,11 @@ } } - if (!this instanceof Inputs) { - return new Inputs(); - } - return { setUIEntities: setUIEntities, hand: getHand, laser: getLaser, - getIntersection: getIntersection, + intersection: getIntersection, update: update, clear: clear, destroy: destroy @@ -155,35 +157,29 @@ }; - UI = function (side, setAppScaleWithHandlesCallback) { + UI = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallback) { // Tool menu and Create palette. var // Primary objects. toolMenu, - // References. - leftInputs, - rightInputs, - isDisplaying = false, - getIntersection, // Function. - intersection; - - toolMenu = new ToolMenu(side, setAppScaleWithHandlesCallback); + getIntersection; // Function. - function setReferences(left, right) { - leftInputs = left; - rightInputs = right; - getIntersection = side === LEFT_HAND ? rightInputs.getIntersection : leftInputs.getIntersection; - - toolMenu.setReferences(left, right); + if (!this instanceof UI) { + return new UI(); } + toolMenu = new ToolMenu(side, leftInputs, rightInputs, setAppScaleWithHandlesCallback); + + getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; + + function setHand(side) { toolMenu.setHand(side); - getIntersection = side === LEFT_HAND ? rightInputs.getIntersection : leftInputs.getIntersection; + getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; } function display() { @@ -191,17 +187,16 @@ toolMenu.display(); - uiEntityIDs = toolMenu.getEntityIDs(); - leftInputs.setUIEntities(uiEntityIDs); - rightInputs.setUIEntities(uiEntityIDs); + uiEntityIDs = toolMenu.entityIDs(); + leftInputs.setUIEntities(side === RIGHT_HAND ? uiEntityIDs : []); + rightInputs.setUIEntities(side === LEFT_HAND ? uiEntityIDs : []); isDisplaying = true; } function update() { if (isDisplaying) { - intersection = getIntersection(); - toolMenu.update(intersection.overlayID); + toolMenu.update(getIntersection().overlayID); } } @@ -220,12 +215,7 @@ } } - if (!this instanceof UI) { - return new UI(); - } - return { - setReferences: setReferences, setHand: setHand, display: display, update: update, @@ -238,7 +228,16 @@ Editor = function (side) { // An entity selection, entity highlights, and entity handles. - var otherEditor, // Other hand's Editor object. + var + // Primary objects. + selection, + highlights, + handles, + + // References. + otherEditor, // Other hand's Editor object. + hand, + laser, // Editor states. EDITOR_IDLE = 0, @@ -258,15 +257,6 @@ isOtherEditorEditingEntityID = false, hoveredOverlayID = null, - // Primary objects. - selection, - highlights, - handles, - - // Input objects. - hand, - laser, - // Position values. initialHandOrientationInverse, initialHandToSelectionVector, @@ -293,6 +283,11 @@ getIntersection, // Function. intersection; + + if (!this instanceof Editor) { + return new Editor(); + } + selection = new Selection(side); highlights = new Highlights(side); handles = new Handles(side); @@ -300,12 +295,13 @@ function setReferences(inputs, editor) { hand = inputs.hand(); // Object. laser = inputs.laser(); // Object. - getIntersection = inputs.getIntersection; // Function. + getIntersection = inputs.intersection; // Function. otherEditor = editor; // Object. laserOffset = laser.handOffset(); // Value. } + function hoverHandle(overlayID) { // Highlights handle if overlayID is a handle, otherwise unhighlights currently highlighted handle if any. handles.hover(overlayID); @@ -911,10 +907,6 @@ } } - if (!this instanceof Editor) { - return new Editor(); - } - return { setReferences: setReferences, hoverHandle: hoverHandle, @@ -1029,8 +1021,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(otherHand(dominantHand), setAppScaleWithHandles); - ui.setReferences(inputs[LEFT_HAND], inputs[RIGHT_HAND]); + ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], setAppScaleWithHandles); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From 3b111b85ef3a58022d9d572a168467a398f41167 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 27 Jul 2017 15:56:42 +1200 Subject: [PATCH 096/504] Move UI to hand --- scripts/vr-edit/modules/toolMenu.js | 33 ++++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 26adbf5a77..c0fb6a17a8 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -29,30 +29,30 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - FOREARM_JOINT_NAME = side === LEFT_HAND ? "LeftForeArm" : "RightForeArm", HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", - ROOT_X_OFFSET = side === LEFT_HAND ? -0.05 : 0.05, - ROOT_Z_OFFSET = side === LEFT_HAND ? -0.05 : 0.05, - ROOT_ROTATION = side === LEFT_HAND - ? Quat.fromVec3Degrees({ x: 180, y: -90, z: 0 }) - : Quat.fromVec3Degrees({ x: 180, y: 90, z: 0 }), + CANVAS_SIZE = { x: 0.21, y: 0.13 }, + + LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, + ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.03 }, + ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, + localPosition: ROOT_POSITION, + localRotation: ROOT_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, parentID: AVATAR_SELF_ID, - localRotation: ROOT_ROTATION, ignoreRayIntersection: true, visible: false }, PANEL_PROPERTIES = { - dimensions: { x: 0.1, y: 0.2, z: 0.01 }, - localPosition: { x: 0.05, y: 0.1, z: 0.005 }, + dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, + localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0.005 }, localRotation: ZERO_ROTATION, color: { red: 192, green: 192, blue: 192 }, alpha: 1.0, @@ -134,19 +134,15 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba function display() { // Creates and shows menu entities. - var forearmJointIndex, - handJointIndex, - forearmLength, - rootOffset; + var handJointIndex; if (isDisplaying) { return; } - // Joint indexes. - forearmJointIndex = MyAvatar.getJointIndex(FOREARM_JOINT_NAME); + // Joint index. handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); - if (forearmJointIndex === -1 || handJointIndex === -1) { + if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. // TODO: Log error. @@ -154,10 +150,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } // Calculate position to put menu. - forearmLength = Vec3.distance(MyAvatar.getJointPosition(forearmJointIndex), MyAvatar.getJointPosition(handJointIndex)); - rootOffset = { x: ROOT_X_OFFSET, y: forearmLength, z: ROOT_Z_OFFSET }; - ORIGIN_PROPERTIES.parentJointIndex = forearmJointIndex; - ORIGIN_PROPERTIES.localPosition = rootOffset; + ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; originOverlay = Overlays.addOverlay("sphere", ORIGIN_PROPERTIES); // Create menu items. From e8c5e1c2d5c517c11cd004a695917007076b730c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 27 Jul 2017 16:05:32 +1200 Subject: [PATCH 097/504] Make the button move down when pressed --- scripts/vr-edit/modules/toolMenu.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c0fb6a17a8..69eccf3fe1 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -74,7 +74,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba BUTTON_HIGHLIGHT_PROPERTIES = { dimensions: { x: 0.034, y: 0.034, z: 0.001 }, - localPosition: { x: 0, y: 0, z: -0.002 }, + localPosition: { x: 0.02, y: 0.02, z: -0.002 }, localRotation: ZERO_ROTATION, color: { red: 240, green: 240, blue: 0 }, alpha: 0.8, @@ -125,9 +125,14 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba if (isButtonPressed) { scaleMode = scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_HANDLES : SCALE_MODE_DIRECT; Overlays.editOverlay(buttonOverlay, { - color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR + color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR, + localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.004 }) }); setAppScaleWithHandlesCallback(scaleMode === SCALE_MODE_HANDLES); + } else { + Overlays.editOverlay(buttonOverlay, { + localPosition: BUTTON_PROPERTIES.localPosition + }); } } } @@ -160,7 +165,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba buttonOverlay = Overlays.addOverlay("cube", BUTTON_PROPERTIES); // Prepare highlight overlay. - BUTTON_HIGHLIGHT_PROPERTIES.parentID = buttonOverlay; + BUTTON_HIGHLIGHT_PROPERTIES.parentID = originOverlay; buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); isDisplaying = true; From 86f33727ebfe0dc989f6d393e46b6d8662178c5c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 27 Jul 2017 16:53:15 +1200 Subject: [PATCH 098/504] Add prototype Create palette --- scripts/vr-edit/modules/toolMenu.js | 143 ++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 18 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 69eccf3fe1..6c13ccbbac 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -21,28 +21,36 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba SCALE_MODE_DIRECT_COLOR = { red: 240, green: 240, blue: 0 }, SCALE_MODE_HANDLES_COLOR = { red: 0, green: 240, blue: 240 }, - originOverlay, - panelOverlay, + menuOriginOverlay, + menuPanelOverlay, buttonOverlay, buttonHighlightOverlay, + paletteOriginOverlay, + palettePanelOverlay, + cubeOverlay, + cubeHighlightOverlay, + LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", CANVAS_SIZE = { x: 0.21, y: 0.13 }, - LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, - ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.03 }, - ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), + + PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.03 }, + PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), + + PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: 0.09 }, + PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - ORIGIN_PROPERTIES = { + PANEL_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, - localPosition: ROOT_POSITION, - localRotation: ROOT_ROTATION, + localPosition: PANEL_ROOT_POSITION, + localRotation: PANEL_ROOT_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, parentID: AVATAR_SELF_ID, @@ -50,7 +58,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba visible: false }, - PANEL_PROPERTIES = { + MENU_PANEL_PROPERTIES = { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0.005 }, localRotation: ZERO_ROTATION, @@ -84,9 +92,63 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba visible: false }, + PALETTE_ORIGIN_PROPERTIES = { + dimensions: { x: 0.005, y: 0.005, z: 0.005 }, + localPosition: PALETTE_ROOT_POSITION, + localRotation: PALETTE_ROOT_ROTATION, + color: { red: 255, blue: 0, green: 0 }, + alpha: 1.0, + parentID: AVATAR_SELF_ID, + ignoreRayIntersection: true, + visible: false + }, + + PALETTE_PANEL_PROPERTIES = { + dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, + localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0 }, + localRotation: ZERO_ROTATION, + color: { red: 192, green: 192, blue: 192 }, + alpha: 0.3, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + CUBE_PROPERTIES = { + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: ZERO_ROTATION, + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + CUBE_HIGHLIGHT_PROPERTIES = { + dimensions: { x: 0.034, y: 0.034, z: 0.034 }, + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: ZERO_ROTATION, + color: { red: 240, green: 240, blue: 0 }, + alpha: 0.8, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }, + + CUBE_ENTITY_PROPERTIES = { + type: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + }, + isDisplaying = false, + isHighlightingButton = false, isButtonPressed = false, + isHighlightingCube = false, + isCubePressed = false, // References. controlHand; @@ -108,7 +170,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } function getEntityIDs() { - return [panelOverlay, buttonOverlay]; + return [menuPanelOverlay, buttonOverlay, palettePanelOverlay, cubeOverlay]; } function update(intersectionOverlayID) { @@ -135,6 +197,31 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba }); } } + + // Highlight cube. + if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { + isHighlightingCube = !isHighlightingCube; + Overlays.editOverlay(cubeHighlightOverlay, { visible: isHighlightingCube }); + } + + // Cube click. + if (isHighlightingCube && controlHand.triggerClicked() !== isCubePressed) { + isCubePressed = controlHand.triggerClicked(); + + if (isCubePressed) { + Overlays.editOverlay(cubeOverlay, { + localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.01 }) + }); + CUBE_ENTITY_PROPERTIES.position = + Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.2, z: -1.0 })); + CUBE_ENTITY_PROPERTIES.rotation = MyAvatar.orientation; + Entities.addEntity(CUBE_ENTITY_PROPERTIES); + } else { + Overlays.editOverlay(cubeOverlay, { + localPosition: BUTTON_PROPERTIES.localPosition + }); + } + } } function display() { @@ -155,19 +242,33 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } // Calculate position to put menu. - ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; - originOverlay = Overlays.addOverlay("sphere", ORIGIN_PROPERTIES); + PANEL_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; + menuOriginOverlay = Overlays.addOverlay("sphere", PANEL_ORIGIN_PROPERTIES); // Create menu items. - PANEL_PROPERTIES.parentID = originOverlay; - panelOverlay = Overlays.addOverlay("cube", PANEL_PROPERTIES); - BUTTON_PROPERTIES.parentID = originOverlay; + MENU_PANEL_PROPERTIES.parentID = menuOriginOverlay; + menuPanelOverlay = Overlays.addOverlay("cube", MENU_PANEL_PROPERTIES); + BUTTON_PROPERTIES.parentID = menuOriginOverlay; buttonOverlay = Overlays.addOverlay("cube", BUTTON_PROPERTIES); - // Prepare highlight overlay. - BUTTON_HIGHLIGHT_PROPERTIES.parentID = originOverlay; + // Prepare button highlight overlay. + BUTTON_HIGHLIGHT_PROPERTIES.parentID = menuOriginOverlay; buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); + // Calculate position to put palette. + PALETTE_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; + paletteOriginOverlay = Overlays.addOverlay("sphere", PALETTE_ORIGIN_PROPERTIES); + + // Create palette items. + PALETTE_PANEL_PROPERTIES.parentID = paletteOriginOverlay; + palettePanelOverlay = Overlays.addOverlay("cube", PALETTE_PANEL_PROPERTIES); + CUBE_PROPERTIES.parentID = paletteOriginOverlay; + cubeOverlay = Overlays.addOverlay("cube", CUBE_PROPERTIES); + + // Prepare cube highlight overlay. + CUBE_HIGHLIGHT_PROPERTIES.parentID = paletteOriginOverlay; + cubeHighlightOverlay = Overlays.addOverlay("cube", CUBE_HIGHLIGHT_PROPERTIES); + isDisplaying = true; } @@ -177,9 +278,15 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba return; } + Overlays.deleteOverlay(cubeHighlightOverlay); + Overlays.deleteOverlay(cubeOverlay); + Overlays.deleteOverlay(palettePanelOverlay); + Overlays.deleteOverlay(paletteOriginOverlay); + Overlays.deleteOverlay(buttonHighlightOverlay); Overlays.deleteOverlay(buttonOverlay); - Overlays.deleteOverlay(panelOverlay); + Overlays.deleteOverlay(menuPanelOverlay); + Overlays.deleteOverlay(menuOriginOverlay); isDisplaying = false; } From 01f37b53e955472a0e94b3a5ce0d090ed59679fb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 28 Jul 2017 09:20:21 +1200 Subject: [PATCH 099/504] Fix non-dominant hand ciursor dot showing when it shouldn't --- scripts/vr-edit/modules/laser.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 5ef0a223cf..fefa13b8ff 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -182,7 +182,7 @@ Laser = function (side) { isLaserOn = true; display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); - } else { + } else if (uiEntityIDs.length > 0) { // Special UI cursor. intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, @@ -198,18 +198,20 @@ Laser = function (side) { } isLaserOn = true; display(pickRay.origin, pickRay.direction, laserLength, false, false); - } else { - if (isLaserOn) { - isLaserOn = false; - hide(); - } + } else if (isLaserOn) { + isLaserOn = false; + hide(); } + } else { + intersection = { intersects: false }; + if (isLaserOn) { + isLaserOn = false; + hide(); + } } } else { - intersection = { - intersects: false - }; + intersection = { intersects: false }; if (isLaserOn) { isLaserOn = false; hide(); From 21c15120ba340870a25b5745693fa5bec67b8e4c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 28 Jul 2017 09:24:15 +1200 Subject: [PATCH 100/504] Raise Tool menu up a little to accommodate wooden mannequin's hand --- scripts/vr-edit/modules/toolMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6c13ccbbac..5f02eba58a 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -39,7 +39,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba CANVAS_SIZE = { x: 0.21, y: 0.13 }, LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, - PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.03 }, + PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.04 }, PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: 0.09 }, From 9fe3a823ee3b20774d2ce3190cb5870ce93f2fd0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 28 Jul 2017 09:35:29 +1200 Subject: [PATCH 101/504] Fix unexpectedly deleting multiple entities --- scripts/vr-edit/vr-edit.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index bd8af9dabd..a10f99ab77 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -255,6 +255,7 @@ highlightedEntityID = null, // Root entity of highlighted entity set. wasAppScaleWithHandles = false, isOtherEditorEditingEntityID = false, + wasGripPressed = false, hoveredOverlayID = null, // Position values. @@ -590,6 +591,7 @@ } startEditing(); wasAppScaleWithHandles = isAppScaleWithHandles; + wasGripPressed = hand.gripPressed(); } function updateEditorGrabbing() { @@ -796,6 +798,7 @@ updateState(); wasAppScaleWithHandles = isAppScaleWithHandles; } + wasGripPressed = false; break; } if (!hand.valid()) { @@ -808,8 +811,10 @@ setState(EDITOR_SEARCHING); } } else if (hand.gripPressed()) { - selection.deleteEntities(); - setState(EDITOR_SEARCHING); + if (!wasGripPressed) { + selection.deleteEntities(); + setState(EDITOR_SEARCHING); + } } else { debug(side, "ERROR: Unexpected condition in EDITOR_GRABBING!"); } From 64f5fe4009a9c04bfd0c0f4584539e8944ab5f22 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 28 Jul 2017 10:36:54 +1200 Subject: [PATCH 102/504] Make Create palette a separate object --- scripts/vr-edit/modules/createPalette.js | 204 +++++++++++++++++++++++ scripts/vr-edit/modules/toolMenu.js | 107 +----------- scripts/vr-edit/vr-edit.js | 14 +- 3 files changed, 218 insertions(+), 107 deletions(-) create mode 100644 scripts/vr-edit/modules/createPalette.js diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js new file mode 100644 index 0000000000..d9a19853c9 --- /dev/null +++ b/scripts/vr-edit/modules/createPalette.js @@ -0,0 +1,204 @@ +// +// createPalette.js +// +// Created by David Rowe on 28 Jul 2017. +// Copyright 2017 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 +// + +/* global CreatePalette */ + +CreatePalette = function (side, leftInputs, rightInputs) { + // Tool menu displayed on top of forearm. + + "use strict"; + + var paletteOriginOverlay, + palettePanelOverlay, + cubeOverlay, + cubeHighlightOverlay, + + LEFT_HAND = 0, + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + + HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + + CANVAS_SIZE = { x: 0.21, y: 0.13 }, + LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, + + PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: 0.09 }, + PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + + PALETTE_ORIGIN_PROPERTIES = { + dimensions: { x: 0.005, y: 0.005, z: 0.005 }, + localPosition: PALETTE_ROOT_POSITION, + localRotation: PALETTE_ROOT_ROTATION, + color: { red: 255, blue: 0, green: 0 }, + alpha: 1.0, + parentID: AVATAR_SELF_ID, + ignoreRayIntersection: true, + visible: false + }, + + PALETTE_PANEL_PROPERTIES = { + dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, + localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0 }, + localRotation: ZERO_ROTATION, + color: { red: 192, green: 192, blue: 192 }, + alpha: 0.3, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + CUBE_PROPERTIES = { + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: ZERO_ROTATION, + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + CUBE_HIGHLIGHT_PROPERTIES = { + dimensions: { x: 0.034, y: 0.034, z: 0.034 }, + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: ZERO_ROTATION, + color: { red: 240, green: 240, blue: 0 }, + alpha: 0.8, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }, + + CUBE_ENTITY_PROPERTIES = { + type: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + }, + + isDisplaying = false, + + isHighlightingCube = false, + isCubePressed = false, + + // References. + controlHand; + + + if (!this instanceof CreatePalette) { + return new CreatePalette(); + } + + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + + function setHand(uiSide) { + side = uiSide; + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + + if (isDisplaying) { + // TODO: Move UI to other hand. + } + } + + function getEntityIDs() { + return [palettePanelOverlay, cubeOverlay]; + } + + function update(intersectionOverlayID) { + // Highlight cube. + if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { + isHighlightingCube = !isHighlightingCube; + Overlays.editOverlay(cubeHighlightOverlay, { visible: isHighlightingCube }); + } + + // Cube click. + if (isHighlightingCube && controlHand.triggerClicked() !== isCubePressed) { + isCubePressed = controlHand.triggerClicked(); + + if (isCubePressed) { + Overlays.editOverlay(cubeOverlay, { + localPosition: Vec3.sum(CUBE_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.01 }) + }); + CUBE_ENTITY_PROPERTIES.position = + Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.2, z: -1.0 })); + CUBE_ENTITY_PROPERTIES.rotation = MyAvatar.orientation; + Entities.addEntity(CUBE_ENTITY_PROPERTIES); + } else { + Overlays.editOverlay(cubeOverlay, { + localPosition: CUBE_PROPERTIES.localPosition + }); + } + } + } + + function display() { + // Creates and shows menu entities. + var handJointIndex; + + if (isDisplaying) { + return; + } + + // Joint index. + handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + if (handJointIndex === -1) { + // Don't display if joint isn't available (yet) to attach to. + // User can clear this condition by toggling the app off and back on once avatar finishes loading. + // TODO: Log error. + return; + } + + // Calculate position to put palette. + PALETTE_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; + paletteOriginOverlay = Overlays.addOverlay("sphere", PALETTE_ORIGIN_PROPERTIES); + + // Create palette items. + PALETTE_PANEL_PROPERTIES.parentID = paletteOriginOverlay; + palettePanelOverlay = Overlays.addOverlay("cube", PALETTE_PANEL_PROPERTIES); + CUBE_PROPERTIES.parentID = paletteOriginOverlay; + cubeOverlay = Overlays.addOverlay("cube", CUBE_PROPERTIES); + + // Prepare cube highlight overlay. + CUBE_HIGHLIGHT_PROPERTIES.parentID = paletteOriginOverlay; + cubeHighlightOverlay = Overlays.addOverlay("cube", CUBE_HIGHLIGHT_PROPERTIES); + + isDisplaying = true; + } + + function clear() { + // Deletes menu entities. + if (!isDisplaying) { + return; + } + + Overlays.deleteOverlay(cubeHighlightOverlay); + Overlays.deleteOverlay(cubeOverlay); + Overlays.deleteOverlay(palettePanelOverlay); + Overlays.deleteOverlay(paletteOriginOverlay); + + isDisplaying = false; + } + + function destroy() { + clear(); + } + + return { + setHand: setHand, + entityIDs: getEntityIDs, + update: update, + display: display, + clear: clear, + destroy: destroy + }; +}; + +CreatePalette.prototype = {}; diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 5f02eba58a..0034155d1a 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -26,11 +26,6 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba buttonOverlay, buttonHighlightOverlay, - paletteOriginOverlay, - palettePanelOverlay, - cubeOverlay, - cubeHighlightOverlay, - LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", @@ -42,9 +37,6 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.04 }, PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), - PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: 0.09 }, - PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), PANEL_ORIGIN_PROPERTIES = { @@ -92,63 +84,10 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba visible: false }, - PALETTE_ORIGIN_PROPERTIES = { - dimensions: { x: 0.005, y: 0.005, z: 0.005 }, - localPosition: PALETTE_ROOT_POSITION, - localRotation: PALETTE_ROOT_ROTATION, - color: { red: 255, blue: 0, green: 0 }, - alpha: 1.0, - parentID: AVATAR_SELF_ID, - ignoreRayIntersection: true, - visible: false - }, - - PALETTE_PANEL_PROPERTIES = { - dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, - localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0 }, - localRotation: ZERO_ROTATION, - color: { red: 192, green: 192, blue: 192 }, - alpha: 0.3, - solid: true, - ignoreRayIntersection: false, - visible: true - }, - - CUBE_PROPERTIES = { - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: ZERO_ROTATION, - color: { red: 240, green: 0, blue: 0 }, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true - }, - - CUBE_HIGHLIGHT_PROPERTIES = { - dimensions: { x: 0.034, y: 0.034, z: 0.034 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: ZERO_ROTATION, - color: { red: 240, green: 240, blue: 0 }, - alpha: 0.8, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }, - - CUBE_ENTITY_PROPERTIES = { - type: "Box", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } - }, - isDisplaying = false, isHighlightingButton = false, isButtonPressed = false, - isHighlightingCube = false, - isCubePressed = false, // References. controlHand; @@ -170,7 +109,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } function getEntityIDs() { - return [menuPanelOverlay, buttonOverlay, palettePanelOverlay, cubeOverlay]; + return [menuPanelOverlay, buttonOverlay]; } function update(intersectionOverlayID) { @@ -197,31 +136,6 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba }); } } - - // Highlight cube. - if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { - isHighlightingCube = !isHighlightingCube; - Overlays.editOverlay(cubeHighlightOverlay, { visible: isHighlightingCube }); - } - - // Cube click. - if (isHighlightingCube && controlHand.triggerClicked() !== isCubePressed) { - isCubePressed = controlHand.triggerClicked(); - - if (isCubePressed) { - Overlays.editOverlay(cubeOverlay, { - localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.01 }) - }); - CUBE_ENTITY_PROPERTIES.position = - Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.2, z: -1.0 })); - CUBE_ENTITY_PROPERTIES.rotation = MyAvatar.orientation; - Entities.addEntity(CUBE_ENTITY_PROPERTIES); - } else { - Overlays.editOverlay(cubeOverlay, { - localPosition: BUTTON_PROPERTIES.localPosition - }); - } - } } function display() { @@ -255,20 +169,6 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba BUTTON_HIGHLIGHT_PROPERTIES.parentID = menuOriginOverlay; buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); - // Calculate position to put palette. - PALETTE_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; - paletteOriginOverlay = Overlays.addOverlay("sphere", PALETTE_ORIGIN_PROPERTIES); - - // Create palette items. - PALETTE_PANEL_PROPERTIES.parentID = paletteOriginOverlay; - palettePanelOverlay = Overlays.addOverlay("cube", PALETTE_PANEL_PROPERTIES); - CUBE_PROPERTIES.parentID = paletteOriginOverlay; - cubeOverlay = Overlays.addOverlay("cube", CUBE_PROPERTIES); - - // Prepare cube highlight overlay. - CUBE_HIGHLIGHT_PROPERTIES.parentID = paletteOriginOverlay; - cubeHighlightOverlay = Overlays.addOverlay("cube", CUBE_HIGHLIGHT_PROPERTIES); - isDisplaying = true; } @@ -278,11 +178,6 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba return; } - Overlays.deleteOverlay(cubeHighlightOverlay); - Overlays.deleteOverlay(cubeOverlay); - Overlays.deleteOverlay(palettePanelOverlay); - Overlays.deleteOverlay(paletteOriginOverlay); - Overlays.deleteOverlay(buttonHighlightOverlay); Overlays.deleteOverlay(buttonOverlay); Overlays.deleteOverlay(menuPanelOverlay); diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a10f99ab77..e73f385f2e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -39,6 +39,7 @@ Laser, Selection, ToolMenu, + CreatePalette, // Miscellaneous UPDATE_LOOP_TIMEOUT = 16, @@ -52,6 +53,7 @@ Script.include("./utilities/utilities.js"); // Modules + Script.include("./modules/createPalette.js"); Script.include("./modules/hand.js"); Script.include("./modules/handles.js"); Script.include("./modules/highlights.js"); @@ -162,6 +164,7 @@ var // Primary objects. toolMenu, + createPalette, isDisplaying = false, @@ -173,12 +176,14 @@ } toolMenu = new ToolMenu(side, leftInputs, rightInputs, setAppScaleWithHandlesCallback); + createPalette = new CreatePalette(side, leftInputs, rightInputs); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; function setHand(side) { toolMenu.setHand(side); + createPalette.setHand(side); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; } @@ -186,8 +191,9 @@ var uiEntityIDs; toolMenu.display(); + createPalette.display(); - uiEntityIDs = toolMenu.entityIDs(); + uiEntityIDs = [].concat(toolMenu.entityIDs(), createPalette.entityIDs()); leftInputs.setUIEntities(side === RIGHT_HAND ? uiEntityIDs : []); rightInputs.setUIEntities(side === LEFT_HAND ? uiEntityIDs : []); @@ -197,6 +203,7 @@ function update() { if (isDisplaying) { toolMenu.update(getIntersection().overlayID); + createPalette.update(getIntersection().overlayID); } } @@ -204,11 +211,16 @@ leftInputs.setUIEntities([]); rightInputs.setUIEntities([]); toolMenu.clear(); + createPalette.clear(); isDisplaying = false; } function destroy() { + if (createPalette) { + createPalette.destroy(); + createPalette = null; + } if (toolMenu) { toolMenu.destroy(); toolMenu = null; From c201e7b65af3ab1252360f40a124fdee16bb83ec Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 10:59:15 +1200 Subject: [PATCH 103/504] Display placeholder for tool icon on dominant hand --- scripts/vr-edit/modules/toolIcon.js | 100 +++++++++++++++++++++++++ scripts/vr-edit/utilities/utilities.js | 6 ++ scripts/vr-edit/vr-edit.js | 35 +++++++-- 3 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 scripts/vr-edit/modules/toolIcon.js diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js new file mode 100644 index 0000000000..813178c5f8 --- /dev/null +++ b/scripts/vr-edit/modules/toolIcon.js @@ -0,0 +1,100 @@ +// +// toolIcon.js +// +// Created by David Rowe on 28 Jul 2017. +// Copyright 2017 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 +// + +/* global ToolIcon */ + +ToolIcon = function (side) { + // Tool icon displayed on non-dominant hand. + + "use strict"; + + var NONE = 0, + SCALE_HANDLES = 1, + + ICON_COLORS = [ + { red: 0, green: 0, blue: 0 }, // Unused entry for NONE. + { red: 0, green: 240, blue: 240 } + ], + + LEFT_HAND = 0, + AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + + ICON_DIMENSIONS = { x: 0.1, y: 0.01, z: 0.1 }, + ICON_POSITION = { x: 0, y: 0.01, z: 0 }, + ICON_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + + ICON_TYPE = "sphere", + ICON_PROPERTIES = { + dimensions: ICON_DIMENSIONS, + localPosition: ICON_POSITION, + localRotation: ICON_ROTATION, + solid: true, + alpha: 1.0, + parentID: AVATAR_SELF_ID, + ignoreRayIntersection: false, + visible: true + }, + + HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + + iconOverlay = null; + + if (!this instanceof ToolIcon) { + return new ToolIcon(); + } + + function update() { + // TODO: Display icon animation. + // TODO: Clear icon animation. + } + + function display(icon) { + // Displays icon on hand. + var handJointIndex, + iconProperties; + + // Joint index. + handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + if (handJointIndex === -1) { + // Don't display if joint isn't available (yet) to attach to. + // User can clear this condition by toggling the app off and back on once avatar finishes loading. + // TODO: Log error. + return; + } + + iconProperties = Object.clone(ICON_PROPERTIES); + iconProperties.parentJointIndex = handJointIndex; + iconProperties.color = ICON_COLORS[icon]; + iconOverlay = Overlays.addOverlay(ICON_TYPE, iconProperties); + } + + function clear() { + // Deletes current icon. + if (iconOverlay) { + Overlays.deleteOverlay(iconOverlay); + iconOverlay = null; + } + } + + function destroy() { + clear(); + } + + return { + NONE: NONE, + SCALE_HANDLES: SCALE_HANDLES, + update: update, + display: display, + clear: clear, + destroy: destroy + }; +}; + +ToolIcon.prototype = {}; diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index a6914b332c..d737d820ea 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -49,3 +49,9 @@ if (typeof Entities.hasEditableRoot !== "function") { return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; }; } + +if (typeof Object.clone !== "function") { + Object.clone = function (object) { + return JSON.parse(JSON.stringify(object)); + }; +} diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e73f385f2e..12bbe91301 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -33,13 +33,14 @@ RIGHT_HAND = 1, // Modules + CreatePalette, Hand, Handles, Highlights, Laser, Selection, + ToolIcon, ToolMenu, - CreatePalette, // Miscellaneous UPDATE_LOOP_TIMEOUT = 16, @@ -59,6 +60,7 @@ Script.include("./modules/highlights.js"); Script.include("./modules/laser.js"); Script.include("./modules/selection.js"); + Script.include("./modules/toolIcon.js"); Script.include("./modules/toolMenu.js"); @@ -80,6 +82,10 @@ } } + function otherHand(hand) { + return (hand + 1) % 2; + } + Inputs = function (side) { // A hand plus a laser. @@ -164,6 +170,7 @@ var // Primary objects. toolMenu, + toolIcon, createPalette, isDisplaying = false, @@ -175,6 +182,7 @@ return new UI(); } + toolIcon = new ToolIcon(otherHand(side)); toolMenu = new ToolMenu(side, leftInputs, rightInputs, setAppScaleWithHandlesCallback); createPalette = new CreatePalette(side, leftInputs, rightInputs); @@ -187,6 +195,14 @@ getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; } + function setToolIcon(icon) { + toolIcon.display(icon); + } + + function clearToolIcon() { + toolIcon.clear(); + } + function display() { var uiEntityIDs; @@ -204,6 +220,7 @@ if (isDisplaying) { toolMenu.update(getIntersection().overlayID); createPalette.update(getIntersection().overlayID); + toolIcon.update(); } } @@ -225,10 +242,17 @@ toolMenu.destroy(); toolMenu = null; } + if (toolIcon) { + toolIcon.destroy(); + toolIcon = null; + } } return { setHand: setHand, + setToolIcon: setToolIcon, + clearToolIcon: clearToolIcon, + SCALE_HANDLES: toolIcon.SCALE_HANDLES, display: display, update: update, clear: clear, @@ -972,6 +996,11 @@ function setAppScaleWithHandles(appScaleWithHandles) { isAppScaleWithHandles = appScaleWithHandles; + if (isAppScaleWithHandles) { + ui.setToolIcon(ui.SCALE_HANDLES); + } else { + ui.clearToolIcon(); + } } function onAppButtonClicked() { @@ -994,10 +1023,6 @@ } } - function otherHand(hand) { - return (hand + 1) % 2; - } - function onDominantHandChanged() { /* // TODO: API coming. From f4b2e399efdbdd83f36ef7e808c932a7848c09bc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 12:54:21 +1200 Subject: [PATCH 104/504] Fix multiple and malingering tool icons --- scripts/vr-edit/modules/toolIcon.js | 13 ++++++++----- scripts/vr-edit/vr-edit.js | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 813178c5f8..781530207c 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -60,7 +60,6 @@ ToolIcon = function (side) { var handJointIndex, iconProperties; - // Joint index. handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. @@ -69,10 +68,14 @@ ToolIcon = function (side) { return; } - iconProperties = Object.clone(ICON_PROPERTIES); - iconProperties.parentJointIndex = handJointIndex; - iconProperties.color = ICON_COLORS[icon]; - iconOverlay = Overlays.addOverlay(ICON_TYPE, iconProperties); + if (iconOverlay === null) { + iconProperties = Object.clone(ICON_PROPERTIES); + iconProperties.parentJointIndex = handJointIndex; + iconProperties.color = ICON_COLORS[icon]; + iconOverlay = Overlays.addOverlay(ICON_TYPE, iconProperties); + } else { + Overlays.editOverlay(iconOverlay, { color: ICON_COLORS[icon] }); + } } function clear() { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 12bbe91301..a2c76a5ad2 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -227,6 +227,7 @@ function clear() { leftInputs.setUIEntities([]); rightInputs.setUIEntities([]); + toolIcon.clear(); toolMenu.clear(); createPalette.clear(); From 9f90960a125aa415163054f215201d6b14d51c4f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 13:31:08 +1200 Subject: [PATCH 105/504] Make grip press discard tool --- scripts/vr-edit/modules/toolMenu.js | 14 +++----------- scripts/vr-edit/vr-edit.js | 24 +++++++++++++++++------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 0034155d1a..f4cb8fe818 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -15,13 +15,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba "use strict"; - var SCALE_MODE_DIRECT = 0, - SCALE_MODE_HANDLES = 1, - scaleMode = SCALE_MODE_DIRECT, - SCALE_MODE_DIRECT_COLOR = { red: 240, green: 240, blue: 0 }, - SCALE_MODE_HANDLES_COLOR = { red: 0, green: 240, blue: 240 }, - - menuOriginOverlay, + var menuOriginOverlay, menuPanelOverlay, buttonOverlay, buttonHighlightOverlay, @@ -65,7 +59,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba dimensions: { x: 0.03, y: 0.03, z: 0.01 }, localPosition: { x: 0.02, y: 0.02, z: 0.0 }, localRotation: ZERO_ROTATION, - color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR, + color: { red: 0, green: 240, blue: 240 }, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -124,12 +118,10 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba isButtonPressed = controlHand.triggerClicked(); if (isButtonPressed) { - scaleMode = scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_HANDLES : SCALE_MODE_DIRECT; Overlays.editOverlay(buttonOverlay, { - color: scaleMode === SCALE_MODE_DIRECT ? SCALE_MODE_DIRECT_COLOR : SCALE_MODE_HANDLES_COLOR, localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.004 }) }); - setAppScaleWithHandlesCallback(scaleMode === SCALE_MODE_HANDLES); + setAppScaleWithHandlesCallback(); } else { Overlays.editOverlay(buttonOverlay, { localPosition: BUTTON_PROPERTIES.localPosition diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a2c76a5ad2..888cab02af 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -577,6 +577,7 @@ selection.clear(); hoveredOverlayID = intersection.overlayID; otherEditor.hoverHandle(hoveredOverlayID); + wasGripPressed = hand.gripPressed(); } function updateEditorSearching() { @@ -598,6 +599,7 @@ } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); wasAppScaleWithHandles = isAppScaleWithHandles; + wasGripPressed = hand.gripPressed(); } function updateEditorHighlighting() { @@ -737,6 +739,16 @@ } + function updateTool() { + var isGripPressed = hand.gripPressed(); + if (!wasGripPressed && isGripPressed && isAppScaleWithHandles) { + isAppScaleWithHandles = false; + ui.clearToolIcon(); + } + wasGripPressed = isGripPressed; + } + + function update() { var previousState = editorState, doUpdateState; @@ -757,6 +769,7 @@ && !(intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID))) { // No transition. updateState(); + updateTool(); break; } if (!hand.valid()) { @@ -802,6 +815,7 @@ if (doUpdateState) { updateState(); } + updateTool(); break; } if (!hand.valid()) { @@ -995,13 +1009,9 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } - function setAppScaleWithHandles(appScaleWithHandles) { - isAppScaleWithHandles = appScaleWithHandles; - if (isAppScaleWithHandles) { - ui.setToolIcon(ui.SCALE_HANDLES); - } else { - ui.clearToolIcon(); - } + function setAppScaleWithHandles() { + isAppScaleWithHandles = true; + ui.setToolIcon(ui.SCALE_HANDLES); } function onAppButtonClicked() { From 52a2538d73d5fa0bb8119708c7e4d393fd059150 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 14:41:58 +1200 Subject: [PATCH 106/504] Fix scale handles not displaying after delete entity --- scripts/vr-edit/modules/handles.js | 97 +++++++++++++----------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index e124e5f945..b9390b02d3 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -98,38 +98,6 @@ Handles = function (side) { Vec3.UNIT_Z ]; - boundingBoxOverlay = Overlays.addOverlay("cube", { - color: BOUNDING_BOX_COLOR, - alpha: BOUNDING_BOX_ALPHA, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }); - - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { - color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_NORMAL_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: false, - visible: false - }); - } - - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - faceHandleOverlays[i] = Overlays.addOverlay("shape", { - shape: "Cone", - color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_NORMAL_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: false, - visible: false - }); - } - function isAxisHandle(overlayID) { return faceHandleOverlays.indexOf(overlayID) !== -1; } @@ -158,7 +126,7 @@ Handles = function (side) { return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; } - function display(rootEntityID, boundingBox, isMultiple) { + function display(rootEntityID, boundingBox, isMultipleEntities) { var boundingBoxCenter, boundingBoxOrientation, cameraPosition, @@ -183,11 +151,16 @@ Handles = function (side) { boundingBoxOrientation = boundingBox.orientation; // Selection bounding box. - Overlays.editOverlay(boundingBoxOverlay, { + boundingBoxOverlay = Overlays.addOverlay("cube", { parentID: rootEntityID, localPosition: boundingBoxLocalCenter, localRotation: ZERO_ROTATION, dimensions: boundingBoxDimensions, + color: BOUNDING_BOX_COLOR, + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, visible: true }); @@ -219,11 +192,17 @@ Handles = function (side) { cornerIndexes[1] = rightCornerIndex; cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - Overlays.editOverlay(cornerHandleOverlays[i], { + cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { parentID: rootEntityID, localPosition: Vec3.sum(boundingBoxLocalCenter, Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), + localRotation: ZERO_ROTATION, dimensions: cornerHandleDimensions, + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, visible: true }); } @@ -231,23 +210,27 @@ Handles = function (side) { // Face scale handles. // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. - if (!isMultiple) { + if (!isMultipleEntities) { faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { + faceHandleOverlays[i] = Overlays.addOverlay("shape", { parentID: rootEntityID, localPosition: Vec3.sum(boundingBoxLocalCenter, Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], dimensions: faceHandleDimensions, + shape: "Cone", + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, visible: true }); } } else { - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); - } + faceHandleOverlays = []; } } @@ -275,12 +258,14 @@ Handles = function (side) { } // Face scale handles. - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { - localPosition: Vec3.sum(scalingBoundingBoxDimensions, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], - Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) - }); + if (faceHandleOverlays.length > 0) { + for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + Overlays.editOverlay(faceHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) + }); + } } } @@ -336,22 +321,22 @@ Handles = function (side) { } function clear() { - var i; + var i, + length; + + Overlays.deleteOverlay(boundingBoxOverlay); + for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + Overlays.deleteOverlay(cornerHandleOverlays[i]); + } + for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(faceHandleOverlays[i]); + } isVisible = false; - - Overlays.editOverlay(boundingBoxOverlay, { visible: false }); - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { - Overlays.editOverlay(cornerHandleOverlays[i], { visible: false }); - } - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - Overlays.editOverlay(faceHandleOverlays[i], { visible: false }); - } } function destroy() { clear(); - Overlays.deleteOverlay(boundingBoxOverlay); } return { From 30595c78a0ee3e02dc5e0dec8a614acd9ad1933d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 14:51:58 +1200 Subject: [PATCH 107/504] Fix button not unpressing when cursor moves off it --- scripts/vr-edit/modules/toolMenu.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index f4cb8fe818..d0c00cb5a5 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -114,9 +114,8 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } // Button click. - if (isHighlightingButton && controlHand.triggerClicked() !== isButtonPressed) { - isButtonPressed = controlHand.triggerClicked(); - + if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { + isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); if (isButtonPressed) { Overlays.editOverlay(buttonOverlay, { localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.004 }) From 1d500dd3aaebb1476d6b77333cd3bd10a8efed46 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 16:10:48 +1200 Subject: [PATCH 108/504] Use dominant hand setting and handle setting changes --- scripts/vr-edit/modules/createPalette.js | 51 +++++++++++++----------- scripts/vr-edit/modules/toolIcon.js | 12 +++++- scripts/vr-edit/modules/toolMenu.js | 49 +++++++++++++---------- scripts/vr-edit/vr-edit.js | 42 ++++++++++++------- 4 files changed, 92 insertions(+), 62 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index d9a19853c9..aea2d365b3 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -22,16 +22,14 @@ CreatePalette = function (side, leftInputs, rightInputs) { LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + controlJointName, CANVAS_SIZE = { x: 0.21, y: 0.13 }, - LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, - - PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: 0.09 }, + PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2, y: 0.15, z: 0.09 }, PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), - - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + lateralOffset, PALETTE_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, @@ -97,17 +95,16 @@ CreatePalette = function (side, leftInputs, rightInputs) { return new CreatePalette(); } - controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - - function setHand(uiSide) { - side = uiSide; + function setHand(hand) { + // Assumes UI is not displaying. + side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - - if (isDisplaying) { - // TODO: Move UI to other hand. - } + controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + lateralOffset = side === LEFT_HAND ? -0.01 : 0.01; } + setHand(side); + function getEntityIDs() { return [palettePanelOverlay, cubeOverlay]; } @@ -141,14 +138,15 @@ CreatePalette = function (side, leftInputs, rightInputs) { function display() { // Creates and shows menu entities. - var handJointIndex; + var handJointIndex, + properties; if (isDisplaying) { return; } // Joint index. - handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + handJointIndex = MyAvatar.getJointIndex(controlJointName); if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. @@ -157,18 +155,23 @@ CreatePalette = function (side, leftInputs, rightInputs) { } // Calculate position to put palette. - PALETTE_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; - paletteOriginOverlay = Overlays.addOverlay("sphere", PALETTE_ORIGIN_PROPERTIES); + properties = Object.clone(PALETTE_ORIGIN_PROPERTIES); + properties.parentJointIndex = handJointIndex; + properties.localPosition = Vec3.sum(PALETTE_ROOT_POSITION, { x: lateralOffset, y: 0, z: 0 }); + paletteOriginOverlay = Overlays.addOverlay("sphere", properties); // Create palette items. - PALETTE_PANEL_PROPERTIES.parentID = paletteOriginOverlay; - palettePanelOverlay = Overlays.addOverlay("cube", PALETTE_PANEL_PROPERTIES); - CUBE_PROPERTIES.parentID = paletteOriginOverlay; - cubeOverlay = Overlays.addOverlay("cube", CUBE_PROPERTIES); + properties = Object.clone(PALETTE_PANEL_PROPERTIES); + properties.parentID = paletteOriginOverlay; + palettePanelOverlay = Overlays.addOverlay("cube", properties); + properties = Object.clone(CUBE_PROPERTIES); + properties.parentID = paletteOriginOverlay; + cubeOverlay = Overlays.addOverlay("cube", properties); // Prepare cube highlight overlay. - CUBE_HIGHLIGHT_PROPERTIES.parentID = paletteOriginOverlay; - cubeHighlightOverlay = Overlays.addOverlay("cube", CUBE_HIGHLIGHT_PROPERTIES); + properties = Object.clone(CUBE_HIGHLIGHT_PROPERTIES); + properties.parentID = paletteOriginOverlay; + cubeHighlightOverlay = Overlays.addOverlay("cube", properties); isDisplaying = true; } diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 781530207c..8af0e3a884 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -42,7 +42,7 @@ ToolIcon = function (side) { visible: true }, - HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + handJointName, iconOverlay = null; @@ -50,6 +50,13 @@ ToolIcon = function (side) { return new ToolIcon(); } + function setHand(side) { + // Assumes UI is not displaying. + handJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + } + + setHand(side); + function update() { // TODO: Display icon animation. // TODO: Clear icon animation. @@ -60,7 +67,7 @@ ToolIcon = function (side) { var handJointIndex, iconProperties; - handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + handJointIndex = MyAvatar.getJointIndex(handJointName); if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. @@ -93,6 +100,7 @@ ToolIcon = function (side) { return { NONE: NONE, SCALE_HANDLES: SCALE_HANDLES, + setHand: setHand, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d0c00cb5a5..09eef7c38a 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -22,16 +22,14 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", + ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - HAND_JOINT_NAME = side === LEFT_HAND ? "LeftHand" : "RightHand", + controlJointName, CANVAS_SIZE = { x: 0.21, y: 0.13 }, - LATERAL_OFFSET = side === LEFT_HAND ? -0.01 : 0.01, - - PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2 + LATERAL_OFFSET, y: 0.15, z: -0.04 }, + PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2, y: 0.15, z: -0.04 }, PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), - - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), + lateralOffset, PANEL_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, @@ -93,15 +91,16 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - function setHand(uiSide) { - side = uiSide; + function setHand(hand) { + // Assumes UI is not displaying. + side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - - if (isDisplaying) { - // TODO: Move UI to other hand. - } + controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + lateralOffset = side === LEFT_HAND ? -0.01 : 0.01; } + setHand(side); + function getEntityIDs() { return [menuPanelOverlay, buttonOverlay]; } @@ -131,14 +130,15 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba function display() { // Creates and shows menu entities. - var handJointIndex; + var handJointIndex, + properties; if (isDisplaying) { return; } // Joint index. - handJointIndex = MyAvatar.getJointIndex(HAND_JOINT_NAME); + handJointIndex = MyAvatar.getJointIndex(controlJointName); if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. @@ -147,18 +147,23 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } // Calculate position to put menu. - PANEL_ORIGIN_PROPERTIES.parentJointIndex = handJointIndex; - menuOriginOverlay = Overlays.addOverlay("sphere", PANEL_ORIGIN_PROPERTIES); + properties = Object.clone(PANEL_ORIGIN_PROPERTIES); + properties.parentJointIndex = handJointIndex; + properties.localPosition = Vec3.sum(PANEL_ROOT_POSITION, { x: lateralOffset, y: 0, z: 0 }); + menuOriginOverlay = Overlays.addOverlay("sphere", properties); // Create menu items. - MENU_PANEL_PROPERTIES.parentID = menuOriginOverlay; - menuPanelOverlay = Overlays.addOverlay("cube", MENU_PANEL_PROPERTIES); - BUTTON_PROPERTIES.parentID = menuOriginOverlay; - buttonOverlay = Overlays.addOverlay("cube", BUTTON_PROPERTIES); + properties = Object.clone(MENU_PANEL_PROPERTIES); + properties.parentID = menuOriginOverlay; + menuPanelOverlay = Overlays.addOverlay("cube", properties); + properties = Object.clone(BUTTON_PROPERTIES); + properties.parentID = menuOriginOverlay; + buttonOverlay = Overlays.addOverlay("cube", properties); // Prepare button highlight overlay. - BUTTON_HIGHLIGHT_PROPERTIES.parentID = menuOriginOverlay; - buttonHighlightOverlay = Overlays.addOverlay("cube", BUTTON_HIGHLIGHT_PROPERTIES); + properties = Object.clone(BUTTON_HIGHLIGHT_PROPERTIES); + properties.parentID = menuOriginOverlay; + buttonHighlightOverlay = Overlays.addOverlay("cube", properties); isDisplaying = true; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 888cab02af..4c4ca78dd5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -190,6 +190,7 @@ function setHand(side) { + toolIcon.setHand(otherHand(side)); toolMenu.setHand(side); createPalette.setHand(side); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -1034,12 +1035,31 @@ } } - function onDominantHandChanged() { - /* - // TODO: API coming. - dominantHand = TODO; - */ + function onDominantHandChanged(hand) { + dominantHand = hand === "left" ? LEFT_HAND : RIGHT_HAND; + + if (isAppActive) { + // Stop operations. + Script.clearTimeout(updateTimer); + updateTimer = null; + inputs[LEFT_HAND].clear(); + inputs[RIGHT_HAND].clear(); + ui.clear(); + editors[LEFT_HAND].clear(); + editors[RIGHT_HAND].clear(); + } + + // Swap UI hands. ui.setHand(otherHand(dominantHand)); + if (isAppScaleWithHandles) { + ui.setToolIcon(ui.SCALE_HANDLES); + } + + if (isAppActive) { + // Resume operations. + ui.display(); + update(); + } } @@ -1052,11 +1072,7 @@ } // Settings values. - // TODO: API coming. - dominantHand = RIGHT_HAND; - /* - dominantHand = TODO; - */ + dominantHand = MyAvatar.getDominantHand() === "left" ? LEFT_HAND : RIGHT_HAND; // Tablet/toolbar button. button = tablet.addButton({ @@ -1083,11 +1099,9 @@ editors[RIGHT_HAND].setReferences(inputs[RIGHT_HAND], editors[LEFT_HAND]); // Settings changes. - /* - // TODO: API coming. - TODO.change.connect(onDominantHandChanged); - */ + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + // Start main update loop. if (isAppActive) { update(); } From 676deb8a5caadc458ff00523a2bf2e1f696b2d7d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 16:16:48 +1200 Subject: [PATCH 109/504] Make grip naming consistent with trigger naming --- scripts/vr-edit/modules/hand.js | 14 +++++++------- scripts/vr-edit/vr-edit.js | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 0430067eb9..0e2bf1c0b3 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -20,7 +20,7 @@ Hand = function (side) { controllerTriggerClicked, controllerGrip, - isGripPressed = false, + isGripClicked = false, GRIP_ON_VALUE = 0.99, GRIP_OFF_VALUE = 0.95, @@ -78,8 +78,8 @@ Hand = function (side) { return isTriggerClicked; } - function gripPressed() { - return isGripPressed; + function gripClicked() { + return isGripClicked; } function getIntersection() { @@ -117,10 +117,10 @@ Hand = function (side) { // Controller grip. gripValue = Controller.getValue(controllerGrip); - if (isGripPressed) { - isGripPressed = gripValue > GRIP_OFF_VALUE; + if (isGripClicked) { + isGripClicked = gripValue > GRIP_OFF_VALUE; } else { - isGripPressed = gripValue > GRIP_ON_VALUE; + isGripClicked = gripValue > GRIP_ON_VALUE; } // Hand-overlay intersection, if any. @@ -193,7 +193,7 @@ Hand = function (side) { orientation: orientation, triggerPressed: triggerPressed, triggerClicked: triggerClicked, - gripPressed: gripPressed, + gripClicked: gripClicked, intersection: getIntersection, update: update, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4c4ca78dd5..190110e261 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -293,7 +293,7 @@ highlightedEntityID = null, // Root entity of highlighted entity set. wasAppScaleWithHandles = false, isOtherEditorEditingEntityID = false, - wasGripPressed = false, + wasGripClicked = false, hoveredOverlayID = null, // Position values. @@ -578,7 +578,7 @@ selection.clear(); hoveredOverlayID = intersection.overlayID; otherEditor.hoverHandle(hoveredOverlayID); - wasGripPressed = hand.gripPressed(); + wasGripClicked = hand.gripClicked(); } function updateEditorSearching() { @@ -600,7 +600,7 @@ } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); wasAppScaleWithHandles = isAppScaleWithHandles; - wasGripPressed = hand.gripPressed(); + wasGripClicked = hand.gripClicked(); } function updateEditorHighlighting() { @@ -631,7 +631,7 @@ } startEditing(); wasAppScaleWithHandles = isAppScaleWithHandles; - wasGripPressed = hand.gripPressed(); + wasGripClicked = hand.gripClicked(); } function updateEditorGrabbing() { @@ -741,12 +741,12 @@ function updateTool() { - var isGripPressed = hand.gripPressed(); - if (!wasGripPressed && isGripPressed && isAppScaleWithHandles) { + var isGripClicked = hand.gripClicked(); + if (!wasGripClicked && isGripClicked && isAppScaleWithHandles) { isAppScaleWithHandles = false; ui.clearToolIcon(); } - wasGripPressed = isGripPressed; + wasGripClicked = isGripClicked; } @@ -843,14 +843,14 @@ } break; case EDITOR_GRABBING: - if (hand.valid() && hand.triggerClicked() && !hand.gripPressed()) { + if (hand.valid() && hand.triggerClicked() && !hand.gripClicked()) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (isAppScaleWithHandles !== wasAppScaleWithHandles) { updateState(); wasAppScaleWithHandles = isAppScaleWithHandles; } - wasGripPressed = false; + wasGripClicked = false; break; } if (!hand.valid()) { @@ -862,8 +862,8 @@ } else { setState(EDITOR_SEARCHING); } - } else if (hand.gripPressed()) { - if (!wasGripPressed) { + } else if (hand.gripClicked()) { + if (!wasGripClicked) { selection.deleteEntities(); setState(EDITOR_SEARCHING); } From 18a9dad9185c9355909b5b2fa0eac5ed12ab6798 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 17:15:33 +1200 Subject: [PATCH 110/504] Add button for clone tool --- scripts/vr-edit/modules/toolMenu.js | 63 ++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 09eef7c38a..ac8c04af18 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -17,7 +17,8 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba var menuOriginOverlay, menuPanelOverlay, - buttonOverlay, + scaleButtonOverlay, + cloneButtonOverlay, buttonHighlightOverlay, LEFT_HAND = 0, @@ -39,7 +40,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba alpha: 1.0, parentID: AVATAR_SELF_ID, ignoreRayIntersection: true, - visible: false + visible: true }, MENU_PANEL_PROPERTIES = { @@ -55,9 +56,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba BUTTON_PROPERTIES = { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, localRotation: ZERO_ROTATION, - color: { red: 0, green: 240, blue: 240 }, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -76,6 +75,18 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba visible: false }, + HIGHLIGHT_Z_OFFSET = -0.002, + + BUTTON_POSITIONS = [ + { x: 0.02, y: 0.02, z: 0.0 }, + { x: 0.06, y: 0.02, z: 0.0 } + ], + + BUTTON_COLORS = [ + { red: 0, green: 240, blue: 240 }, + { red: 240, green: 0, blue: 240 } + ], + isDisplaying = false, isHighlightingButton = false, @@ -102,29 +113,45 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba setHand(side); function getEntityIDs() { - return [menuPanelOverlay, buttonOverlay]; + return [menuPanelOverlay, scaleButtonOverlay, cloneButtonOverlay]; } function update(intersectionOverlayID) { // Highlight button. - if (intersectionOverlayID === buttonOverlay !== isHighlightingButton) { + if ((intersectionOverlayID === scaleButtonOverlay || intersectionOverlayID === cloneButtonOverlay) + !== isHighlightingButton) { isHighlightingButton = !isHighlightingButton; - Overlays.editOverlay(buttonHighlightOverlay, { visible: isHighlightingButton }); + Overlays.editOverlay(buttonHighlightOverlay, { + localPosition: Vec3.sum({ x: 0, y: 0, z: HIGHLIGHT_Z_OFFSET }, + intersectionOverlayID === scaleButtonOverlay ? BUTTON_POSITIONS[0] : BUTTON_POSITIONS[1]), + visible: isHighlightingButton + }); } // Button click. if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - if (isButtonPressed) { - Overlays.editOverlay(buttonOverlay, { - localPosition: Vec3.sum(BUTTON_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.004 }) + if (isButtonPressed && intersectionOverlayID === scaleButtonOverlay) { + Overlays.editOverlay(scaleButtonOverlay, { + localPosition: Vec3.sum(BUTTON_POSITIONS[0], { x: 0, y: 0, z: 0.004 }) }); - setAppScaleWithHandlesCallback(); } else { - Overlays.editOverlay(buttonOverlay, { - localPosition: BUTTON_PROPERTIES.localPosition + Overlays.editOverlay(scaleButtonOverlay, { + localPosition: BUTTON_POSITIONS[0] }); } + if (isButtonPressed && intersectionOverlayID === cloneButtonOverlay) { + Overlays.editOverlay(cloneButtonOverlay, { + localPosition: Vec3.sum(BUTTON_POSITIONS[1], { x: 0, y: 0, z: 0.004 }) + }); + } else { + Overlays.editOverlay(cloneButtonOverlay, { + localPosition: BUTTON_POSITIONS[1] + }); + } + if (isButtonPressed) { + setAppScaleWithHandlesCallback(); + } } } @@ -158,7 +185,12 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba menuPanelOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(BUTTON_PROPERTIES); properties.parentID = menuOriginOverlay; - buttonOverlay = Overlays.addOverlay("cube", properties); + properties.localPosition = BUTTON_POSITIONS[0]; + properties.color = BUTTON_COLORS[0]; + scaleButtonOverlay = Overlays.addOverlay("cube", properties); + properties.localPosition = BUTTON_POSITIONS[1]; + properties.color = BUTTON_COLORS[1]; + cloneButtonOverlay = Overlays.addOverlay("cube", properties); // Prepare button highlight overlay. properties = Object.clone(BUTTON_HIGHLIGHT_PROPERTIES); @@ -175,7 +207,8 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba } Overlays.deleteOverlay(buttonHighlightOverlay); - Overlays.deleteOverlay(buttonOverlay); + Overlays.deleteOverlay(cloneButtonOverlay); + Overlays.deleteOverlay(scaleButtonOverlay); Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); isDisplaying = false; From 48ee7a3b1a52c112d83d0b05225474758c7151cb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 29 Jul 2017 17:39:23 +1200 Subject: [PATCH 111/504] Add icon for clone tool --- scripts/vr-edit/modules/toolIcon.js | 12 +++++----- scripts/vr-edit/modules/toolMenu.js | 4 ++-- scripts/vr-edit/vr-edit.js | 36 +++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 8af0e3a884..c9f955ecf5 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -15,12 +15,12 @@ ToolIcon = function (side) { "use strict"; - var NONE = 0, - SCALE_HANDLES = 1, + var SCALE_TOOL = 0, + CLONE_TOOL = 1, ICON_COLORS = [ - { red: 0, green: 0, blue: 0 }, // Unused entry for NONE. - { red: 0, green: 240, blue: 240 } + { red: 0, green: 240, blue: 240 }, + { red: 240, green: 0, blue: 240 } ], LEFT_HAND = 0, @@ -98,8 +98,8 @@ ToolIcon = function (side) { } return { - NONE: NONE, - SCALE_HANDLES: SCALE_HANDLES, + SCALE_TOOL: SCALE_TOOL, + CLONE_TOOL: CLONE_TOOL, setHand: setHand, update: update, display: display, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index ac8c04af18..7684fa66d3 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,7 +10,7 @@ /* global ToolMenu */ -ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallback) { +ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -150,7 +150,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallba }); } if (isButtonPressed) { - setAppScaleWithHandlesCallback(); + setToolCallback(intersectionOverlayID === scaleButtonOverlay ? "scale" : "clone"); } } } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 190110e261..06d438a19e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -20,6 +20,7 @@ // Application state isAppActive = false, isAppScaleWithHandles = false, + isAppCloneEntities = false, dominantHand, // Primary objects @@ -165,7 +166,7 @@ }; - UI = function (side, leftInputs, rightInputs, setAppScaleWithHandlesCallback) { + UI = function (side, leftInputs, rightInputs, setToolCallback) { // Tool menu and Create palette. var // Primary objects. @@ -183,7 +184,7 @@ } toolIcon = new ToolIcon(otherHand(side)); - toolMenu = new ToolMenu(side, leftInputs, rightInputs, setAppScaleWithHandlesCallback); + toolMenu = new ToolMenu(side, leftInputs, rightInputs, setToolCallback); createPalette = new CreatePalette(side, leftInputs, rightInputs); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -254,7 +255,8 @@ setHand: setHand, setToolIcon: setToolIcon, clearToolIcon: clearToolIcon, - SCALE_HANDLES: toolIcon.SCALE_HANDLES, + SCALE_TOOL: toolIcon.SCALE_TOOL, + CLONE_TOOL: toolIcon.CLONE_TOOL, display: display, update: update, clear: clear, @@ -742,8 +744,9 @@ function updateTool() { var isGripClicked = hand.gripClicked(); - if (!wasGripClicked && isGripClicked && isAppScaleWithHandles) { + if (!wasGripClicked && isGripClicked && (isAppScaleWithHandles || isAppCloneEntities)) { isAppScaleWithHandles = false; + isAppCloneEntities = false; ui.clearToolIcon(); } wasGripClicked = isGripClicked; @@ -1010,9 +1013,21 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } - function setAppScaleWithHandles() { - isAppScaleWithHandles = true; - ui.setToolIcon(ui.SCALE_HANDLES); + function setTool(tool) { + switch (tool) { + case "scale": + isAppScaleWithHandles = true; + isAppCloneEntities = false; + ui.setToolIcon(ui.SCALE_TOOL); + break; + case "clone": + isAppScaleWithHandles = false; + isAppCloneEntities = true; + ui.setToolIcon(ui.CLONE_TOOL); + break; + default: + debug("ERROR: Unexpected condition in setTool()!"); + } } function onAppButtonClicked() { @@ -1052,7 +1067,10 @@ // Swap UI hands. ui.setHand(otherHand(dominantHand)); if (isAppScaleWithHandles) { - ui.setToolIcon(ui.SCALE_HANDLES); + ui.setToolIcon(ui.SCALE_TOOL); + } + if (isAppCloneEntities) { + ui.setToolIcon(ui.CLONE_TOOL); } if (isAppActive) { @@ -1090,7 +1108,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], setAppScaleWithHandles); + ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], setTool); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From ab8bccf16b10dce10e5c8028a37fc5126f3a0ca0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 30 Jul 2017 11:03:19 +1200 Subject: [PATCH 112/504] Implement cloning action --- scripts/vr-edit/modules/selection.js | 35 +++++++++++++++++++++++++- scripts/vr-edit/vr-edit.js | 37 +++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 5437e49bf3..dc4d05a374 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -33,7 +33,7 @@ Selection = function (side) { // Recursively traverses tree of entities and their children, gather IDs and properties. var children, properties, - SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "localPosition", + SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "parentID", "localPosition", "dynamic", "collisionless"], i, length; @@ -42,6 +42,7 @@ Selection = function (side) { result.push({ id: id, position: properties.position, + parentID: properties.parentID, localPosition: properties.localPosition, registrationPoint: properties.registrationPoint, rotation: properties.rotation, @@ -294,6 +295,37 @@ Selection = function (side) { select(selectedEntityID); // Refresh. } + function cloneEntities() { + var parentIDIndexes = [], + parentID, + properties, + i, + j, + length; + + // Map parent IDs. + for (i = 1, length = selection.length; i < length; i += 1) { + parentID = selection[i].parentID; + for (j = 0; j < i; j += 1) { + if (parentID === selection[j].id) { + parentIDIndexes[i] = j; + break; + } + } + } + + // Clone entities. + for (i = 0, length = selection.length; i < length; i += 1) { + properties = Entities.getEntityProperties(selection[i].id); + if (i > 0) { + properties.parentID = selection[parentIDIndexes[i]].id; + } + selection[i].id = Entities.addEntity(properties); + } + + rootEntityID = selection[0].id; + } + function clear() { selection = []; selectedEntityID = null; @@ -327,6 +359,7 @@ Selection = function (side) { handleScale: handleScale, finishHandleScaling: finishHandleScaling, finishEditing: finishEditing, + cloneEntities: cloneEntities, deleteEntities: deleteEntities, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 06d438a19e..09ad63280f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -286,8 +286,9 @@ EDITOR_GRABBING = 3, EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. EDITOR_HANDLE_SCALING = 5, // "" + EDITOR_CLONING = 6, EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", - "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING"], + "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING", "EDITOR_CLONING"], editorState = EDITOR_IDLE, // State machine. @@ -694,6 +695,16 @@ laser.enable(); } + function enterEditorCloning() { + selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.cloneEntities(); + highlightedEntityID = selection.rootEntityID(); + } + + function exitEditorCloning() { + // Nothing to do. + } + STATE_MACHINE = { EDITOR_IDLE: { enter: enterEditorIdle, @@ -724,6 +735,11 @@ enter: enterEditorHandleScaling, update: updateEditorHandleScaling, exit: exitEditorHandleScaling + }, + EDITOR_CLONING: { + enter: enterEditorCloning, + update: null, + exit: exitEditorCloning } }; @@ -791,6 +807,8 @@ if (!isAppScaleWithHandles) { setState(EDITOR_DIRECT_SCALING); } + } else if (isAppCloneEntities) { + setState(EDITOR_CLONING); } else { setState(EDITOR_GRABBING); } @@ -836,6 +854,8 @@ } else { debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! A"); } + } else if (isAppCloneEntities) { + setState(EDITOR_CLONING); } else { setState(EDITOR_GRABBING); } @@ -921,6 +941,21 @@ setState(EDITOR_GRABBING); } break; + case EDITOR_CLONING: + // Immediate transition out of state after cloning entities during state entry. + if (hand.valid() && hand.triggerClicked()) { + setState(EDITOR_GRABBING); + } else if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!hand.triggerClicked()) { + if (intersection.entityID && intersection.editableEntity) { + highlightedEntityID = Entities.rootOf(intersection.entityID); + setState(EDITOR_HIGHLIGHTING); + } else { + setState(EDITOR_SEARCHING); + } + } + break; } if (DEBUG && editorState !== previousState) { From 59dc5ec3b8e1552d32901eb1767399cac4037474 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 1 Aug 2017 10:20:55 +1200 Subject: [PATCH 113/504] Refactor tool buttons --- scripts/vr-edit/modules/toolMenu.js | 100 +++++++++++++++------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 7684fa66d3..87588105ae 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -17,8 +17,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { var menuOriginOverlay, menuPanelOverlay, - scaleButtonOverlay, - cloneButtonOverlay, + toolButtonOverlays = [], buttonHighlightOverlay, LEFT_HAND = 0, @@ -77,14 +76,17 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { HIGHLIGHT_Z_OFFSET = -0.002, - BUTTON_POSITIONS = [ - { x: 0.02, y: 0.02, z: 0.0 }, - { x: 0.06, y: 0.02, z: 0.0 } - ], - - BUTTON_COLORS = [ - { red: 0, green: 240, blue: 240 }, - { red: 240, green: 0, blue: 240 } + TOOL_BUTTONS = [ + { // Scale + position: { x: 0.02, y: 0.02, z: 0.0 }, + color: { red: 0, green: 240, blue: 240 }, + callback: "scale" + }, + { // Clone + position: { x: 0.06, y: 0.02, z: 0.0 }, + color: { red: 240, green: 0, blue: 240 }, + callback: "clone" + } ], isDisplaying = false, @@ -113,44 +115,47 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { setHand(side); function getEntityIDs() { - return [menuPanelOverlay, scaleButtonOverlay, cloneButtonOverlay]; + return [menuPanelOverlay].concat(toolButtonOverlays); } function update(intersectionOverlayID) { + var highlightedButton, + i, + length; + // Highlight button. - if ((intersectionOverlayID === scaleButtonOverlay || intersectionOverlayID === cloneButtonOverlay) - !== isHighlightingButton) { + highlightedButton = toolButtonOverlays.indexOf(intersectionOverlayID); + if ((highlightedButton !== -1) !== isHighlightingButton) { isHighlightingButton = !isHighlightingButton; - Overlays.editOverlay(buttonHighlightOverlay, { - localPosition: Vec3.sum({ x: 0, y: 0, z: HIGHLIGHT_Z_OFFSET }, - intersectionOverlayID === scaleButtonOverlay ? BUTTON_POSITIONS[0] : BUTTON_POSITIONS[1]), - visible: isHighlightingButton - }); + if (isHighlightingButton) { + Overlays.editOverlay(buttonHighlightOverlay, { + localPosition: Vec3.sum({ x: 0, y: 0, z: HIGHLIGHT_Z_OFFSET }, TOOL_BUTTONS[highlightedButton].position), + visible: true + }); + + } else { + Overlays.editOverlay(buttonHighlightOverlay, { + visible: false + }); + } } // Button click. if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - if (isButtonPressed && intersectionOverlayID === scaleButtonOverlay) { - Overlays.editOverlay(scaleButtonOverlay, { - localPosition: Vec3.sum(BUTTON_POSITIONS[0], { x: 0, y: 0, z: 0.004 }) - }); - } else { - Overlays.editOverlay(scaleButtonOverlay, { - localPosition: BUTTON_POSITIONS[0] - }); - } - if (isButtonPressed && intersectionOverlayID === cloneButtonOverlay) { - Overlays.editOverlay(cloneButtonOverlay, { - localPosition: Vec3.sum(BUTTON_POSITIONS[1], { x: 0, y: 0, z: 0.004 }) - }); - } else { - Overlays.editOverlay(cloneButtonOverlay, { - localPosition: BUTTON_POSITIONS[1] - }); + for (i = 0, length = toolButtonOverlays.length; i < length; i += 1) { + if (isButtonPressed && intersectionOverlayID === toolButtonOverlays[i]) { + Overlays.editOverlay(toolButtonOverlays[i], { + localPosition: Vec3.sum(TOOL_BUTTONS[i].position, { x: 0, y: 0, z: 0.004 }) + }); + } else { + Overlays.editOverlay(toolButtonOverlays[i], { + localPosition: TOOL_BUTTONS[i].position + }); + } } if (isButtonPressed) { - setToolCallback(intersectionOverlayID === scaleButtonOverlay ? "scale" : "clone"); + setToolCallback(TOOL_BUTTONS[highlightedButton].callback); } } } @@ -158,7 +163,9 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { function display() { // Creates and shows menu entities. var handJointIndex, - properties; + properties, + i, + length; if (isDisplaying) { return; @@ -185,12 +192,11 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { menuPanelOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(BUTTON_PROPERTIES); properties.parentID = menuOriginOverlay; - properties.localPosition = BUTTON_POSITIONS[0]; - properties.color = BUTTON_COLORS[0]; - scaleButtonOverlay = Overlays.addOverlay("cube", properties); - properties.localPosition = BUTTON_POSITIONS[1]; - properties.color = BUTTON_COLORS[1]; - cloneButtonOverlay = Overlays.addOverlay("cube", properties); + for (i = 0, length = TOOL_BUTTONS.length; i < length; i += 1) { + properties.localPosition = TOOL_BUTTONS[i].position; + properties.color = TOOL_BUTTONS[i].color; + toolButtonOverlays[i] = Overlays.addOverlay("cube", properties); + } // Prepare button highlight overlay. properties = Object.clone(BUTTON_HIGHLIGHT_PROPERTIES); @@ -202,13 +208,17 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { function clear() { // Deletes menu entities. + var i, + length; + if (!isDisplaying) { return; } Overlays.deleteOverlay(buttonHighlightOverlay); - Overlays.deleteOverlay(cloneButtonOverlay); - Overlays.deleteOverlay(scaleButtonOverlay); + for (i = 0, length = toolButtonOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(toolButtonOverlays[i]); + } Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); isDisplaying = false; From ca7c747c965f20da9a0b3b5756a4f92f8ed7722e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 1 Aug 2017 11:46:22 +1200 Subject: [PATCH 114/504] Create entities at hand position rather than in front of avatar --- scripts/vr-edit/modules/createPalette.js | 9 +++++---- scripts/vr-edit/modules/hand.js | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index aea2d365b3..928cb48048 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -110,6 +110,7 @@ CreatePalette = function (side, leftInputs, rightInputs) { } function update(intersectionOverlayID) { + var CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }; // Highlight cube. if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { isHighlightingCube = !isHighlightingCube; @@ -119,14 +120,14 @@ CreatePalette = function (side, leftInputs, rightInputs) { // Cube click. if (isHighlightingCube && controlHand.triggerClicked() !== isCubePressed) { isCubePressed = controlHand.triggerClicked(); - if (isCubePressed) { Overlays.editOverlay(cubeOverlay, { localPosition: Vec3.sum(CUBE_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.01 }) }); - CUBE_ENTITY_PROPERTIES.position = - Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.2, z: -1.0 })); - CUBE_ENTITY_PROPERTIES.rotation = MyAvatar.orientation; + CUBE_ENTITY_PROPERTIES.position = Vec3.sum(controlHand.palmPosition(), + Vec3.multiplyQbyV(controlHand.orientation(), + Vec3.sum({ x: 0, y: CUBE_ENTITY_PROPERTIES.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); + CUBE_ENTITY_PROPERTIES.rotation = controlHand.orientation(); Entities.addEntity(CUBE_ENTITY_PROPERTIES); } else { Overlays.editOverlay(cubeOverlay, { diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 0e2bf1c0b3..42d6751ed1 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -39,6 +39,7 @@ Hand = function (side) { handPose, handPosition, handOrientation, + palmPosition, intersection = {}; @@ -70,6 +71,10 @@ Hand = function (side) { return handOrientation; } + function getPalmPosition() { + return palmPosition; + } + function triggerPressed() { return isTriggerPressed; } @@ -88,7 +93,6 @@ Hand = function (side) { function update() { var gripValue, - palmPosition, overlayID, overlayIDs, overlayDistance, @@ -191,6 +195,7 @@ Hand = function (side) { valid: valid, position: position, orientation: orientation, + palmPosition: getPalmPosition, triggerPressed: triggerPressed, triggerClicked: triggerClicked, gripClicked: gripClicked, From f2cf2cab6eda2f42a6265640587d60d06dec6392 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 1 Aug 2017 16:02:04 +1200 Subject: [PATCH 115/504] Generalize tool selected --- scripts/vr-edit/vr-edit.js | 74 ++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 09ad63280f..da945a2668 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -19,10 +19,14 @@ // Application state isAppActive = false, - isAppScaleWithHandles = false, - isAppCloneEntities = false, dominantHand, + // Tool state + TOOL_NONE = 0, + TOOL_SCALE = 1, + TOOL_CLONE = 2, + toolSelected = TOOL_NONE, + // Primary objects Inputs, inputs = [], @@ -294,7 +298,7 @@ // State machine. STATE_MACHINE, highlightedEntityID = null, // Root entity of highlighted entity set. - wasAppScaleWithHandles = false, + wasScaleTool = false, isOtherEditorEditingEntityID = false, wasGripClicked = false, hoveredOverlayID = null, @@ -585,7 +589,7 @@ } function updateEditorSearching() { - if (isAppScaleWithHandles && intersection.overlayID !== hoveredOverlayID && otherEditor.isEditing()) { + if (toolSelected === TOOL_SCALE && intersection.overlayID !== hoveredOverlayID && otherEditor.isEditing()) { hoveredOverlayID = intersection.overlayID; otherEditor.hoverHandle(hoveredOverlayID); } @@ -597,20 +601,20 @@ function enterEditorHighlighting() { selection.select(highlightedEntityID); - if (!isAppScaleWithHandles || !otherEditor.isEditing(highlightedEntityID)) { + if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID)); } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); - wasAppScaleWithHandles = isAppScaleWithHandles; + wasScaleTool = toolSelected === TOOL_SCALE; wasGripClicked = hand.gripClicked(); } function updateEditorHighlighting() { selection.select(highlightedEntityID); - if (!isAppScaleWithHandles || !otherEditor.isEditing(highlightedEntityID)) { + if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - isAppScaleWithHandles || otherEditor.isEditing(highlightedEntityID)); + toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID)); } else { highlights.clear(); } @@ -629,17 +633,17 @@ } else { laser.disable(); } - if (isAppScaleWithHandles) { + if (toolSelected === TOOL_SCALE) { handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); } startEditing(); - wasAppScaleWithHandles = isAppScaleWithHandles; + wasScaleTool = toolSelected === TOOL_SCALE; wasGripClicked = hand.gripClicked(); } function updateEditorGrabbing() { selection.select(highlightedEntityID); - if (isAppScaleWithHandles) { + if (toolSelected === TOOL_SCALE) { handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); } else { handles.clear(); @@ -760,9 +764,8 @@ function updateTool() { var isGripClicked = hand.gripClicked(); - if (!wasGripClicked && isGripClicked && (isAppScaleWithHandles || isAppCloneEntities)) { - isAppScaleWithHandles = false; - isAppCloneEntities = false; + if (!wasGripClicked && isGripClicked && (toolSelected !== TOOL_NONE)) { + toolSelected = TOOL_NONE; ui.clearToolIcon(); } wasGripClicked = isGripClicked; @@ -804,10 +807,10 @@ } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); if (otherEditor.isEditing(highlightedEntityID)) { - if (!isAppScaleWithHandles) { + if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } - } else if (isAppCloneEntities) { + } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); } else { setState(EDITOR_GRABBING); @@ -819,7 +822,8 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && intersection.editableEntity - && !(hand.triggerClicked() && (!otherEditor.isEditing(highlightedEntityID) || !isAppScaleWithHandles)) + && !(hand.triggerClicked() + && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) && !(hand.triggerClicked() && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { // No transition. doUpdateState = false; @@ -830,8 +834,8 @@ highlightedEntityID = Entities.rootOf(intersection.entityID); doUpdateState = true; } - if (isAppScaleWithHandles !== wasAppScaleWithHandles) { - wasAppScaleWithHandles = isAppScaleWithHandles; + if (toolSelected === TOOL_SCALE !== wasScaleTool) { + wasScaleTool = toolSelected === TOOL_SCALE; doUpdateState = true; } if (doUpdateState) { @@ -849,12 +853,12 @@ } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. if (otherEditor.isEditing(highlightedEntityID)) { - if (!isAppScaleWithHandles) { + if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } else { debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! A"); } - } else if (isAppCloneEntities) { + } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); } else { setState(EDITOR_GRABBING); @@ -869,9 +873,9 @@ if (hand.valid() && hand.triggerClicked() && !hand.gripClicked()) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. - if (isAppScaleWithHandles !== wasAppScaleWithHandles) { + if (toolSelected === TOOL_SCALE !== wasScaleTool) { updateState(); - wasAppScaleWithHandles = isAppScaleWithHandles; + wasScaleTool = toolSelected === TOOL_SCALE; } wasGripClicked = false; break; @@ -898,8 +902,8 @@ if (hand.valid() && hand.triggerClicked() && (otherEditor.isEditing(highlightedEntityID) || otherEditor.isHandle(intersection.overlayID))) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. - // Don't test isAppScaleWithHandles because this will eventually be a UI element and so not able to be - // changed while scaling with two hands. + // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while + // scaling with two hands. // No transition. updateState(); break; @@ -921,8 +925,8 @@ case EDITOR_HANDLE_SCALING: if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. - // Don't test isAppScaleWithHandles because this will eventually be a UI element and so not able to be - // changed while scaling with two hands. + // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while + // scaling with two hands. // No transition. updateState(); break; @@ -1051,13 +1055,11 @@ function setTool(tool) { switch (tool) { case "scale": - isAppScaleWithHandles = true; - isAppCloneEntities = false; + toolSelected = TOOL_SCALE; ui.setToolIcon(ui.SCALE_TOOL); break; case "clone": - isAppScaleWithHandles = false; - isAppCloneEntities = true; + toolSelected = TOOL_CLONE; ui.setToolIcon(ui.CLONE_TOOL); break; default: @@ -1101,11 +1103,13 @@ // Swap UI hands. ui.setHand(otherHand(dominantHand)); - if (isAppScaleWithHandles) { + switch (toolSelected) { + case TOOL_SCALE: ui.setToolIcon(ui.SCALE_TOOL); - } - if (isAppCloneEntities) { + break; + case TOOL_CLONE: ui.setToolIcon(ui.CLONE_TOOL); + break; } if (isAppActive) { From 12d911f29050cdf23015f4b0ee412de62e70b5e5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 08:41:53 +1200 Subject: [PATCH 116/504] Add "group" bool button and icon --- scripts/vr-edit/modules/toolIcon.js | 5 ++++- scripts/vr-edit/modules/toolMenu.js | 5 +++++ scripts/vr-edit/vr-edit.js | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index c9f955ecf5..7aa116af81 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -17,10 +17,12 @@ ToolIcon = function (side) { var SCALE_TOOL = 0, CLONE_TOOL = 1, + GROUP_TOOL = 2, ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, - { red: 240, green: 0, blue: 240 } + { red: 240, green: 0, blue: 240 }, + { red: 240, green: 240, blue: 0 } ], LEFT_HAND = 0, @@ -100,6 +102,7 @@ ToolIcon = function (side) { return { SCALE_TOOL: SCALE_TOOL, CLONE_TOOL: CLONE_TOOL, + GROUP_TOOL: GROUP_TOOL, setHand: setHand, update: update, display: display, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 87588105ae..3af6063646 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -86,6 +86,11 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { position: { x: 0.06, y: 0.02, z: 0.0 }, color: { red: 240, green: 0, blue: 240 }, callback: "clone" + }, + { // Group + position: { x: 0.10, y: 0.02, z: 0.0 }, + color: { red: 240, green: 240, blue: 0 }, + callback: "group" } ], diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index da945a2668..dcfa702c46 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -25,6 +25,7 @@ TOOL_NONE = 0, TOOL_SCALE = 1, TOOL_CLONE = 2, + TOOL_GROUP = 3, toolSelected = TOOL_NONE, // Primary objects @@ -261,6 +262,7 @@ clearToolIcon: clearToolIcon, SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, + GROUP_TOOL: toolIcon.GROUP_TOOL, display: display, update: update, clear: clear, @@ -1062,6 +1064,10 @@ toolSelected = TOOL_CLONE; ui.setToolIcon(ui.CLONE_TOOL); break; + case "group": + toolSelected = TOOL_GROUP; + ui.setToolIcon(ui.GROUP_TOOL); + break; default: debug("ERROR: Unexpected condition in setTool()!"); } @@ -1110,6 +1116,9 @@ case TOOL_CLONE: ui.setToolIcon(ui.CLONE_TOOL); break; + case TOOL_GROUP: + ui.setToolIcon(ui.GROUP_TOOL); + break; } if (isAppActive) { From a94b2b367b124b72615d42a3632576052158bb80 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 09:16:24 +1200 Subject: [PATCH 117/504] Simplify grip click handling --- scripts/vr-edit/vr-edit.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index dcfa702c46..2719236a35 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -302,6 +302,7 @@ highlightedEntityID = null, // Root entity of highlighted entity set. wasScaleTool = false, isOtherEditorEditingEntityID = false, + isGripClicked = false, wasGripClicked = false, hoveredOverlayID = null, @@ -587,7 +588,6 @@ selection.clear(); hoveredOverlayID = intersection.overlayID; otherEditor.hoverHandle(hoveredOverlayID); - wasGripClicked = hand.gripClicked(); } function updateEditorSearching() { @@ -609,7 +609,6 @@ } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); wasScaleTool = toolSelected === TOOL_SCALE; - wasGripClicked = hand.gripClicked(); } function updateEditorHighlighting() { @@ -640,7 +639,6 @@ } startEditing(); wasScaleTool = toolSelected === TOOL_SCALE; - wasGripClicked = hand.gripClicked(); } function updateEditorGrabbing() { @@ -765,12 +763,10 @@ function updateTool() { - var isGripClicked = hand.gripClicked(); if (!wasGripClicked && isGripClicked && (toolSelected !== TOOL_NONE)) { toolSelected = TOOL_NONE; ui.clearToolIcon(); } - wasGripClicked = isGripClicked; } @@ -779,6 +775,7 @@ doUpdateState; intersection = getIntersection(); + isGripClicked = hand.gripClicked(); // State update. switch (editorState) { @@ -872,14 +869,14 @@ } break; case EDITOR_GRABBING: - if (hand.valid() && hand.triggerClicked() && !hand.gripClicked()) { + if (hand.valid() && hand.triggerClicked() && !isGripClicked) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (toolSelected === TOOL_SCALE !== wasScaleTool) { updateState(); wasScaleTool = toolSelected === TOOL_SCALE; } - wasGripClicked = false; + //updateTool(); Don't updateTool() because grip button is used to delete grabbed entity. break; } if (!hand.valid()) { @@ -891,7 +888,7 @@ } else { setState(EDITOR_SEARCHING); } - } else if (hand.gripClicked()) { + } else if (isGripClicked) { if (!wasGripClicked) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -908,6 +905,7 @@ // scaling with two hands. // No transition. updateState(); + // updateTool(); Don't updateTool() because this hand is currently using the scaling tool. break; } if (!hand.valid()) { @@ -931,6 +929,7 @@ // scaling with two hands. // No transition. updateState(); + updateTool(); break; } if (!hand.valid()) { @@ -964,6 +963,8 @@ break; } + wasGripClicked = isGripClicked; + if (DEBUG && editorState !== previousState) { debug(side, EDITOR_STATE_STRINGS[editorState]); } From 6d90b6d0fd37a985095b6dee64be402d7b675aa4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 11:49:10 +1200 Subject: [PATCH 118/504] Simplify trigger click usage --- scripts/vr-edit/vr-edit.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2719236a35..a4454e83ba 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -302,6 +302,8 @@ highlightedEntityID = null, // Root entity of highlighted entity set. wasScaleTool = false, isOtherEditorEditingEntityID = false, + isTriggerClicked = false, + wasTriggerClicked = false, isGripClicked = false, wasGripClicked = false, hoveredOverlayID = null, @@ -775,6 +777,7 @@ doUpdateState; intersection = getIntersection(); + isTriggerClicked = hand.triggerClicked(); isGripClicked = hand.gripClicked(); // State update. @@ -788,7 +791,7 @@ break; case EDITOR_SEARCHING: if (hand.valid() && (!intersection.entityID || !intersection.editableEntity) - && !(intersection.overlayID && hand.triggerClicked() && otherEditor.isHandle(intersection.overlayID))) { + && !(intersection.overlayID && isTriggerClicked && otherEditor.isHandle(intersection.overlayID))) { // No transition. updateState(); updateTool(); @@ -796,14 +799,14 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (intersection.overlayID && hand.triggerClicked() + } else if (intersection.overlayID && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && !hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && !isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); if (otherEditor.isEditing(highlightedEntityID)) { if (toolSelected !== TOOL_SCALE) { @@ -821,9 +824,8 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && intersection.editableEntity - && !(hand.triggerClicked() - && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) - && !(hand.triggerClicked() && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { + && !(isTriggerClicked && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) + && !(isTriggerClicked && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { // No transition. doUpdateState = false; if (otherEditor.isEditing(highlightedEntityID) !== isOtherEditorEditingEntityID) { @@ -845,11 +847,11 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (intersection.overlayID && hand.triggerClicked() + } else if (intersection.overlayID && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && hand.triggerClicked()) { + } else if (intersection.entityID && intersection.editableEntity && isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. if (otherEditor.isEditing(highlightedEntityID)) { if (toolSelected !== TOOL_SCALE) { @@ -869,7 +871,7 @@ } break; case EDITOR_GRABBING: - if (hand.valid() && hand.triggerClicked() && !isGripClicked) { + if (hand.valid() && isTriggerClicked && !isGripClicked) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. if (toolSelected === TOOL_SCALE !== wasScaleTool) { @@ -881,7 +883,7 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (!hand.triggerClicked()) { + } else if (!isTriggerClicked) { if (intersection.entityID && intersection.editableEntity) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); @@ -898,7 +900,7 @@ } break; case EDITOR_DIRECT_SCALING: - if (hand.valid() && hand.triggerClicked() + if (hand.valid() && isTriggerClicked && (otherEditor.isEditing(highlightedEntityID) || otherEditor.isHandle(intersection.overlayID))) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while @@ -910,7 +912,7 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (!hand.triggerClicked()) { + } else if (!isTriggerClicked) { if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { @@ -923,7 +925,7 @@ } break; case EDITOR_HANDLE_SCALING: - if (hand.valid() && hand.triggerClicked() && otherEditor.isEditing(highlightedEntityID)) { + if (hand.valid() && isTriggerClicked && otherEditor.isEditing(highlightedEntityID)) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while // scaling with two hands. @@ -934,7 +936,7 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (!hand.triggerClicked()) { + } else if (!isTriggerClicked) { if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { @@ -948,11 +950,11 @@ break; case EDITOR_CLONING: // Immediate transition out of state after cloning entities during state entry. - if (hand.valid() && hand.triggerClicked()) { + if (hand.valid() && isTriggerClicked) { setState(EDITOR_GRABBING); } else if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (!hand.triggerClicked()) { + } else if (!isTriggerClicked) { if (intersection.entityID && intersection.editableEntity) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); @@ -963,6 +965,7 @@ break; } + wasTriggerClicked = isTriggerClicked; wasGripClicked = isGripClicked; if (DEBUG && editorState !== previousState) { From 7e1584a43efc4f8714bfa9cb237b387e08e85d50 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 12:27:02 +1200 Subject: [PATCH 119/504] Add grouping state --- scripts/vr-edit/vr-edit.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a4454e83ba..21a74a146c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -293,8 +293,9 @@ EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. EDITOR_HANDLE_SCALING = 5, // "" EDITOR_CLONING = 6, + EDITOR_GROUPING = 7, EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", - "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING", "EDITOR_CLONING"], + "EDITOR_DIRECT_SCALING", "EDITOR_HANDLE_SCALING", "EDITOR_CLONING", "EDITOR_GROUPING"], editorState = EDITOR_IDLE, // State machine. @@ -711,6 +712,14 @@ // Nothing to do. } + function enterEditorGrouping() { + // TODO: Add/remove highlightedEntityID to/from groups. + } + + function exitEditorGrouping() { + // Nothing to do. + } + STATE_MACHINE = { EDITOR_IDLE: { enter: enterEditorIdle, @@ -746,6 +755,11 @@ enter: enterEditorCloning, update: null, exit: exitEditorCloning + }, + EDITOR_GROUPING: { + enter: enterEditorGrouping, + update: null, + exit: exitEditorGrouping } }; @@ -814,6 +828,8 @@ } } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); + } else if (toolSelected === TOOL_GROUP) { + setState(EDITOR_GROUPING); } else { setState(EDITOR_GRABBING); } @@ -861,6 +877,8 @@ } } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); + } else if (toolSelected === TOOL_GROUP) { + setState(EDITOR_GROUPING); } else { setState(EDITOR_GRABBING); } @@ -922,6 +940,8 @@ } else if (!otherEditor.isEditing(highlightedEntityID)) { // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); + } else { + debug(side, "ERROR: Unexpected condition in EDITOR_DIRECT_SCALING!"); } break; case EDITOR_HANDLE_SCALING: @@ -946,6 +966,8 @@ } else if (!otherEditor.isEditing(highlightedEntityID)) { // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); + } else { + debug(side, "ERROR: Unexpected condition in EDITOR_HANDLE_SCALING!"); } break; case EDITOR_CLONING: @@ -961,6 +983,19 @@ } else { setState(EDITOR_SEARCHING); } + } else { + debug(side, "ERROR: Unexpected condition in EDITOR_CLONING!"); + } + break; + case EDITOR_GROUPING: + if (hand.valid() && isTriggerClicked) { + // No transition. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else { + setState(EDITOR_SEARCHING); } break; } From c686cef4a01928c06f822a95680a3e665e3f0c8d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 13:15:53 +1200 Subject: [PATCH 120/504] Collect grouping data --- scripts/vr-edit/modules/groups.js | 85 ++++++++++++++++++++++++++++ scripts/vr-edit/modules/selection.js | 1 + scripts/vr-edit/vr-edit.js | 71 ++++++++++++++++++++++- 3 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 scripts/vr-edit/modules/groups.js diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js new file mode 100644 index 0000000000..3c6d301b0c --- /dev/null +++ b/scripts/vr-edit/modules/groups.js @@ -0,0 +1,85 @@ +// +// groups.js +// +// Created by David Rowe on 1 Aug 2017. +// Copyright 2017 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 +// + +/* global Groups */ + +Groups = function () { + // Groups and ungroups trees of entities. + + "use strict"; + + var groupRootEntityIDs = [], + groupSelectionDetails = []; + + if (!this instanceof Groups) { + return new Groups(); + } + + function add(selection) { + groupRootEntityIDs.push(selection[0].id); + groupSelectionDetails.push(Object.clone(selection)); + } + + function remove(selection) { + var index = groupRootEntityIDs.indexOf(selection[0].id); + + groupRootEntityIDs.splice(index, 1); + groupSelectionDetails.splice(index, 1); + } + + function toggle(selection) { + if (selection.length === 0) { + return; + } + + if (groupRootEntityIDs.indexOf(selection[0].id) === -1) { + add(selection); + } else { + remove(selection); + } + } + + function getGroups() { + return groupSelectionDetails; + } + + function count() { + return groupSelectionDetails.length; + } + + function group() { + // TODO + } + + function ungroup() { + // TODO + } + + function clear() { + groupRootEntityIDs = []; + groupSelectionDetails = []; + } + + function destroy() { + clear(); + } + + return { + toggle: toggle, + groups: getGroups, + count: count, + group: group, + ungroup: ungroup, + clear: clear, + destroy: destroy + }; +}; + +Groups.prototype = {}; diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index dc4d05a374..89e5221f81 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -31,6 +31,7 @@ Selection = function (side) { function traverseEntityTree(id, result) { // Recursively traverses tree of entities and their children, gather IDs and properties. + // The root entity is always the first entry. var children, properties, SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "parentID", "localPosition", diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 21a74a146c..d456a63087 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -37,9 +37,12 @@ editors = [], LEFT_HAND = 0, RIGHT_HAND = 1, + Grouping, + grouping, // Modules CreatePalette, + Groups, Hand, Handles, Highlights, @@ -61,6 +64,7 @@ // Modules Script.include("./modules/createPalette.js"); + Script.include("./modules/groups.js"); Script.include("./modules/hand.js"); Script.include("./modules/handles.js"); Script.include("./modules/highlights.js"); @@ -713,11 +717,12 @@ } function enterEditorGrouping() { - // TODO: Add/remove highlightedEntityID to/from groups. + highlights.display(intersection.handIntersected, selection.selection(), false); + grouping.toggle(selection.selection()); } function exitEditorGrouping() { - // Nothing to do. + highlights.clear(); } STATE_MACHINE = { @@ -1068,6 +1073,57 @@ }; + Grouping = function () { + // Grouping highlights and functions. + + var groups; + + if (!this instanceof Grouping) { + return new Grouping(); + } + + groups = new Groups(); + + function toggle(selection) { + groups.toggle(selection); + } + + function count() { + return groups.count(); + } + + function group() { + groups.group(); + } + + function ungroup() { + groups.ungroup(); + } + + function update(leftRootEntityID, rightRootEntityID) { + // TODO: Update grouping highlights. + } + + function clear() { + groups.clear(); + } + + function destroy() { + groups.destroy(); + } + + return { + toggle: toggle, + count: count, + group: group, + ungroup: ungroup, + update: update, + clear: clear, + destroy: destroy + }; + }; + + function update() { // Main update loop. updateTimer = null; @@ -1085,6 +1141,9 @@ editors[LEFT_HAND].apply(); editors[RIGHT_HAND].apply(); + // Grouping display. + grouping.update(editors[LEFT_HAND].rootEntityID(), editors[RIGHT_HAND].rootEntityID()); + updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); } @@ -1203,6 +1262,9 @@ editors[LEFT_HAND].setReferences(inputs[LEFT_HAND], editors[RIGHT_HAND]); editors[RIGHT_HAND].setReferences(inputs[RIGHT_HAND], editors[LEFT_HAND]); + // Grouping object. + grouping = new Grouping(); + // Settings changes. MyAvatar.dominantHandChanged.connect(onDominantHandChanged); @@ -1230,6 +1292,11 @@ button = null; } + if (grouping) { + grouping.destroy(); + grouping = null; + } + if (editors[LEFT_HAND]) { editors[LEFT_HAND].destroy(); editors[LEFT_HAND] = null; From 65e57a9262ae8da94753996a2b5b4cd4cda7d606 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 14:38:26 +1200 Subject: [PATCH 121/504] Highlight grouping entities --- scripts/vr-edit/modules/groups.js | 25 +++++++++-- scripts/vr-edit/modules/highlights.js | 13 +++--- scripts/vr-edit/vr-edit.js | 65 ++++++++++++++++++++++++--- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index 3c6d301b0c..5491b14b8c 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -46,8 +46,26 @@ Groups = function () { } } - function getGroups() { - return groupSelectionDetails; + function selection(excludes) { + var result = [], + i, + lengthI, + j, + lengthJ; + + for (i = 0, lengthI = groupRootEntityIDs.length; i < lengthI; i += 1) { + if (excludes.indexOf(groupRootEntityIDs[i]) === -1) { + for (j = 0, lengthJ = groupSelectionDetails[i].length; j < lengthJ; j += 1) { + result.push(groupSelectionDetails[i][j]); + } + } + } + + return result; + } + + function includes(rootEntityID) { + return groupRootEntityIDs.indexOf(rootEntityID) !== -1; } function count() { @@ -73,7 +91,8 @@ Groups = function () { return { toggle: toggle, - groups: getGroups, + selection: selection, + includes: includes, count: count, group: group, ungroup: ungroup, diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index ecde8ffa3e..6a0e789a0d 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -17,8 +17,9 @@ Highlights = function (side) { var handOverlay, entityOverlays = [], - GRAB_HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, - SCALE_HIGHLIGHT_COLOR = { red: 0, green: 240, blue: 240 }, + HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + SCALE_COLOR = { red: 0, green: 240, blue: 240 }, + GROUP_COLOR = { red: 220, green: 60, blue: 220 }, HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, @@ -70,9 +71,8 @@ Highlights = function (side) { }); } - function display(handIntersected, selection, isScale) { - var overlayColor = isScale ? SCALE_HIGHLIGHT_COLOR : GRAB_HIGHLIGHT_COLOR, - i, + function display(handIntersected, selection, overlayColor) { + var i, length; // Show/hide hand overlay. @@ -114,6 +114,9 @@ Highlights = function (side) { } return { + HIGHLIGHT_COLOR: HIGHLIGHT_COLOR, + SCALE_COLOR: SCALE_COLOR, + GROUP_COLOR: GROUP_COLOR, display: display, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d456a63087..1831031b09 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -612,7 +612,8 @@ selection.select(highlightedEntityID); if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID)); + toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) + ? highlights.SCALE_CLOR : highlights.HIGHLIGHT_COLOR); } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); wasScaleTool = toolSelected === TOOL_SCALE; @@ -622,7 +623,8 @@ selection.select(highlightedEntityID); if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID)); + toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) + ? highlights.SCALE_CLOR : highlights.HIGHLIGHT_COLOR); } else { highlights.clear(); } @@ -717,7 +719,9 @@ } function enterEditorGrouping() { - highlights.display(intersection.handIntersected, selection.selection(), false); + if (!grouping.includes(highlightedEntityID)) { + highlights.display(false, selection.selection(), highlights.GROUP_COLOR); + } grouping.toggle(selection.selection()); } @@ -1076,16 +1080,34 @@ Grouping = function () { // Grouping highlights and functions. - var groups; + var groups, + highlights, + exludedLeftRootEntityID = null, + exludedrightRootEntityID = null, + excludedRootEntityIDs = [], + hasHighlights = false, + hasSelectionChanged = false; if (!this instanceof Grouping) { return new Grouping(); } groups = new Groups(); + highlights = new Highlights(); function toggle(selection) { groups.toggle(selection); + if (groups.count() === 0) { + hasHighlights = false; + highlights.clear(); + } else { + hasHighlights = true; + hasSelectionChanged = true; + } + } + + function includes(rootEntityID) { + return groups.includes(rootEntityID); } function count() { @@ -1101,7 +1123,30 @@ } function update(leftRootEntityID, rightRootEntityID) { - // TODO: Update grouping highlights. + var hasExludedRootEntitiesChanged; + + hasExludedRootEntitiesChanged = leftRootEntityID !== exludedLeftRootEntityID + || rightRootEntityID !== exludedrightRootEntityID; + + if (!hasHighlights || (!hasSelectionChanged && !hasExludedRootEntitiesChanged)) { + return; + } + + if (hasExludedRootEntitiesChanged) { + excludedRootEntityIDs = []; + if (leftRootEntityID) { + excludedRootEntityIDs.push(leftRootEntityID); + } + if (rightRootEntityID) { + excludedRootEntityIDs.push(rightRootEntityID); + } + exludedLeftRootEntityID = leftRootEntityID; + exludedrightRootEntityID = rightRootEntityID; + } + + highlights.display(false, groups.selection(excludedRootEntityIDs), highlights.GROUP_COLOR); + + hasSelectionChanged = false; } function clear() { @@ -1109,11 +1154,19 @@ } function destroy() { - groups.destroy(); + if (groups) { + groups.destroy(); + groups = null; + } + if (highlights) { + highlights.destroy(); + highlights = null; + } } return { toggle: toggle, + includes: includes, count: count, group: group, ungroup: ungroup, From 26a14d093492e29a58320bec77cb68727f803ee5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 14:41:42 +1200 Subject: [PATCH 122/504] Swap button and tool icon colours to match grouping highlights --- scripts/vr-edit/modules/toolIcon.js | 4 ++-- scripts/vr-edit/modules/toolMenu.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 7aa116af81..076c5997d4 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -21,8 +21,8 @@ ToolIcon = function (side) { ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, - { red: 240, green: 0, blue: 240 }, - { red: 240, green: 240, blue: 0 } + { red: 240, green: 240, blue: 0 }, + { red: 220, green: 60, blue: 220 } ], LEFT_HAND = 0, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3af6063646..c95ec1a9d8 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -84,12 +84,12 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { }, { // Clone position: { x: 0.06, y: 0.02, z: 0.0 }, - color: { red: 240, green: 0, blue: 240 }, + color: { red: 240, green: 240, blue: 0 }, callback: "clone" }, { // Group position: { x: 0.10, y: 0.02, z: 0.0 }, - color: { red: 240, green: 240, blue: 0 }, + color: { red: 220, green: 60, blue: 220 }, callback: "group" } ], From 53d755d8da7d149fd999cbabbc60fb26842e8f43 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 14:45:19 +1200 Subject: [PATCH 123/504] Clear grouping selection when drop tool --- scripts/vr-edit/vr-edit.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 1831031b09..42a7d85b93 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -790,6 +790,7 @@ function updateTool() { if (!wasGripClicked && isGripClicked && (toolSelected !== TOOL_NONE)) { toolSelected = TOOL_NONE; + grouping.clear(); ui.clearToolIcon(); } } @@ -1151,6 +1152,7 @@ function clear() { groups.clear(); + highlights.clear(); } function destroy() { From fb1284ad615bce9f85a13c4be8cfaf6e2dd7ddf8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 16:12:49 +1200 Subject: [PATCH 124/504] Clear grouping selection when choose another tool --- scripts/vr-edit/vr-edit.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 42a7d85b93..e61ad2311e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1208,6 +1208,10 @@ } function setTool(tool) { + if (toolSelected === TOOL_GROUP) { + grouping.clear(); + } + switch (tool) { case "scale": toolSelected = TOOL_SCALE; From 83f580c514ecd1509f29b8fff7a6ca388cc66875 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 19:07:11 +1200 Subject: [PATCH 125/504] Generalize Tool menu UI --- scripts/vr-edit/modules/toolMenu.js | 238 ++++++++++++++++--------- scripts/vr-edit/utilities/utilities.js | 9 + scripts/vr-edit/vr-edit.js | 14 +- 3 files changed, 165 insertions(+), 96 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c95ec1a9d8..c82b7426d6 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,30 +10,32 @@ /* global ToolMenu */ -ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { +ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Tool menu displayed on top of forearm. "use strict"; - var menuOriginOverlay, + var attachmentJointName, + + menuOriginOverlay, menuPanelOverlay, - toolButtonOverlays = [], - buttonHighlightOverlay, + + menuOverlays = [], + menuCallbacks = [], + highlightOverlay, LEFT_HAND = 0, AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), - controlJointName, - - CANVAS_SIZE = { x: 0.21, y: 0.13 }, - PANEL_ROOT_POSITION = { x: CANVAS_SIZE.x / 2, y: 0.15, z: -0.04 }, + CANVAS_SIZE = { x: 0.22, y: 0.13 }, + PANEL_ORIGIN_POSITION = { x: CANVAS_SIZE.x / 2, y: 0.15, z: -0.04 }, PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), - lateralOffset, + panelLateralOffset, - PANEL_ORIGIN_PROPERTIES = { + MENU_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, - localPosition: PANEL_ROOT_POSITION, + localPosition: PANEL_ORIGIN_POSITION, localRotation: PANEL_ROOT_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, @@ -46,59 +48,99 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0.005 }, localRotation: ZERO_ROTATION, - color: { red: 192, green: 192, blue: 192 }, + color: { red: 164, green: 164, blue: 164 }, alpha: 1.0, solid: true, ignoreRayIntersection: false, visible: true }, - BUTTON_PROPERTIES = { - dimensions: { x: 0.03, y: 0.03, z: 0.01 }, - localRotation: ZERO_ROTATION, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true + UI_ELEMENTS = { + "panel": { + overlay: "cube", + properties: { + dimensions: { x: 0.10, y: 0.12, z: 0.01 }, + localRotation: ZERO_ROTATION, + color: { red: 192, green: 192, blue: 192 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + "button": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + localRotation: ZERO_ROTATION, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + } }, - BUTTON_HIGHLIGHT_PROPERTIES = { - dimensions: { x: 0.034, y: 0.034, z: 0.001 }, - localPosition: { x: 0.02, y: 0.02, z: -0.002 }, - localRotation: ZERO_ROTATION, - color: { red: 240, green: 240, blue: 0 }, - alpha: 0.8, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }, - - HIGHLIGHT_Z_OFFSET = -0.002, - - TOOL_BUTTONS = [ - { // Scale - position: { x: 0.02, y: 0.02, z: 0.0 }, - color: { red: 0, green: 240, blue: 240 }, - callback: "scale" + MENU_ITEMS = [ + { + // Background element + id: "toolsMenuPanel", + type: "panel", + properties: { + localPosition: { x: -0.055, y: 0.0, z: -0.005 } + } }, - { // Clone - position: { x: 0.06, y: 0.02, z: 0.0 }, - color: { red: 240, green: 240, blue: 0 }, - callback: "clone" + { + id: "scaleButton", + type: "button", + properties: { + localPosition: { x: -0.022, y: -0.04, z: -0.005 }, + color: { red: 0, green: 240, blue: 240 } + }, + callback: "scaleTool" }, - { // Group - position: { x: 0.10, y: 0.02, z: 0.0 }, - color: { red: 220, green: 60, blue: 220 }, - callback: "group" + { + id: "cloneButton", + type: "button", + properties: { + localPosition: { x: 0.022, y: -0.04, z: -0.005 }, + color: { red: 240, green: 240, blue: 0 } + }, + callback: "cloneTool" + }, + { + id: "groupButton", + type: "button", + properties: { + localPosition: { x: -0.022, y: 0.0, z: -0.005 }, + color: { red: 220, green: 60, blue: 220 } + }, + callback: "groupTool" } ], - isDisplaying = false, + HIGHLIGHT_PROPERTIES = { + xDelta: 0.004, + yDelta: 0.004, + zDimension: 0.001, + properties: { + localPosition: { x: 0, y: 0, z: -0.003 }, + localRotation: ZERO_ROTATION, + color: { red: 255, green: 255, blue: 0 }, + alpha: 0.8, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + } + }, + highlightedItem = -1, isHighlightingButton = false, isButtonPressed = false, + isDisplaying = false, + // References. controlHand; @@ -113,54 +155,67 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { // Assumes UI is not displaying. side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; - lateralOffset = side === LEFT_HAND ? -0.01 : 0.01; + attachmentJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + panelLateralOffset = side === LEFT_HAND ? -0.01 : 0.01; } setHand(side); function getEntityIDs() { - return [menuPanelOverlay].concat(toolButtonOverlays); + return [menuPanelOverlay].concat(menuOverlays); } function update(intersectionOverlayID) { - var highlightedButton, + var intersectedItem, i, - length; + length, + CLICK_DELTA = 0.004; - // Highlight button. - highlightedButton = toolButtonOverlays.indexOf(intersectionOverlayID); - if ((highlightedButton !== -1) !== isHighlightingButton) { - isHighlightingButton = !isHighlightingButton; - if (isHighlightingButton) { - Overlays.editOverlay(buttonHighlightOverlay, { - localPosition: Vec3.sum({ x: 0, y: 0, z: HIGHLIGHT_Z_OFFSET }, TOOL_BUTTONS[highlightedButton].position), + // Highlight clickable item. + intersectedItem = menuOverlays.indexOf(intersectionOverlayID); + if (intersectedItem !== highlightedItem) { + if (intersectedItem !== -1 && menuCallbacks[intersectedItem] !== undefined) { + Overlays.editOverlay(highlightOverlay, { + parentID: menuOverlays[intersectedItem], + dimensions: { + x: UI_ELEMENTS[MENU_ITEMS[intersectedItem].type].properties.dimensions.x + HIGHLIGHT_PROPERTIES.xDelta, + y: UI_ELEMENTS[MENU_ITEMS[intersectedItem].type].properties.dimensions.y + HIGHLIGHT_PROPERTIES.yDelta, + z: HIGHLIGHT_PROPERTIES.zDimension + }, + localPosition: Vec3.sum(MENU_ITEMS[intersectedItem].properties, + HIGHLIGHT_PROPERTIES.properties.localPosition), + localRotation: HIGHLIGHT_PROPERTIES.properties.localRotation, // Needs to be set again for some reason. + color: HIGHLIGHT_PROPERTIES.properties.color, // "" visible: true }); - + isHighlightingButton = true; } else { - Overlays.editOverlay(buttonHighlightOverlay, { + Overlays.editOverlay(highlightOverlay, { visible: false }); + isHighlightingButton = false; } + highlightedItem = intersectedItem; } - // Button click. if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - for (i = 0, length = toolButtonOverlays.length; i < length; i += 1) { - if (isButtonPressed && intersectionOverlayID === toolButtonOverlays[i]) { - Overlays.editOverlay(toolButtonOverlays[i], { - localPosition: Vec3.sum(TOOL_BUTTONS[i].position, { x: 0, y: 0, z: 0.004 }) - }); - } else { - Overlays.editOverlay(toolButtonOverlays[i], { - localPosition: TOOL_BUTTONS[i].position - }); + for (i = 0, length = menuOverlays.length; i < length; i += 1) { + if (menuCallbacks[intersectedItem] !== undefined) { + if (isButtonPressed && intersectedItem === menuOverlays[i]) { + Overlays.editOverlay(menuOverlays[i], { + localPosition: Vec3.sum(MENU_ITEMS[i].properties.LocalPosition, { x: 0, y: 0, z: CLICK_DELTA }) + }); + } else { + Overlays.editOverlay(menuOverlays[i], { + localPosition: MENU_ITEMS[i].properties.LocalPosition + }); + } + } } if (isButtonPressed) { - setToolCallback(TOOL_BUTTONS[highlightedButton].callback); + commandCallback(MENU_ITEMS[highlightedItem].callback); } } } @@ -169,6 +224,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { // Creates and shows menu entities. var handJointIndex, properties, + parentID, i, length; @@ -177,7 +233,7 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { } // Joint index. - handJointIndex = MyAvatar.getJointIndex(controlJointName); + handJointIndex = MyAvatar.getJointIndex(attachmentJointName); if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. @@ -185,28 +241,32 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { return; } - // Calculate position to put menu. - properties = Object.clone(PANEL_ORIGIN_PROPERTIES); + // Menu origin. + properties = Object.clone(MENU_ORIGIN_PROPERTIES); properties.parentJointIndex = handJointIndex; - properties.localPosition = Vec3.sum(PANEL_ROOT_POSITION, { x: lateralOffset, y: 0, z: 0 }); + properties.localPosition = Vec3.sum(properties.localPosition, { x: panelLateralOffset, y: 0, z: 0 }); menuOriginOverlay = Overlays.addOverlay("sphere", properties); - // Create menu items. + // Panel background. properties = Object.clone(MENU_PANEL_PROPERTIES); properties.parentID = menuOriginOverlay; menuPanelOverlay = Overlays.addOverlay("cube", properties); - properties = Object.clone(BUTTON_PROPERTIES); - properties.parentID = menuOriginOverlay; - for (i = 0, length = TOOL_BUTTONS.length; i < length; i += 1) { - properties.localPosition = TOOL_BUTTONS[i].position; - properties.color = TOOL_BUTTONS[i].color; - toolButtonOverlays[i] = Overlays.addOverlay("cube", properties); + + // Menu items. + parentID = menuPanelOverlay; // Menu panel parents to background panel. + for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); + properties = Object.merge(properties, MENU_ITEMS[i].properties); + properties.parentID = parentID; + menuOverlays.push(Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties)); + parentID = menuOverlays[0]; // Menu buttons parent to menu panel. + menuCallbacks.push(MENU_ITEMS[i].callback); } - // Prepare button highlight overlay. - properties = Object.clone(BUTTON_HIGHLIGHT_PROPERTIES); + // Prepare highlight overlay. + properties = Object.clone(HIGHLIGHT_PROPERTIES); properties.parentID = menuOriginOverlay; - buttonHighlightOverlay = Overlays.addOverlay("cube", properties); + highlightOverlay = Overlays.addOverlay("cube", properties); isDisplaying = true; } @@ -220,9 +280,9 @@ ToolMenu = function (side, leftInputs, rightInputs, setToolCallback) { return; } - Overlays.deleteOverlay(buttonHighlightOverlay); - for (i = 0, length = toolButtonOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(toolButtonOverlays[i]); + Overlays.deleteOverlay(highlightOverlay); + for (i = 0, length = menuOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(menuOverlays[i]); } Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index d737d820ea..cbf8206750 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -55,3 +55,12 @@ if (typeof Object.clone !== "function") { return JSON.parse(JSON.stringify(object)); }; } + +if (typeof Object.merge !== "function") { + Object.merge = function (objectA, objectB) { + var a = JSON.stringify(objectA), + b = JSON.stringify(objectB); + return JSON.parse(a.slice(0, -1) + "," + b.slice(1)); + }; +} + diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e61ad2311e..4fd28f2ce9 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1207,26 +1207,26 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } - function setTool(tool) { + function onUICommand(command) { if (toolSelected === TOOL_GROUP) { grouping.clear(); } - switch (tool) { - case "scale": + switch (command) { + case "scaleTool": toolSelected = TOOL_SCALE; ui.setToolIcon(ui.SCALE_TOOL); break; - case "clone": + case "cloneTool": toolSelected = TOOL_CLONE; ui.setToolIcon(ui.CLONE_TOOL); break; - case "group": + case "groupTool": toolSelected = TOOL_GROUP; ui.setToolIcon(ui.GROUP_TOOL); break; default: - debug("ERROR: Unexpected condition in setTool()!"); + debug("ERROR: Unexpected command in onUICommand()!"); } } @@ -1313,7 +1313,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], setTool); + ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], onUICommand); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From 25bbdba9878456c7577b3e80448509c0a9354fa7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 Aug 2017 22:17:02 +1200 Subject: [PATCH 126/504] Add Tool menu options panels, in particular grouping buttons --- scripts/vr-edit/modules/toolMenu.js | 108 ++++++++++++++++++++++++---- scripts/vr-edit/vr-edit.js | 24 +++++-- 2 files changed, 113 insertions(+), 19 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c82b7426d6..3dc18bcd60 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -22,6 +22,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { menuOverlays = [], menuCallbacks = [], + optionsOverlays = [], + optionsCallbacks = [], highlightOverlay, LEFT_HAND = 0, @@ -81,6 +83,39 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } }, + OPTON_PANELS = { + groupOptions: [ + { + // Background element + id: "toolsOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + }, + { + id: "groupButton", + type: "button", + properties: { + dimensions: { x: 0.07, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: -0.025, z: -0.005 }, + color: { red: 64, green: 240, blue: 64 } + }, + callback: "groupButton" + }, + { + id: "ungroupButton", + type: "button", + properties: { + dimensions: { x: 0.07, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: 0.025, z: -0.005 }, + color: { red: 240, green: 64, blue: 64 } + }, + callback: "ungroupButton" + } + ] + }, + MENU_ITEMS = [ { // Background element @@ -115,6 +150,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: -0.022, y: 0.0, z: -0.005 }, color: { red: 220, green: 60, blue: 220 } }, + toolOptions: "groupOptions", callback: "groupTool" } ], @@ -136,6 +172,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { }, highlightedItem = -1, + highlightedSource = null, isHighlightingButton = false, isButtonPressed = false, @@ -162,30 +199,74 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { setHand(side); function getEntityIDs() { - return [menuPanelOverlay].concat(menuOverlays); + return [menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); + } + + function openOptions(toolOptions) { + var options, + properties, + parentID, + i, + length; + + // Close current panel, if any. + for (i = 0, length = optionsOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(optionsOverlays[i]); + optionsOverlays = []; + optionsCallbacks = []; + } + + // Open specified panel, if any. + if (toolOptions) { + options = OPTON_PANELS[toolOptions]; + parentID = menuPanelOverlay; // Menu panel parents to background panel. + for (i = 0, length = options.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[options[i].type].properties); + properties = Object.merge(properties, options[i].properties); + properties.parentID = parentID; + optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[options[i].type].overlay, properties)); + parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. + optionsCallbacks.push(options[i].callback); + } + } } function update(intersectionOverlayID) { var intersectedItem, + intersectionOverlays, + intersectionCallbacks, + parentProperties, i, length, CLICK_DELTA = 0.004; // Highlight clickable item. intersectedItem = menuOverlays.indexOf(intersectionOverlayID); - if (intersectedItem !== highlightedItem) { - if (intersectedItem !== -1 && menuCallbacks[intersectedItem] !== undefined) { + if (intersectedItem !== -1) { + intersectionOverlays = menuOverlays; + intersectionCallbacks = menuCallbacks; + } else { + intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); + if (intersectedItem !== -1) { + intersectionOverlays = optionsOverlays; + intersectionCallbacks = optionsCallbacks; + } + } + + if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { + if (intersectedItem !== -1 && intersectionCallbacks[intersectedItem] !== undefined) { + parentProperties = Overlays.getProperties(intersectionOverlays[intersectedItem], + ["dimensions", "localPosition"]); Overlays.editOverlay(highlightOverlay, { - parentID: menuOverlays[intersectedItem], + parentID: intersectionOverlays[intersectedItem], dimensions: { - x: UI_ELEMENTS[MENU_ITEMS[intersectedItem].type].properties.dimensions.x + HIGHLIGHT_PROPERTIES.xDelta, - y: UI_ELEMENTS[MENU_ITEMS[intersectedItem].type].properties.dimensions.y + HIGHLIGHT_PROPERTIES.yDelta, + x: parentProperties.dimensions.x + HIGHLIGHT_PROPERTIES.xDelta, + y: parentProperties.dimensions.y + HIGHLIGHT_PROPERTIES.yDelta, z: HIGHLIGHT_PROPERTIES.zDimension }, - localPosition: Vec3.sum(MENU_ITEMS[intersectedItem].properties, - HIGHLIGHT_PROPERTIES.properties.localPosition), - localRotation: HIGHLIGHT_PROPERTIES.properties.localRotation, // Needs to be set again for some reason. - color: HIGHLIGHT_PROPERTIES.properties.color, // "" + localPosition: HIGHLIGHT_PROPERTIES.properties.localPosition, + localRotation: HIGHLIGHT_PROPERTIES.properties.localRotation, + color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); isHighlightingButton = true; @@ -196,6 +277,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { isHighlightingButton = false; } highlightedItem = intersectedItem; + highlightedSource = intersectionOverlays; } if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { @@ -211,11 +293,13 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: MENU_ITEMS[i].properties.LocalPosition }); } - } } if (isButtonPressed) { - commandCallback(MENU_ITEMS[highlightedItem].callback); + if (intersectionOverlays === menuOverlays) { + openOptions(MENU_ITEMS[highlightedItem].toolOptions); + } + commandCallback(intersectionCallbacks[highlightedItem]); } } } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4fd28f2ce9..9cde84503c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -214,16 +214,16 @@ toolIcon.clear(); } - function display() { - var uiEntityIDs; - - toolMenu.display(); - createPalette.display(); - - uiEntityIDs = [].concat(toolMenu.entityIDs(), createPalette.entityIDs()); + function setUIEntities() { + var uiEntityIDs = [].concat(toolMenu.entityIDs(), createPalette.entityIDs()); leftInputs.setUIEntities(side === RIGHT_HAND ? uiEntityIDs : []); rightInputs.setUIEntities(side === LEFT_HAND ? uiEntityIDs : []); + } + function display() { + toolMenu.display(); + createPalette.display(); + setUIEntities(); isDisplaying = true; } @@ -268,6 +268,7 @@ CLONE_TOOL: toolIcon.CLONE_TOOL, GROUP_TOOL: toolIcon.GROUP_TOOL, display: display, + updateUIEntities: setUIEntities, update: update, clear: clear, destroy: destroy @@ -1216,14 +1217,23 @@ case "scaleTool": toolSelected = TOOL_SCALE; ui.setToolIcon(ui.SCALE_TOOL); + ui.updateUIEntities(); break; case "cloneTool": toolSelected = TOOL_CLONE; ui.setToolIcon(ui.CLONE_TOOL); + ui.updateUIEntities(); break; case "groupTool": toolSelected = TOOL_GROUP; ui.setToolIcon(ui.GROUP_TOOL); + ui.updateUIEntities(); + break; + case "groupButton": + grouping.group(); + break; + case "ungroupButton": + grouping.ungroup(); break; default: debug("ERROR: Unexpected command in onUICommand()!"); From 37c1060080a9275d9a5f5f25a60c2d15daa1fba0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 11:06:51 +1200 Subject: [PATCH 127/504] Fix button highlighting and clicking --- scripts/vr-edit/modules/toolMenu.js | 102 +++++++++++++++++----------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3dc18bcd60..887e7b40da 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -24,6 +24,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { menuCallbacks = [], optionsOverlays = [], optionsCallbacks = [], + optionsItems, highlightOverlay, LEFT_HAND = 0, @@ -83,7 +84,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } }, - OPTON_PANELS = { + OPTONS_PANELS = { groupOptions: [ { // Background element @@ -171,9 +172,16 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } }, - highlightedItem = -1, + NONE = -1, + + intersectionOverlays, + intersectionCallbacks, + intersectionProperties, + highlightedItem = NONE, highlightedSource = null, isHighlightingButton = false, + pressedItem = NONE, + pressedSource = null, isButtonPressed = false, isDisplaying = false, @@ -203,8 +211,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } function openOptions(toolOptions) { - var options, - properties, + var properties, parentID, i, length; @@ -218,43 +225,49 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Open specified panel, if any. if (toolOptions) { - options = OPTON_PANELS[toolOptions]; + optionsItems = OPTONS_PANELS[toolOptions]; parentID = menuPanelOverlay; // Menu panel parents to background panel. - for (i = 0, length = options.length; i < length; i += 1) { - properties = Object.clone(UI_ELEMENTS[options[i].type].properties); - properties = Object.merge(properties, options[i].properties); + for (i = 0, length = optionsItems.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); + properties = Object.merge(properties, optionsItems[i].properties); properties.parentID = parentID; - optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[options[i].type].overlay, properties)); + optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. - optionsCallbacks.push(options[i].callback); + optionsCallbacks.push(optionsItems[i].callback); } } } + function update(intersectionOverlayID) { var intersectedItem, - intersectionOverlays, - intersectionCallbacks, parentProperties, - i, - length, - CLICK_DELTA = 0.004; + BUTTON_PRESS_DELTA = 0.004; - // Highlight clickable item. - intersectedItem = menuOverlays.indexOf(intersectionOverlayID); - if (intersectedItem !== -1) { - intersectionOverlays = menuOverlays; - intersectionCallbacks = menuCallbacks; - } else { - intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); + // Intersection details. + if (intersectionOverlayID) { + intersectedItem = menuOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { - intersectionOverlays = optionsOverlays; - intersectionCallbacks = optionsCallbacks; + intersectionOverlays = menuOverlays; + intersectionCallbacks = menuCallbacks; + intersectionProperties = MENU_ITEMS; + } else { + intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); + if (intersectedItem !== -1) { + intersectionOverlays = optionsOverlays; + intersectionCallbacks = optionsCallbacks; + intersectionProperties = optionsItems; + } } } + if (!intersectionOverlays) { + return; + } + // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { if (intersectedItem !== -1 && intersectionCallbacks[intersectedItem] !== undefined) { + // Highlight new button. parentProperties = Overlays.getProperties(intersectionOverlays[intersectedItem], ["dimensions", "localPosition"]); Overlays.editOverlay(highlightOverlay, { @@ -269,35 +282,42 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); + highlightedItem = intersectedItem; isHighlightingButton = true; - } else { + } else if (highlightedItem !== NONE) { + // Un-highlight previous button. Overlays.editOverlay(highlightOverlay, { visible: false }); + highlightedItem = NONE; isHighlightingButton = false; } - highlightedItem = intersectedItem; highlightedSource = intersectionOverlays; } - if (!isHighlightingButton || controlHand.triggerClicked() !== isButtonPressed) { - isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - for (i = 0, length = menuOverlays.length; i < length; i += 1) { - if (menuCallbacks[intersectedItem] !== undefined) { - if (isButtonPressed && intersectedItem === menuOverlays[i]) { - Overlays.editOverlay(menuOverlays[i], { - localPosition: Vec3.sum(MENU_ITEMS[i].properties.LocalPosition, { x: 0, y: 0, z: CLICK_DELTA }) - }); - } else { - Overlays.editOverlay(menuOverlays[i], { - localPosition: MENU_ITEMS[i].properties.LocalPosition - }); - } - } + // Press button. + if (intersectedItem !== pressedItem || intersectionOverlays !== pressedSource + || controlHand.triggerClicked() !== isButtonPressed) { + if (pressedItem !== NONE) { + // Unpress previous button. + Overlays.editOverlay(intersectionOverlays[pressedItem], { + localPosition: intersectionProperties[pressedItem].properties.localPosition + }); + pressedItem = NONE; } + isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); if (isButtonPressed) { + // Press new button. + Overlays.editOverlay(intersectionOverlays[intersectedItem], { + localPosition: Vec3.sum(intersectionProperties[intersectedItem].properties.localPosition, + { x: 0, y: 0, z: BUTTON_PRESS_DELTA }) + }); + pressedItem = intersectedItem; + pressedSource = intersectionOverlays; + + // Button press actions. if (intersectionOverlays === menuOverlays) { - openOptions(MENU_ITEMS[highlightedItem].toolOptions); + openOptions(intersectionProperties[highlightedItem].toolOptions); } commandCallback(intersectionCallbacks[highlightedItem]); } From c4eac1660c378ff5574e1079bfb0f40e5c6d0f46 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 11:46:05 +1200 Subject: [PATCH 128/504] Fix app toggling and hand swapping issues --- scripts/vr-edit/modules/toolMenu.js | 39 ++++++++++++++++++---- scripts/vr-edit/vr-edit.js | 51 +++++++++++++---------------- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 887e7b40da..266abf1596 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -44,7 +44,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { alpha: 1.0, parentID: AVATAR_SELF_ID, ignoreRayIntersection: true, - visible: true + visible: false }, MENU_PANEL_PROPERTIES = { @@ -177,12 +177,12 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { intersectionOverlays, intersectionCallbacks, intersectionProperties, - highlightedItem = NONE, - highlightedSource = null, - isHighlightingButton = false, - pressedItem = NONE, - pressedSource = null, - isButtonPressed = false, + highlightedItem, + highlightedSource, + isHighlightingButton, + pressedItem, + pressedSource, + isButtonPressed, isDisplaying = false, @@ -238,6 +238,9 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } } + function clearTool() { + openOptions(); + } function update(intersectionOverlayID) { var intersectedItem, @@ -372,6 +375,17 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties.parentID = menuOriginOverlay; highlightOverlay = Overlays.addOverlay("cube", properties); + // Initial values. + intersectionOverlays = null; + intersectionCallbacks = null; + intersectionProperties = null; + highlightedItem = NONE; + highlightedSource = null; + isHighlightingButton = false; + pressedItem = NONE; + pressedSource = null; + isButtonPressed = false; + isDisplaying = true; } @@ -385,11 +399,21 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } Overlays.deleteOverlay(highlightOverlay); + for (i = 0, length = optionsOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(optionsOverlays[i]); + } + optionsOverlays = []; + optionsCallbacks = []; + for (i = 0, length = menuOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(menuOverlays[i]); } + menuOverlays = []; + menuCallbacks = []; + Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); + isDisplaying = false; } @@ -400,6 +424,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { return { setHand: setHand, entityIDs: getEntityIDs, + clearTool: clearTool, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 9cde84503c..c810d7f712 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -212,6 +212,7 @@ function clearToolIcon() { toolIcon.clear(); + toolMenu.clearTool(); } function setUIEntities() { @@ -1240,6 +1241,23 @@ } } + function startApp() { + ui.display(); + update(); + } + + function stopApp() { + Script.clearTimeout(updateTimer); + updateTimer = null; + inputs[LEFT_HAND].clear(); + inputs[RIGHT_HAND].clear(); + ui.clear(); + grouping.clear(); + editors[LEFT_HAND].clear(); + editors[RIGHT_HAND].clear(); + toolSelected = TOOL_NONE; + } + function onAppButtonClicked() { // Application tablet/toolbar button clicked. isAppActive = !isAppActive; @@ -1247,16 +1265,9 @@ button.editProperties({ isActive: isAppActive }); if (isAppActive) { - ui.display(); - update(); + startApp(); } else { - Script.clearTimeout(updateTimer); - updateTimer = null; - inputs[LEFT_HAND].clear(); - inputs[RIGHT_HAND].clear(); - ui.clear(); - editors[LEFT_HAND].clear(); - editors[RIGHT_HAND].clear(); + stopApp(); } } @@ -1265,33 +1276,15 @@ if (isAppActive) { // Stop operations. - Script.clearTimeout(updateTimer); - updateTimer = null; - inputs[LEFT_HAND].clear(); - inputs[RIGHT_HAND].clear(); - ui.clear(); - editors[LEFT_HAND].clear(); - editors[RIGHT_HAND].clear(); + stopApp(); } // Swap UI hands. ui.setHand(otherHand(dominantHand)); - switch (toolSelected) { - case TOOL_SCALE: - ui.setToolIcon(ui.SCALE_TOOL); - break; - case TOOL_CLONE: - ui.setToolIcon(ui.CLONE_TOOL); - break; - case TOOL_GROUP: - ui.setToolIcon(ui.GROUP_TOOL); - break; - } if (isAppActive) { // Resume operations. - ui.display(); - update(); + startApp(); } } From 1b866fdf9e617c6e9da7a5b2cd529d7e20b64c2e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 13:20:14 +1200 Subject: [PATCH 129/504] Enable/disable grouping buttons depending on current group selection --- scripts/vr-edit/modules/groups.js | 14 ++++-- scripts/vr-edit/modules/toolMenu.js | 67 +++++++++++++++++++++++++---- scripts/vr-edit/vr-edit.js | 15 ++++--- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index 5491b14b8c..c62615f25e 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -16,7 +16,8 @@ Groups = function () { "use strict"; var groupRootEntityIDs = [], - groupSelectionDetails = []; + groupSelectionDetails = [], + numberOfEntitiesSelected = 0; if (!this instanceof Groups) { return new Groups(); @@ -25,11 +26,13 @@ Groups = function () { function add(selection) { groupRootEntityIDs.push(selection[0].id); groupSelectionDetails.push(Object.clone(selection)); + numberOfEntitiesSelected += selection.length; } function remove(selection) { var index = groupRootEntityIDs.indexOf(selection[0].id); + numberOfEntitiesSelected -= groupSelectionDetails[index].length; groupRootEntityIDs.splice(index, 1); groupSelectionDetails.splice(index, 1); } @@ -68,10 +71,14 @@ Groups = function () { return groupRootEntityIDs.indexOf(rootEntityID) !== -1; } - function count() { + function groupsCount() { return groupSelectionDetails.length; } + function entitiesCount() { + return numberOfEntitiesSelected; + } + function group() { // TODO } @@ -93,7 +100,8 @@ Groups = function () { toggle: toggle, selection: selection, includes: includes, - count: count, + groupsCount: groupsCount, + entitiesCount: entitiesCount, group: group, ungroup: ungroup, clear: clear, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 266abf1596..168cde8254 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -24,7 +24,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { menuCallbacks = [], optionsOverlays = [], optionsCallbacks = [], - optionsItems, + optionsEnabled = [], highlightOverlay, LEFT_HAND = 0, @@ -100,8 +100,9 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, localPosition: { x: 0, y: -0.025, z: -0.005 }, - color: { red: 64, green: 240, blue: 64 } + color: { red: 200, green: 200, blue: 200 } }, + enabledColor: { red: 64, green: 240, blue: 64 }, callback: "groupButton" }, { @@ -110,13 +111,17 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, localPosition: { x: 0, y: 0.025, z: -0.005 }, - color: { red: 240, green: 64, blue: 64 } + color: { red: 200, green: 200, blue: 200 } }, + enabledColor: { red: 240, green: 64, blue: 64 }, callback: "ungroupButton" } ] }, + GROUP_BUTTON_INDEX = 1, + UNGROUP_BUTTON_INDEX = 2, + MENU_ITEMS = [ { // Background element @@ -174,8 +179,10 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { NONE = -1, + optionsItems, intersectionOverlays, intersectionCallbacks, + intersectionCallbacksEnabled, intersectionProperties, highlightedItem, highlightedSource, @@ -183,6 +190,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { pressedItem, pressedSource, isButtonPressed, + isGroupButtonEnabled, + isUngroupButtonEnabled, isDisplaying = false, @@ -221,6 +230,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.deleteOverlay(optionsOverlays[i]); optionsOverlays = []; optionsCallbacks = []; + optionsEnabled = []; + optionsItems = null; } // Open specified panel, if any. @@ -234,18 +245,27 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. optionsCallbacks.push(optionsItems[i].callback); + optionsEnabled.push(true); } } + + // Special handling for Group options. + if (toolOptions === "groupOptions") { + optionsEnabled[GROUP_BUTTON_INDEX] = false; + optionsEnabled[UNGROUP_BUTTON_INDEX] = false; + } } function clearTool() { openOptions(); } - function update(intersectionOverlayID) { + function update(intersectionOverlayID, groupsCount, entitiesCount) { var intersectedItem, parentProperties, - BUTTON_PRESS_DELTA = 0.004; + BUTTON_PRESS_DELTA = 0.004, + enableGroupButton, + enableUngroupButton; // Intersection details. if (intersectionOverlayID) { @@ -253,12 +273,14 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { if (intersectedItem !== -1) { intersectionOverlays = menuOverlays; intersectionCallbacks = menuCallbacks; + intersectionCallbacksEnabled = null; intersectionProperties = MENU_ITEMS; } else { intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { intersectionOverlays = optionsOverlays; intersectionCallbacks = optionsCallbacks; + intersectionCallbacksEnabled = optionsEnabled; intersectionProperties = optionsItems; } } @@ -309,7 +331,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { pressedItem = NONE; } isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - if (isButtonPressed) { + if (isButtonPressed && (intersectionCallbacksEnabled === null || intersectionCallbacksEnabled[intersectedItem])) { // Press new button. Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(intersectionProperties[intersectedItem].properties.localPosition, @@ -320,9 +342,34 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Button press actions. if (intersectionOverlays === menuOverlays) { - openOptions(intersectionProperties[highlightedItem].toolOptions); + openOptions(intersectionProperties[intersectedItem].toolOptions); } - commandCallback(intersectionCallbacks[highlightedItem]); + commandCallback(intersectionCallbacks[intersectedItem]); + } + } + + // Special handling for Group options. + if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { + enableGroupButton = groupsCount > 1; + if (enableGroupButton !== isGroupButtonEnabled) { + isGroupButtonEnabled = enableGroupButton; + Overlays.editOverlay(optionsOverlays[GROUP_BUTTON_INDEX], { + color: isGroupButtonEnabled + ? OPTONS_PANELS.groupOptions[GROUP_BUTTON_INDEX].enabledColor + : OPTONS_PANELS.groupOptions[GROUP_BUTTON_INDEX].properties.color + }); + optionsEnabled[GROUP_BUTTON_INDEX] = enableGroupButton; + } + + enableUngroupButton = groupsCount === 1 && entitiesCount > 1; + if (enableUngroupButton !== isUngroupButtonEnabled) { + isUngroupButtonEnabled = enableUngroupButton; + Overlays.editOverlay(optionsOverlays[UNGROUP_BUTTON_INDEX], { + color: isUngroupButtonEnabled + ? OPTONS_PANELS.groupOptions[UNGROUP_BUTTON_INDEX].enabledColor + : OPTONS_PANELS.groupOptions[UNGROUP_BUTTON_INDEX].properties.color + }); + optionsEnabled[UNGROUP_BUTTON_INDEX] = enableUngroupButton; } } } @@ -376,8 +423,10 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { highlightOverlay = Overlays.addOverlay("cube", properties); // Initial values. + optionsItems = null; intersectionOverlays = null; intersectionCallbacks = null; + intersectionCallbacksEnabled = null; intersectionProperties = null; highlightedItem = NONE; highlightedSource = null; @@ -385,6 +434,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { pressedItem = NONE; pressedSource = null; isButtonPressed = false; + isGroupButtonEnabled = false; + isUngroupButtonEnabled = false; isDisplaying = true; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c810d7f712..238fb5f48c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -230,7 +230,7 @@ function update() { if (isDisplaying) { - toolMenu.update(getIntersection().overlayID); + toolMenu.update(getIntersection().overlayID, grouping.groupsCount(), grouping.entitiesCount()); createPalette.update(getIntersection().overlayID); toolIcon.update(); } @@ -1100,7 +1100,7 @@ function toggle(selection) { groups.toggle(selection); - if (groups.count() === 0) { + if (groups.groupsCount() === 0) { hasHighlights = false; highlights.clear(); } else { @@ -1113,8 +1113,12 @@ return groups.includes(rootEntityID); } - function count() { - return groups.count(); + function groupsCount() { + return groups.groupsCount(); + } + + function entitiesCount() { + return groups.entitiesCount(); } function group() { @@ -1171,7 +1175,8 @@ return { toggle: toggle, includes: includes, - count: count, + groupsCount: groupsCount, + entitiesCount: entitiesCount, group: group, ungroup: ungroup, update: update, From 1e1cb3a02eeb3495f771c535f90c1a2538a0e7bd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 13:24:38 +1200 Subject: [PATCH 130/504] Fix scale tool highlight color --- scripts/vr-edit/vr-edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 238fb5f48c..6ddaeb5b97 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -615,7 +615,7 @@ if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) - ? highlights.SCALE_CLOR : highlights.HIGHLIGHT_COLOR); + ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); wasScaleTool = toolSelected === TOOL_SCALE; @@ -626,7 +626,7 @@ if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) - ? highlights.SCALE_CLOR : highlights.HIGHLIGHT_COLOR); + ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } else { highlights.clear(); } From 1ba658ee454b3f5c5a7f93a720b795ce5abf6e67 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 19:52:39 +1200 Subject: [PATCH 131/504] Implement grouping and ungrouping --- scripts/vr-edit/modules/groups.js | 55 +++++++++++++++++++++++++++++-- scripts/vr-edit/vr-edit.js | 6 ++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index c62615f25e..39217c8a76 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -80,16 +80,67 @@ Groups = function () { } function group() { - // TODO + var rootID, + i, + count; + + // Make the first entity in the first group the root and link the first entities of all other groups to it. + rootID = groupRootEntityIDs[0]; + for (i = 1, count = groupRootEntityIDs.length; i < count; i += 1) { + Entities.editEntity(groupRootEntityIDs[i], { + parentID: rootID + }); + } + + // Update selection. + groupRootEntityIDs.splice(1, groupRootEntityIDs.length - 1); + for (i = 1, count = groupSelectionDetails.length; i < count; i += 1) { + groupSelectionDetails[i][0].parentID = rootID; + groupSelectionDetails[0] = groupSelectionDetails[0].concat(groupSelectionDetails[i]); + } + groupSelectionDetails.splice(1, groupSelectionDetails.length - 1); } function ungroup() { - // TODO + var rootID, + childrenIDs, + childrenIDIndexes, + i, + count, + NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + + // Compile information on children. + rootID = groupRootEntityIDs[0]; + childrenIDs = []; + childrenIDIndexes = []; + for (i = 1, count = groupSelectionDetails[0].length; i < count; i += 1) { + if (groupSelectionDetails[0][i].parentID === rootID) { + childrenIDs.push(groupSelectionDetails[0][i].id); + childrenIDIndexes.push(i); + } + } + childrenIDIndexes.push(groupSelectionDetails[0].length); // Extra item at end to aid updating selection. + + // Unlink direct children from root entity. + for (i = 0, count = childrenIDs.length; i < count; i += 1) { + Entities.editEntity(childrenIDs[i], { + parentID: NULL_UUID + }); + } + + // Update selection. + groupRootEntityIDs = groupRootEntityIDs.concat(childrenIDs); + for (i = 0, count = childrenIDs.length; i < count; i += 1) { + groupSelectionDetails.push(groupSelectionDetails[0].slice(childrenIDIndexes[i], childrenIDIndexes[i + 1])); + groupSelectionDetails[i + 1][0].parentID = NULL_UUID; + } + groupSelectionDetails[0].splice(1, groupSelectionDetails[0].length - childrenIDIndexes[0]); } function clear() { groupRootEntityIDs = []; groupSelectionDetails = []; + numberOfEntitiesSelected = 0; } function destroy() { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6ddaeb5b97..4231865016 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1215,17 +1215,15 @@ } function onUICommand(command) { - if (toolSelected === TOOL_GROUP) { - grouping.clear(); - } - switch (command) { case "scaleTool": + grouping.clear(); toolSelected = TOOL_SCALE; ui.setToolIcon(ui.SCALE_TOOL); ui.updateUIEntities(); break; case "cloneTool": + grouping.clear(); toolSelected = TOOL_CLONE; ui.setToolIcon(ui.CLONE_TOOL); ui.updateUIEntities(); From e29bed7f0700ada16cb0262c9c9f222318be7b81 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 3 Aug 2017 20:26:21 +1200 Subject: [PATCH 132/504] Don't auto-grab if trigger fully pressed when laser starts intersecting --- scripts/vr-edit/vr-edit.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4231865016..83a62b9cb9 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -816,8 +816,10 @@ setState(EDITOR_SEARCHING); break; case EDITOR_SEARCHING: - if (hand.valid() && (!intersection.entityID || !intersection.editableEntity) - && !(intersection.overlayID && isTriggerClicked && otherEditor.isHandle(intersection.overlayID))) { + if (hand.valid() + && (!intersection.entityID || !intersection.editableEntity) + && !(intersection.overlayID && !wasTriggerClicked && isTriggerClicked + && otherEditor.isHandle(intersection.overlayID))) { // No transition. updateState(); updateTool(); @@ -825,14 +827,14 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (intersection.overlayID && isTriggerClicked + } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && !isTriggerClicked) { + } else if (intersection.entityID && intersection.editableEntity && (wasTriggerClicked || !isTriggerClicked)) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && intersection.editableEntity && isTriggerClicked) { + } else if (intersection.entityID && intersection.editableEntity && !wasTriggerClicked && isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); if (otherEditor.isEditing(highlightedEntityID)) { if (toolSelected !== TOOL_SCALE) { @@ -852,8 +854,10 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && intersection.editableEntity - && !(isTriggerClicked && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) - && !(isTriggerClicked && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { + && !(!wasTriggerClicked && isTriggerClicked + && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) + && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID + && otherEditor.isHandle(intersection.overlayID))) { // No transition. doUpdateState = false; if (otherEditor.isEditing(highlightedEntityID) !== isOtherEditorEditingEntityID) { @@ -875,11 +879,11 @@ } if (!hand.valid()) { setState(EDITOR_IDLE); - } else if (intersection.overlayID && isTriggerClicked + } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && isTriggerClicked) { + } else if (intersection.entityID && intersection.editableEntity && !wasTriggerClicked && isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. if (otherEditor.isEditing(highlightedEntityID)) { if (toolSelected !== TOOL_SCALE) { From 110355796ce2e79b1afe6b5cb6d51ab3085078cc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 08:55:43 +1200 Subject: [PATCH 133/504] Add delete tool --- scripts/vr-edit/modules/toolIcon.js | 5 ++++- scripts/vr-edit/modules/toolMenu.js | 9 +++++++++ scripts/vr-edit/vr-edit.js | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 076c5997d4..6eaf37e161 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -18,11 +18,13 @@ ToolIcon = function (side) { var SCALE_TOOL = 0, CLONE_TOOL = 1, GROUP_TOOL = 2, + DELETE_TOOL = 3, ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, { red: 240, green: 240, blue: 0 }, - { red: 220, green: 60, blue: 220 } + { red: 220, green: 60, blue: 220 }, + { red: 240, green: 60, blue: 60 } ], LEFT_HAND = 0, @@ -103,6 +105,7 @@ ToolIcon = function (side) { SCALE_TOOL: SCALE_TOOL, CLONE_TOOL: CLONE_TOOL, GROUP_TOOL: GROUP_TOOL, + DELETE_TOOL: DELETE_TOOL, setHand: setHand, update: update, display: display, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 168cde8254..e98d38f3b7 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -158,6 +158,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { }, toolOptions: "groupOptions", callback: "groupTool" + }, + { + id: "deleteButton", + type: "button", + properties: { + localPosition: { x: 0.022, y: 0.04, z: -0.005 }, + color: { red: 240, green: 60, blue: 60 } + }, + callback: "deleteTool" } ], diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 83a62b9cb9..ee37bd7518 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -26,6 +26,7 @@ TOOL_SCALE = 1, TOOL_CLONE = 2, TOOL_GROUP = 3, + TOOL_DELETE = 4, toolSelected = TOOL_NONE, // Primary objects @@ -268,6 +269,7 @@ SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, GROUP_TOOL: toolIcon.GROUP_TOOL, + DELETE_TOOL: toolIcon.DELETE_TOOL, display: display, updateUIEntities: setUIEntities, update: update, @@ -844,6 +846,10 @@ setState(EDITOR_CLONING); } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_DELETE) { + setState(EDITOR_HIGHLIGHTING); + selection.deleteEntities(); + setState(EDITOR_SEARCHING); } else { setState(EDITOR_GRABBING); } @@ -895,6 +901,9 @@ setState(EDITOR_CLONING); } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_DELETE) { + selection.deleteEntities(); + setState(EDITOR_SEARCHING); } else { setState(EDITOR_GRABBING); } @@ -1237,6 +1246,12 @@ ui.setToolIcon(ui.GROUP_TOOL); ui.updateUIEntities(); break; + case "deleteTool": + grouping.clear(); + toolSelected = TOOL_DELETE; + ui.setToolIcon(ui.DELETE_TOOL); + ui.updateUIEntities(); + break; case "groupButton": grouping.group(); break; From 4d8226ac02fa09662d84f681361224d3049b3549 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 09:23:40 +1200 Subject: [PATCH 134/504] Abstract out some common constants missing from the API --- scripts/vr-edit/modules/createPalette.js | 10 ++++------ scripts/vr-edit/modules/groups.js | 7 +++---- scripts/vr-edit/modules/handles.js | 5 ++--- scripts/vr-edit/modules/highlights.js | 8 +++----- scripts/vr-edit/modules/laser.js | 3 +-- scripts/vr-edit/modules/toolIcon.js | 3 +-- scripts/vr-edit/modules/toolMenu.js | 12 +++++------- scripts/vr-edit/utilities/utilities.js | 21 +++++++++++++++------ 8 files changed, 34 insertions(+), 35 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 928cb48048..be9558e432 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -21,8 +21,6 @@ CreatePalette = function (side, leftInputs, rightInputs) { cubeHighlightOverlay, LEFT_HAND = 0, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), controlJointName, @@ -37,7 +35,7 @@ CreatePalette = function (side, leftInputs, rightInputs) { localRotation: PALETTE_ROOT_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, - parentID: AVATAR_SELF_ID, + parentID: Uuid.SELF, ignoreRayIntersection: true, visible: false }, @@ -45,7 +43,7 @@ CreatePalette = function (side, leftInputs, rightInputs) { PALETTE_PANEL_PROPERTIES = { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 192, green: 192, blue: 192 }, alpha: 0.3, solid: true, @@ -56,7 +54,7 @@ CreatePalette = function (side, leftInputs, rightInputs) { CUBE_PROPERTIES = { dimensions: { x: 0.03, y: 0.03, z: 0.03 }, localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, solid: true, @@ -67,7 +65,7 @@ CreatePalette = function (side, leftInputs, rightInputs) { CUBE_HIGHLIGHT_PROPERTIES = { dimensions: { x: 0.034, y: 0.034, z: 0.034 }, localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 240, green: 240, blue: 0 }, alpha: 0.8, solid: false, diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index 39217c8a76..036df1a71c 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -106,8 +106,7 @@ Groups = function () { childrenIDs, childrenIDIndexes, i, - count, - NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + count; // Compile information on children. rootID = groupRootEntityIDs[0]; @@ -124,7 +123,7 @@ Groups = function () { // Unlink direct children from root entity. for (i = 0, count = childrenIDs.length; i < count; i += 1) { Entities.editEntity(childrenIDs[i], { - parentID: NULL_UUID + parentID: Uuid.NULL }); } @@ -132,7 +131,7 @@ Groups = function () { groupRootEntityIDs = groupRootEntityIDs.concat(childrenIDs); for (i = 0, count = childrenIDs.length; i < count; i += 1) { groupSelectionDetails.push(groupSelectionDetails[0].slice(childrenIDIndexes[i], childrenIDIndexes[i + 1])); - groupSelectionDetails[i + 1][0].parentID = NULL_UUID; + groupSelectionDetails[i + 1][0].parentID = Uuid.NULL; } groupSelectionDetails[0].splice(1, groupSelectionDetails[0].length - childrenIDIndexes[0]); } diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index b9390b02d3..e32918160a 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -38,7 +38,6 @@ Handles = function (side) { FACE_HANDLE_OVERLAY_OFFSETS, FACE_HANDLE_OVERLAY_ROTATIONS, FACE_HANDLE_OVERLAY_SCALE_AXES, - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, hoveredOverlayID = null, isVisible = false, @@ -154,7 +153,7 @@ Handles = function (side) { boundingBoxOverlay = Overlays.addOverlay("cube", { parentID: rootEntityID, localPosition: boundingBoxLocalCenter, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, dimensions: boundingBoxDimensions, color: BOUNDING_BOX_COLOR, alpha: BOUNDING_BOX_ALPHA, @@ -196,7 +195,7 @@ Handles = function (side) { parentID: rootEntityID, localPosition: Vec3.sum(boundingBoxLocalCenter, Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, dimensions: cornerHandleDimensions, color: HANDLE_NORMAL_COLOR, alpha: HANDLE_NORMAL_ALPHA, diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 6a0e789a0d..9f0dc44275 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -24,9 +24,7 @@ Highlights = function (side) { ENTITY_HIGHLIGHT_ALPHA = 0.8, HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, - LEFT_HAND = 0, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO); + LEFT_HAND = 0; if (!this instanceof Highlights) { return new Highlights(); @@ -34,7 +32,7 @@ Highlights = function (side) { handOverlay = Overlays.addOverlay("sphere", { dimensions: HAND_HIGHLIGHT_DIMENSIONS, - parentID: AVATAR_SELF_ID, + parentID: Uuid.SELF, parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND ? "_CONTROLLER_LEFTHAND" : "_CONTROLLER_RIGHTHAND"), @@ -64,7 +62,7 @@ Highlights = function (side) { Overlays.editOverlay(entityOverlays[index], { parentID: details.id, localPosition: offset, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, dimensions: details.dimensions, color: overlayColor, visible: true diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index fefa13b8ff..991c9173b6 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -46,7 +46,6 @@ Laser = function (side) { specifiedLaserLength = null, LEFT_HAND = 0, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", uiEntityIDs = [], @@ -77,7 +76,7 @@ Laser = function (side) { glow: 1.0, ignoreRayIntersection: true, drawInFront: true, - parentID: AVATAR_SELF_ID, + parentID: Uuid.SELF, parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 6eaf37e161..4f8f2da752 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -28,7 +28,6 @@ ToolIcon = function (side) { ], LEFT_HAND = 0, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", ICON_DIMENSIONS = { x: 0.1, y: 0.01, z: 0.1 }, ICON_POSITION = { x: 0, y: 0.01, z: 0 }, @@ -41,7 +40,7 @@ ToolIcon = function (side) { localRotation: ICON_ROTATION, solid: true, alpha: 1.0, - parentID: AVATAR_SELF_ID, + parentID: Uuid.SELF, ignoreRayIntersection: false, visible: true }, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index e98d38f3b7..3c5fab0e23 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -28,8 +28,6 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { highlightOverlay, LEFT_HAND = 0, - AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}", - ZERO_ROTATION = Quat.fromVec3Radians(Vec3.ZERO), CANVAS_SIZE = { x: 0.22, y: 0.13 }, PANEL_ORIGIN_POSITION = { x: CANVAS_SIZE.x / 2, y: 0.15, z: -0.04 }, @@ -42,7 +40,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localRotation: PANEL_ROOT_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, - parentID: AVATAR_SELF_ID, + parentID: Uuid.SELF, ignoreRayIntersection: true, visible: false }, @@ -50,7 +48,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { MENU_PANEL_PROPERTIES = { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0.005 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 164, green: 164, blue: 164 }, alpha: 1.0, solid: true, @@ -63,7 +61,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { overlay: "cube", properties: { dimensions: { x: 0.10, y: 0.12, z: 0.01 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 192, green: 192, blue: 192 }, alpha: 1.0, solid: true, @@ -75,7 +73,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -176,7 +174,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { zDimension: 0.001, properties: { localPosition: { x: 0, y: 0, z: -0.003 }, - localRotation: ZERO_ROTATION, + localRotation: Quat.ZERO, color: { red: 255, green: 255, blue: 0 }, alpha: 0.8, solid: false, diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index cbf8206750..62343c5436 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -20,15 +20,26 @@ if (typeof Vec3.max !== "function") { }; } +if (typeof Quat.ZERO !== "object") { + Quat.ZERO = Quat.fromVec3Radians(Vec3.ZERO); +} + +if (typeof Uuid.NULL !== "string") { + Uuid.NULL = "{00000000-0000-0000-0000-000000000000}"; +} + +if (typeof Uuid.SELF !== "string") { + Uuid.SELF = "{00000000-0000-0000-0000-000000000001}"; +} + if (typeof Entities.rootOf !== "function") { Entities.rootOf = function (entityID) { var rootEntityID, entityProperties, - PARENT_PROPERTIES = ["parentID"], - NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + PARENT_PROPERTIES = ["parentID"]; rootEntityID = entityID; entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); - while (entityProperties.parentID && entityProperties.parentID !== NULL_UUID) { + while (entityProperties.parentID && entityProperties.parentID !== Uuid.NULL) { rootEntityID = entityProperties.parentID; entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); } @@ -40,10 +51,9 @@ if (typeof Entities.hasEditableRoot !== "function") { Entities.hasEditableRoot = function (entityID) { var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], - NULL_UUID = "{00000000-0000-0000-0000-000000000000}", properties; properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); - while (properties.parentID && properties.parentID !== NULL_UUID) { + while (properties.parentID && properties.parentID !== Uuid.NULL) { properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); } return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; @@ -63,4 +73,3 @@ if (typeof Object.merge !== "function") { return JSON.parse(a.slice(0, -1) + "," + b.slice(1)); }; } - From 2237290b2e2d592436024a1b68cd2c823bf284b3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 13:22:48 +1200 Subject: [PATCH 135/504] Add labels to buttons --- scripts/vr-edit/modules/toolMenu.js | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3c5fab0e23..e082da08ae 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -79,6 +79,24 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { ignoreRayIntersection: false, visible: true } + }, + "label": { + overlay: "text3d", + properties: { + dimensions: { x: 0.03, y: 0.0075 }, + localPosition: { x: 0.0, y: 0.0, z: -0.005 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + topMargin: 0, + leftMargin: 0, + color: { red: 128, green: 128, blue: 128 }, + alpha: 1.0, + lineHeight: 0.007, + backgroundAlpha: 0, + ignoreRayIntersection: true, + isFacingAvatar: false, + drawInFront: true, + visible: true + } } }, @@ -100,6 +118,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: 0, y: -0.025, z: -0.005 }, color: { red: 200, green: 200, blue: 200 } }, + label: " GROUP", enabledColor: { red: 64, green: 240, blue: 64 }, callback: "groupButton" }, @@ -111,6 +130,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: 0, y: 0.025, z: -0.005 }, color: { red: 200, green: 200, blue: 200 } }, + label: "UNGROUP", enabledColor: { red: 240, green: 64, blue: 64 }, callback: "ungroupButton" } @@ -136,6 +156,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: -0.022, y: -0.04, z: -0.005 }, color: { red: 0, green: 240, blue: 240 } }, + label: " SCALE", callback: "scaleTool" }, { @@ -145,6 +166,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: 0.022, y: -0.04, z: -0.005 }, color: { red: 240, green: 240, blue: 0 } }, + label: " CLONE", callback: "cloneTool" }, { @@ -154,6 +176,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: -0.022, y: 0.0, z: -0.005 }, color: { red: 220, green: 60, blue: 220 } }, + label: " GROUP", toolOptions: "groupOptions", callback: "groupTool" }, @@ -164,6 +187,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { localPosition: { x: 0.022, y: 0.04, z: -0.005 }, color: { red: 240, green: 60, blue: 60 } }, + label: " DELETE", callback: "deleteTool" } ], @@ -250,6 +274,12 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties = Object.merge(properties, optionsItems[i].properties); properties.parentID = parentID; optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); + if (optionsItems[i].label) { + properties = Object.clone(UI_ELEMENTS.label.properties); + properties.text = optionsItems[i].label; + properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. optionsCallbacks.push(optionsItems[i].callback); optionsEnabled.push(true); @@ -420,6 +450,12 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties = Object.merge(properties, MENU_ITEMS[i].properties); properties.parentID = parentID; menuOverlays.push(Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties)); + if (MENU_ITEMS[i].label) { + properties = Object.clone(UI_ELEMENTS.label.properties); + properties.text = MENU_ITEMS[i].label; + properties.parentID = menuOverlays[menuOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + } parentID = menuOverlays[0]; // Menu buttons parent to menu panel. menuCallbacks.push(MENU_ITEMS[i].callback); } From ed497afdc36e48c06db0dc836ae478125fb90868 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 14:08:57 +1200 Subject: [PATCH 136/504] Add "Color" tool button, icon, and empty options panel --- scripts/vr-edit/modules/toolIcon.js | 5 ++++- scripts/vr-edit/modules/toolMenu.js | 23 +++++++++++++++++++++-- scripts/vr-edit/vr-edit.js | 15 ++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 4f8f2da752..3b67d7790b 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -18,12 +18,14 @@ ToolIcon = function (side) { var SCALE_TOOL = 0, CLONE_TOOL = 1, GROUP_TOOL = 2, - DELETE_TOOL = 3, + COLOR_TOOL = 3, + DELETE_TOOL = 4, ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, { red: 240, green: 240, blue: 0 }, { red: 220, green: 60, blue: 220 }, + { red: 220, green: 220, blue: 220 }, { red: 240, green: 60, blue: 60 } ], @@ -104,6 +106,7 @@ ToolIcon = function (side) { SCALE_TOOL: SCALE_TOOL, CLONE_TOOL: CLONE_TOOL, GROUP_TOOL: GROUP_TOOL, + COLOR_TOOL: COLOR_TOOL, DELETE_TOOL: DELETE_TOOL, setHand: setHand, update: update, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index e082da08ae..9ae428de90 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -103,8 +103,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { OPTONS_PANELS = { groupOptions: [ { - // Background element - id: "toolsOptionsPanel", + id: "groupOptionsPanel", type: "panel", properties: { localPosition: { x: 0.055, y: 0.0, z: -0.005 } @@ -134,6 +133,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { enabledColor: { red: 240, green: 64, blue: 64 }, callback: "ungroupButton" } + ], + colorOptions: [ + { + id: "colorOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + } ] }, @@ -180,6 +188,17 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { toolOptions: "groupOptions", callback: "groupTool" }, + { + id: "colorButton", + type: "button", + properties: { + localPosition: { x: 0.022, y: 0.0, z: -0.005 }, + color: { red: 220, green: 220, blue: 220 } + }, + label: " COLOR", + toolOptions: "colorOptions", + callback: "colorTool" + }, { id: "deleteButton", type: "button", diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index ee37bd7518..7d45fd4dc4 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -26,7 +26,8 @@ TOOL_SCALE = 1, TOOL_CLONE = 2, TOOL_GROUP = 3, - TOOL_DELETE = 4, + TOOL_COLOR = 4, + TOOL_DELETE = 5, toolSelected = TOOL_NONE, // Primary objects @@ -269,6 +270,7 @@ SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, GROUP_TOOL: toolIcon.GROUP_TOOL, + COLOR_TOOL: toolIcon.COLOR_TOOL, DELETE_TOOL: toolIcon.DELETE_TOOL, display: display, updateUIEntities: setUIEntities, @@ -846,6 +848,9 @@ setState(EDITOR_CLONING); } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_COLOR) { + // TODO + print("$$$$$$$ apply color"); } else if (toolSelected === TOOL_DELETE) { setState(EDITOR_HIGHLIGHTING); selection.deleteEntities(); @@ -901,6 +906,9 @@ setState(EDITOR_CLONING); } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_COLOR) { + // TODO + print("$$$$$$$ apply color"); } else if (toolSelected === TOOL_DELETE) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -1246,6 +1254,11 @@ ui.setToolIcon(ui.GROUP_TOOL); ui.updateUIEntities(); break; + case "colorTool": + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + ui.updateUIEntities(); + break; case "deleteTool": grouping.clear(); toolSelected = TOOL_DELETE; From d0143c2c19f3d25a67717bcfec511250a51b2b50 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 17:33:00 +1200 Subject: [PATCH 137/504] Add color swatch buttons and "current color" circle --- scripts/vr-edit/modules/toolMenu.js | 161 ++++++++++++++++++++++++++-- scripts/vr-edit/vr-edit.js | 2 +- 2 files changed, 152 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 9ae428de90..7a17a0fede 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -22,9 +22,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { menuOverlays = [], menuCallbacks = [], + optionsOverlays = [], + optionsOverlaysIDs = [], + optionsCommands = [], + optionsCommandsParameters = [], optionsCallbacks = [], + optionsCallbacksParameters = [], optionsEnabled = [], + highlightOverlay, LEFT_HAND = 0, @@ -97,6 +103,19 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { drawInFront: true, visible: true } + }, + "circle": { + overlay: "circle3d", + properties: { + size: 0.015, + localPosition: { x: 0.0, y: 0.0, z: -0.01 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + color: { red: 128, green: 128, blue: 128 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: true, + visible: true + } } }, @@ -141,6 +160,66 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties: { localPosition: { x: 0.055, y: 0.0, z: -0.005 } } + }, + { + id: "colorSwatch1", + type: "button", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.035, y: 0.02, z: -0.005 }, + color: { red: 255, green: 0, blue: 0 } + }, + command: "setCurrentColor", + commandParameter: "colorSwatch1.color", + callback: "setColor", + callbackParameter: "colorSwatch1.color" + }, + { + id: "colorSwatch2", + type: "button", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.01, y: 0.02, z: -0.005 }, + color: { red: 0, green: 255, blue: 0 } + }, + command: "setCurrentColor", + commandParameter: "colorSwatch2.color", + callback: "setColor", + callbackParameter: "colorSwatch2.color" + }, + { + id: "colorSwatch3", + type: "button", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.035, y: 0.045, z: -0.005 }, + color: { red: 0, green: 0, blue: 255 } + }, + command: "setCurrentColor", + commandParameter: "colorSwatch3.color", + callback: "setColor", + callbackParameter: "colorSwatch3.color" + }, + { + id: "colorSwatch4", + type: "button", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.01, y: 0.045, z: -0.005 }, + color: { red: 255, green: 255, blue: 255 } + }, + command: "setCurrentColor", + commandParameter: "colorSwatch4.color", + callback: "setColor", + callbackParameter: "colorSwatch4.color" + }, + { + id: "currentColor", + type: "circle", + properties: { + localPosition: { x: 0.025, y: 0.0325, z: -0.007 }, + color: { red: 128, green: 128, blue: 128 } + } } ] }, @@ -231,8 +310,12 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { optionsItems, intersectionOverlays, + intersectionOverlaysIDs, + intersectionCommands, + intersectionCommandsParameters, intersectionCallbacks, - intersectionCallbacksEnabled, + intersectionCallbacksParameters, + intersectionEnabled, intersectionProperties, highlightedItem, highlightedSource, @@ -278,11 +361,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Close current panel, if any. for (i = 0, length = optionsOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(optionsOverlays[i]); - optionsOverlays = []; - optionsCallbacks = []; - optionsEnabled = []; - optionsItems = null; } + optionsOverlays = []; + optionsOverlaysIDs = []; + optionsCommands = []; + optionsCommandsParameters = []; + optionsCallbacks = []; + optionsCallbacksParameters = []; + optionsEnabled = []; + optionsItems = null; // Open specified panel, if any. if (toolOptions) { @@ -293,6 +380,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties = Object.merge(properties, optionsItems[i].properties); properties.parentID = parentID; optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); + optionsOverlaysIDs.push(optionsItems[i].id); if (optionsItems[i].label) { properties = Object.clone(UI_ELEMENTS.label.properties); properties.text = optionsItems[i].label; @@ -300,7 +388,10 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. + optionsCommands.push(optionsItems[i].command); + optionsCommandsParameters.push(optionsItems[i].commandParameter); optionsCallbacks.push(optionsItems[i].callback); + optionsCallbacksParameters.push(optionsItems[i].callbackParameter); optionsEnabled.push(true); } } @@ -316,10 +407,37 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { openOptions(); } + function evaluateParameter(parameter) { + var parameters, + overlayID, + overlayProperty; + + parameters = parameter.split("."); + overlayID = parameters[0]; + overlayProperty = parameters[1]; + + return Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf(overlayID)], overlayProperty); + } + + function peformCommand(command, parameter) { + switch (command) { + case "setCurrentColor": + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { + color: parameter + }); + break; + default: + // TODO: Log error. + } + + } + function update(intersectionOverlayID, groupsCount, entitiesCount) { var intersectedItem, parentProperties, BUTTON_PRESS_DELTA = 0.004, + commandParameter, + callbackParameter, enableGroupButton, enableUngroupButton; @@ -328,15 +446,23 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { intersectedItem = menuOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { intersectionOverlays = menuOverlays; + intersectionOverlaysIDs = null; + intersectionCommands = null; + intersectionCommandsParameters = null; intersectionCallbacks = menuCallbacks; - intersectionCallbacksEnabled = null; + intersectionCallbacksParameters = null; + intersectionEnabled = null; intersectionProperties = MENU_ITEMS; } else { intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { intersectionOverlays = optionsOverlays; + intersectionOverlaysIDs = optionsOverlaysIDs; + intersectionCommands = optionsCommands; + intersectionCommandsParameters = optionsCommandsParameters; intersectionCallbacks = optionsCallbacks; - intersectionCallbacksEnabled = optionsEnabled; + intersectionCallbacksParameters = optionsCallbacksParameters; + intersectionEnabled = optionsEnabled; intersectionProperties = optionsItems; } } @@ -387,7 +513,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { pressedItem = NONE; } isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - if (isButtonPressed && (intersectionCallbacksEnabled === null || intersectionCallbacksEnabled[intersectedItem])) { + if (isButtonPressed && (intersectionEnabled === null || intersectionEnabled[intersectedItem])) { // Press new button. Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(intersectionProperties[intersectedItem].properties.localPosition, @@ -400,7 +526,18 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { if (intersectionOverlays === menuOverlays) { openOptions(intersectionProperties[intersectedItem].toolOptions); } - commandCallback(intersectionCallbacks[intersectedItem]); + if (intersectionCommands && intersectionCommands[intersectedItem]) { + if (intersectionCommandsParameters && intersectionCommandsParameters[intersectedItem]) { + commandParameter = evaluateParameter(intersectionCommandsParameters[intersectedItem]); + } + peformCommand(intersectionCommands[intersectedItem], commandParameter); + } + if (intersectionCallbacks[intersectedItem]) { + if (intersectionCallbacksParameters && intersectionCallbacksParameters[intersectedItem]) { + callbackParameter = evaluateParameter(intersectionCallbacksParameters[intersectedItem]); + } + commandCallback(intersectionCallbacks[intersectedItem], callbackParameter); + } } } @@ -487,8 +624,12 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Initial values. optionsItems = null; intersectionOverlays = null; + intersectionOverlaysIDs = null; + intersectionCommands = null; + intersectionCommandsParameters = null; intersectionCallbacks = null; - intersectionCallbacksEnabled = null; + intersectionCallbacksParameters = null; + intersectionEnabled = null; intersectionProperties = null; highlightedItem = NONE; highlightedSource = null; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7d45fd4dc4..f0f828398d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1235,7 +1235,7 @@ Settings.setValue(VR_EDIT_SETTING, isAppActive); } - function onUICommand(command) { + function onUICommand(command, parameter) { switch (command) { case "scaleTool": grouping.clear(); From 75b481adab869529cbc54037a8e8bd9927d80cd4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 17:52:53 +1200 Subject: [PATCH 138/504] Update icon color as current color is changed --- scripts/vr-edit/modules/toolIcon.js | 5 +++++ scripts/vr-edit/vr-edit.js | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 3b67d7790b..41a6294820 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -90,6 +90,10 @@ ToolIcon = function (side) { } } + function setColor(color) { + Overlays.editOverlay(iconOverlay, { color: color }); + } + function clear() { // Deletes current icon. if (iconOverlay) { @@ -111,6 +115,7 @@ ToolIcon = function (side) { setHand: setHand, update: update, display: display, + setColor: setColor, clear: clear, destroy: destroy }; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index f0f828398d..7af8be3fb6 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -212,6 +212,10 @@ toolIcon.display(icon); } + function setToolColor(color) { + toolIcon.setColor(color); + } + function clearToolIcon() { toolIcon.clear(); toolMenu.clearTool(); @@ -266,6 +270,7 @@ return { setHand: setHand, setToolIcon: setToolIcon, + setToolColor: setToolColor, clearToolIcon: clearToolIcon, SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, @@ -1271,6 +1276,9 @@ case "ungroupButton": grouping.ungroup(); break; + case "setColor": + ui.setToolColor(parameter); + break; default: debug("ERROR: Unexpected command in onUICommand()!"); } From ceba5769e0e7c001620181ad170a4938f3919ebe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 4 Aug 2017 18:07:42 +1200 Subject: [PATCH 139/504] Apply color to entities when click them --- scripts/vr-edit/modules/selection.js | 10 ++++++++++ scripts/vr-edit/vr-edit.js | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 89e5221f81..7234553a9d 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -327,6 +327,15 @@ Selection = function (side) { rootEntityID = selection[0].id; } + function applyColor(color) { + // Entities without a color property simply ignore the edit. + for (i = 0, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, { + color: color + }); + } + } + function clear() { selection = []; selectedEntityID = null; @@ -361,6 +370,7 @@ Selection = function (side) { finishHandleScaling: finishHandleScaling, finishEditing: finishEditing, cloneEntities: cloneEntities, + applyColor: applyColor, deleteEntities: deleteEntities, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7af8be3fb6..72232d6101 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -29,6 +29,7 @@ TOOL_COLOR = 4, TOOL_DELETE = 5, toolSelected = TOOL_NONE, + colorToolColor = { red: 128, green: 128, blue: 128 }, // Primary objects Inputs, @@ -854,8 +855,7 @@ } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { - // TODO - print("$$$$$$$ apply color"); + selection.applyColor(colorToolColor); } else if (toolSelected === TOOL_DELETE) { setState(EDITOR_HIGHLIGHTING); selection.deleteEntities(); @@ -912,8 +912,7 @@ } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { - // TODO - print("$$$$$$$ apply color"); + selection.applyColor(colorToolColor); } else if (toolSelected === TOOL_DELETE) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -1278,6 +1277,7 @@ break; case "setColor": ui.setToolColor(parameter); + colorToolColor = parameter; break; default: debug("ERROR: Unexpected command in onUICommand()!"); From 24f36c1ae50bfe750534b18c327885caff9c07d7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 12:07:33 +1200 Subject: [PATCH 140/504] Default current color and remember current color over tool/app toggles --- scripts/vr-edit/modules/toolMenu.js | 31 ++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 7a17a0fede..c241ad9740 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -31,6 +31,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { optionsCallbacksParameters = [], optionsEnabled = [], + optionsSettings = {}, + highlightOverlay, LEFT_HAND = 0, @@ -217,8 +219,13 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { id: "currentColor", type: "circle", properties: { - localPosition: { x: 0.025, y: 0.0325, z: -0.007 }, - color: { red: 128, green: 128, blue: 128 } + localPosition: { x: 0.025, y: 0.0325, z: -0.007 } + }, + setting: { + key: "VREdit.colorTool.currentColor", + property: "color", + defaultValue: { red: 128, green: 128, blue: 128 }, + callback: "setColor" } } ] @@ -355,6 +362,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { function openOptions(toolOptions) { var properties, parentID, + value, i, length; @@ -379,6 +387,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); properties = Object.merge(properties, optionsItems[i].properties); properties.parentID = parentID; + if (optionsItems[i].setting) { + optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; + value = Settings.getValue(optionsItems[i].setting.key); + if (value === "") { + value = optionsItems[i].setting.defaultValue; + } + properties[optionsItems[i].setting.property] = value; + commandCallback(optionsItems[i].setting.callback, value); // Apply setting. + } optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); optionsOverlaysIDs.push(optionsItems[i].id); if (optionsItems[i].label) { @@ -425,6 +442,9 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { color: parameter }); + if (optionsSettings.currentColor) { + Settings.setValue(optionsSettings.currentColor.key, parameter); + } break; default: // TODO: Log error. @@ -523,9 +543,6 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { pressedSource = intersectionOverlays; // Button press actions. - if (intersectionOverlays === menuOverlays) { - openOptions(intersectionProperties[intersectedItem].toolOptions); - } if (intersectionCommands && intersectionCommands[intersectedItem]) { if (intersectionCommandsParameters && intersectionCommandsParameters[intersectedItem]) { commandParameter = evaluateParameter(intersectionCommandsParameters[intersectedItem]); @@ -538,6 +555,10 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } commandCallback(intersectionCallbacks[intersectedItem], callbackParameter); } + // Open options panel after changing tool so that options values can be applied to the tool. + if (intersectionOverlays === menuOverlays) { + openOptions(intersectionProperties[intersectedItem].toolOptions); + } } } From 23fab65f2759701ce81e0d2c2f5f3a8c92cae646 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 12:08:36 +1200 Subject: [PATCH 141/504] Tidying --- scripts/vr-edit/modules/selection.js | 3 +++ scripts/vr-edit/vr-edit.js | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 7234553a9d..5425fb7153 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -329,6 +329,9 @@ Selection = function (side) { function applyColor(color) { // Entities without a color property simply ignore the edit. + var i, + length; + for (i = 0, length = selection.length; i < length; i += 1) { Entities.editEntity(selection[i].id, { color: color diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 72232d6101..4c26f46d0d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -217,7 +217,7 @@ toolIcon.setColor(color); } - function clearToolIcon() { + function clearTool() { toolIcon.clear(); toolMenu.clearTool(); } @@ -272,7 +272,7 @@ setHand: setHand, setToolIcon: setToolIcon, setToolColor: setToolColor, - clearToolIcon: clearToolIcon, + clearTool: clearTool, SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, GROUP_TOOL: toolIcon.GROUP_TOOL, @@ -803,7 +803,7 @@ if (!wasGripClicked && isGripClicked && (toolSelected !== TOOL_NONE)) { toolSelected = TOOL_NONE; grouping.clear(); - ui.clearToolIcon(); + ui.clearTool(); } } From e6456ca5010f1f5724fd7c7a38924a04e58f016a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 17:08:56 +1200 Subject: [PATCH 142/504] Tidy and fix options handling --- scripts/vr-edit/modules/toolMenu.js | 108 ++++++++++------------------ scripts/vr-edit/vr-edit.js | 2 + 2 files changed, 38 insertions(+), 72 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c241ad9740..a915fd6093 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,7 +10,7 @@ /* global ToolMenu */ -ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { +ToolMenu = function (side, leftInputs, rightInputs, doCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -21,16 +21,10 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { menuPanelOverlay, menuOverlays = [], - menuCallbacks = [], optionsOverlays = [], - optionsOverlaysIDs = [], - optionsCommands = [], - optionsCommandsParameters = [], - optionsCallbacks = [], - optionsCallbacksParameters = [], + optionsOverlaysIDs = [], // Text ids (names) of options overlays. optionsEnabled = [], - optionsSettings = {}, highlightOverlay, @@ -224,8 +218,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { setting: { key: "VREdit.colorTool.currentColor", property: "color", - defaultValue: { red: 128, green: 128, blue: 128 }, - callback: "setColor" + defaultValue: { red: 128, green: 128, blue: 128 } } } ] @@ -236,7 +229,6 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { MENU_ITEMS = [ { - // Background element id: "toolsMenuPanel", type: "panel", properties: { @@ -283,7 +275,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { }, label: " COLOR", toolOptions: "colorOptions", - callback: "colorTool" + callback: "colorTool", + callbackParameter: "currentColor.color" }, { id: "deleteButton", @@ -317,13 +310,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { optionsItems, intersectionOverlays, - intersectionOverlaysIDs, - intersectionCommands, - intersectionCommandsParameters, - intersectionCallbacks, - intersectionCallbacksParameters, intersectionEnabled, - intersectionProperties, highlightedItem, highlightedSource, isHighlightingButton, @@ -371,11 +358,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.deleteOverlay(optionsOverlays[i]); } optionsOverlays = []; + optionsOverlaysIDs = []; - optionsCommands = []; - optionsCommandsParameters = []; - optionsCallbacks = []; - optionsCallbacksParameters = []; optionsEnabled = []; optionsItems = null; @@ -394,7 +378,9 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { value = optionsItems[i].setting.defaultValue; } properties[optionsItems[i].setting.property] = value; - commandCallback(optionsItems[i].setting.callback, value); // Apply setting. + if (optionsItems[i].setting.callback) { + doCallback(optionsItems[i].setting.callback, value); + } } optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); optionsOverlaysIDs.push(optionsItems[i].id); @@ -405,10 +391,6 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. - optionsCommands.push(optionsItems[i].command); - optionsCommandsParameters.push(optionsItems[i].commandParameter); - optionsCallbacks.push(optionsItems[i].callback); - optionsCallbacksParameters.push(optionsItems[i].callbackParameter); optionsEnabled.push(true); } } @@ -436,7 +418,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { return Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf(overlayID)], overlayProperty); } - function peformCommand(command, parameter) { + function doCommand(command, parameter) { switch (command) { case "setCurrentColor": Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { @@ -453,11 +435,11 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { } function update(intersectionOverlayID, groupsCount, entitiesCount) { - var intersectedItem, + var intersectedItem = -1, + intersectionItems, parentProperties, BUTTON_PRESS_DELTA = 0.004, - commandParameter, - callbackParameter, + parameterValue, enableGroupButton, enableUngroupButton; @@ -465,25 +447,15 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { if (intersectionOverlayID) { intersectedItem = menuOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { + intersectionItems = MENU_ITEMS; intersectionOverlays = menuOverlays; - intersectionOverlaysIDs = null; - intersectionCommands = null; - intersectionCommandsParameters = null; - intersectionCallbacks = menuCallbacks; - intersectionCallbacksParameters = null; intersectionEnabled = null; - intersectionProperties = MENU_ITEMS; } else { intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); if (intersectedItem !== -1) { + intersectionItems = optionsItems; intersectionOverlays = optionsOverlays; - intersectionOverlaysIDs = optionsOverlaysIDs; - intersectionCommands = optionsCommands; - intersectionCommandsParameters = optionsCommandsParameters; - intersectionCallbacks = optionsCallbacks; - intersectionCallbacksParameters = optionsCallbacksParameters; intersectionEnabled = optionsEnabled; - intersectionProperties = optionsItems; } } } @@ -493,8 +465,8 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { - if (intersectedItem !== -1 && intersectionCallbacks[intersectedItem] !== undefined) { - // Highlight new button. + if (intersectedItem !== -1 && intersectionItems[intersectedItem].callback !== undefined) { + // Highlight new button. (The existence of a callback infers that the item is a button.) parentProperties = Overlays.getProperties(intersectionOverlays[intersectedItem], ["dimensions", "localPosition"]); Overlays.editOverlay(highlightOverlay, { @@ -527,37 +499,38 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { || controlHand.triggerClicked() !== isButtonPressed) { if (pressedItem !== NONE) { // Unpress previous button. - Overlays.editOverlay(intersectionOverlays[pressedItem], { - localPosition: intersectionProperties[pressedItem].properties.localPosition - }); + if (intersectionItems) { + Overlays.editOverlay(intersectionOverlays[pressedItem], { + localPosition: intersectionItems[pressedItem].properties.localPosition + }); + } pressedItem = NONE; } isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); if (isButtonPressed && (intersectionEnabled === null || intersectionEnabled[intersectedItem])) { // Press new button. Overlays.editOverlay(intersectionOverlays[intersectedItem], { - localPosition: Vec3.sum(intersectionProperties[intersectedItem].properties.localPosition, + localPosition: Vec3.sum(intersectionItems[intersectedItem].properties.localPosition, { x: 0, y: 0, z: BUTTON_PRESS_DELTA }) }); pressedItem = intersectedItem; pressedSource = intersectionOverlays; // Button press actions. - if (intersectionCommands && intersectionCommands[intersectedItem]) { - if (intersectionCommandsParameters && intersectionCommandsParameters[intersectedItem]) { - commandParameter = evaluateParameter(intersectionCommandsParameters[intersectedItem]); - } - peformCommand(intersectionCommands[intersectedItem], commandParameter); - } - if (intersectionCallbacks[intersectedItem]) { - if (intersectionCallbacksParameters && intersectionCallbacksParameters[intersectedItem]) { - callbackParameter = evaluateParameter(intersectionCallbacksParameters[intersectedItem]); - } - commandCallback(intersectionCallbacks[intersectedItem], callbackParameter); - } - // Open options panel after changing tool so that options values can be applied to the tool. if (intersectionOverlays === menuOverlays) { - openOptions(intersectionProperties[intersectedItem].toolOptions); + openOptions(intersectionItems[intersectedItem].toolOptions); + } + if (intersectionItems[intersectedItem].command) { + if (intersectionItems[intersectedItem].callbackParameter) { + parameterValue = evaluateParameter(intersectionItems[intersectedItem].commandParameter); + } + doCommand(intersectionItems[intersectedItem].command, parameterValue); + } + if (intersectionItems[intersectedItem].callback) { + if (intersectionItems[intersectedItem].callbackParameter) { + parameterValue = evaluateParameter(intersectionItems[intersectedItem].callbackParameter); + } + doCallback(intersectionItems[intersectedItem].callback, parameterValue); } } } @@ -634,7 +607,6 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } parentID = menuOverlays[0]; // Menu buttons parent to menu panel. - menuCallbacks.push(MENU_ITEMS[i].callback); } // Prepare highlight overlay. @@ -645,13 +617,7 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { // Initial values. optionsItems = null; intersectionOverlays = null; - intersectionOverlaysIDs = null; - intersectionCommands = null; - intersectionCommandsParameters = null; - intersectionCallbacks = null; - intersectionCallbacksParameters = null; intersectionEnabled = null; - intersectionProperties = null; highlightedItem = NONE; highlightedSource = null; isHighlightingButton = false; @@ -678,13 +644,11 @@ ToolMenu = function (side, leftInputs, rightInputs, commandCallback) { Overlays.deleteOverlay(optionsOverlays[i]); } optionsOverlays = []; - optionsCallbacks = []; for (i = 0, length = menuOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(menuOverlays[i]); } menuOverlays = []; - menuCallbacks = []; Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4c26f46d0d..745cb15d04 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1261,6 +1261,8 @@ case "colorTool": toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); + ui.setToolColor(parameter); + colorToolColor = parameter; ui.updateUIEntities(); break; case "deleteTool": From bff3ad342da01a4ddc608db4b72af9848eeda8de Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 17:37:40 +1200 Subject: [PATCH 143/504] Tidy command and callback handling --- scripts/vr-edit/modules/toolMenu.js | 92 +++++++++++++++++++---------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index a915fd6093..bfde776509 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -134,7 +134,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { }, label: " GROUP", enabledColor: { red: 64, green: 240, blue: 64 }, - callback: "groupButton" + callback: { + method: "groupButton" + } }, { id: "ungroupButton", @@ -146,7 +148,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { }, label: "UNGROUP", enabledColor: { red: 240, green: 64, blue: 64 }, - callback: "ungroupButton" + callback: { + method: "ungroupButton" + } } ], colorOptions: [ @@ -165,10 +169,14 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { localPosition: { x: -0.035, y: 0.02, z: -0.005 }, color: { red: 255, green: 0, blue: 0 } }, - command: "setCurrentColor", - commandParameter: "colorSwatch1.color", - callback: "setColor", - callbackParameter: "colorSwatch1.color" + command: { + method: "setCurrentColor", + parameter: "colorSwatch1.color" + }, + callback: { + method: "setColor", + parameter: "colorSwatch1.color" + } }, { id: "colorSwatch2", @@ -178,10 +186,14 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { localPosition: { x: -0.01, y: 0.02, z: -0.005 }, color: { red: 0, green: 255, blue: 0 } }, - command: "setCurrentColor", - commandParameter: "colorSwatch2.color", - callback: "setColor", - callbackParameter: "colorSwatch2.color" + command: { + method: "setCurrentColor", + parameter: "colorSwatch2.color" + }, + callback: { + method: "setColor", + parameter: "colorSwatch2.color" + } }, { id: "colorSwatch3", @@ -191,10 +203,14 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { localPosition: { x: -0.035, y: 0.045, z: -0.005 }, color: { red: 0, green: 0, blue: 255 } }, - command: "setCurrentColor", - commandParameter: "colorSwatch3.color", - callback: "setColor", - callbackParameter: "colorSwatch3.color" + command: { + method: "setCurrentColor", + parameter: "colorSwatch3.color" + }, + callback: { + method: "setColor", + parameter: "colorSwatch3.color" + } }, { id: "colorSwatch4", @@ -204,10 +220,14 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { localPosition: { x: -0.01, y: 0.045, z: -0.005 }, color: { red: 255, green: 255, blue: 255 } }, - command: "setCurrentColor", - commandParameter: "colorSwatch4.color", - callback: "setColor", - callbackParameter: "colorSwatch4.color" + command: { + method: "setCurrentColor", + parameter: "colorSwatch4.color" + }, + callback: { + method: "setColor", + parameter: "colorSwatch4.color" + } }, { id: "currentColor", @@ -243,7 +263,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { color: { red: 0, green: 240, blue: 240 } }, label: " SCALE", - callback: "scaleTool" + callback: { + method: "scaleTool" + } }, { id: "cloneButton", @@ -253,7 +275,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { color: { red: 240, green: 240, blue: 0 } }, label: " CLONE", - callback: "cloneTool" + callback: { + method: "cloneTool" + } }, { id: "groupButton", @@ -264,7 +288,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { }, label: " GROUP", toolOptions: "groupOptions", - callback: "groupTool" + callback: { + method: "groupTool" + } }, { id: "colorButton", @@ -275,8 +301,10 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { }, label: " COLOR", toolOptions: "colorOptions", - callback: "colorTool", - callbackParameter: "currentColor.color" + callback: { + method: "colorTool", + parameter: "currentColor.color" + } }, { id: "deleteButton", @@ -286,7 +314,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { color: { red: 240, green: 60, blue: 60 } }, label: " DELETE", - callback: "deleteTool" + callback: { + method: "deleteTool" + } } ], @@ -379,7 +409,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } properties[optionsItems[i].setting.property] = value; if (optionsItems[i].setting.callback) { - doCallback(optionsItems[i].setting.callback, value); + doCallback(optionsItems[i].setting.callback.method, value); } } optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); @@ -521,16 +551,16 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { openOptions(intersectionItems[intersectedItem].toolOptions); } if (intersectionItems[intersectedItem].command) { - if (intersectionItems[intersectedItem].callbackParameter) { - parameterValue = evaluateParameter(intersectionItems[intersectedItem].commandParameter); + if (intersectionItems[intersectedItem].command.parameter) { + parameterValue = evaluateParameter(intersectionItems[intersectedItem].command.parameter); } - doCommand(intersectionItems[intersectedItem].command, parameterValue); + doCommand(intersectionItems[intersectedItem].command.method, parameterValue); } if (intersectionItems[intersectedItem].callback) { - if (intersectionItems[intersectedItem].callbackParameter) { - parameterValue = evaluateParameter(intersectionItems[intersectedItem].callbackParameter); + if (intersectionItems[intersectedItem].callback.parameter) { + parameterValue = evaluateParameter(intersectionItems[intersectedItem].callback.parameter); } - doCallback(intersectionItems[intersectedItem].callback, parameterValue); + doCallback(intersectionItems[intersectedItem].callback.method, parameterValue); } } } From 4e87c1302023d6c6e401dbb0294b3cd0fd4f588b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 17:52:54 +1200 Subject: [PATCH 144/504] Tidy group buttons' enabling --- scripts/vr-edit/modules/toolMenu.js | 38 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index bfde776509..ea07ea52c4 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -244,9 +244,6 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { ] }, - GROUP_BUTTON_INDEX = 1, - UNGROUP_BUTTON_INDEX = 2, - MENU_ITEMS = [ { id: "toolsMenuPanel", @@ -347,8 +344,11 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { pressedItem, pressedSource, isButtonPressed, + isGroupButtonEnabled, isUngroupButtonEnabled, + groupButtonIndex, + ungroupButtonIndex, isDisplaying = false, @@ -427,8 +427,8 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { // Special handling for Group options. if (toolOptions === "groupOptions") { - optionsEnabled[GROUP_BUTTON_INDEX] = false; - optionsEnabled[UNGROUP_BUTTON_INDEX] = false; + optionsEnabled[groupButtonIndex] = false; + optionsEnabled[ungroupButtonIndex] = false; } } @@ -570,23 +570,23 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { enableGroupButton = groupsCount > 1; if (enableGroupButton !== isGroupButtonEnabled) { isGroupButtonEnabled = enableGroupButton; - Overlays.editOverlay(optionsOverlays[GROUP_BUTTON_INDEX], { + Overlays.editOverlay(optionsOverlays[groupButtonIndex], { color: isGroupButtonEnabled - ? OPTONS_PANELS.groupOptions[GROUP_BUTTON_INDEX].enabledColor - : OPTONS_PANELS.groupOptions[GROUP_BUTTON_INDEX].properties.color + ? OPTONS_PANELS.groupOptions[groupButtonIndex].enabledColor + : OPTONS_PANELS.groupOptions[groupButtonIndex].properties.color }); - optionsEnabled[GROUP_BUTTON_INDEX] = enableGroupButton; + optionsEnabled[groupButtonIndex] = enableGroupButton; } enableUngroupButton = groupsCount === 1 && entitiesCount > 1; if (enableUngroupButton !== isUngroupButtonEnabled) { isUngroupButtonEnabled = enableUngroupButton; - Overlays.editOverlay(optionsOverlays[UNGROUP_BUTTON_INDEX], { + Overlays.editOverlay(optionsOverlays[ungroupButtonIndex], { color: isUngroupButtonEnabled - ? OPTONS_PANELS.groupOptions[UNGROUP_BUTTON_INDEX].enabledColor - : OPTONS_PANELS.groupOptions[UNGROUP_BUTTON_INDEX].properties.color + ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].enabledColor + : OPTONS_PANELS.groupOptions[ungroupButtonIndex].properties.color }); - optionsEnabled[UNGROUP_BUTTON_INDEX] = enableUngroupButton; + optionsEnabled[ungroupButtonIndex] = enableUngroupButton; } } } @@ -596,6 +596,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { var handJointIndex, properties, parentID, + id, i, length; @@ -657,6 +658,17 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { isGroupButtonEnabled = false; isUngroupButtonEnabled = false; + // Special handling for Group options. + for (i = 0, length = OPTONS_PANELS.groupOptions.length; i < length; i += 1) { + id = OPTONS_PANELS.groupOptions[i].id; + if (id === "groupButton") { + groupButtonIndex = i; + } + if (id === "ungroupButton") { + ungroupButtonIndex = i; + } + } + isDisplaying = true; } From 0f44e36128685b0968cff16085d189f4c3a2abdc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 5 Aug 2017 18:04:39 +1200 Subject: [PATCH 145/504] Fix buttons sometimes staying pressed --- scripts/vr-edit/modules/toolMenu.js | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index ea07ea52c4..77d52ec7bb 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -341,7 +341,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { highlightedItem, highlightedSource, isHighlightingButton, - pressedItem, + pressedItem = null, pressedSource, isButtonPressed, @@ -468,6 +468,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { var intersectedItem = -1, intersectionItems, parentProperties, + localPosition, BUTTON_PRESS_DELTA = 0.004, parameterValue, enableGroupButton, @@ -524,27 +525,28 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { highlightedSource = intersectionOverlays; } - // Press button. - if (intersectedItem !== pressedItem || intersectionOverlays !== pressedSource + // Press/unpress button. + if ((pressedItem && intersectedItem !== pressedItem.index) || intersectionOverlays !== pressedSource || controlHand.triggerClicked() !== isButtonPressed) { - if (pressedItem !== NONE) { + if (pressedItem) { // Unpress previous button. - if (intersectionItems) { - Overlays.editOverlay(intersectionOverlays[pressedItem], { - localPosition: intersectionItems[pressedItem].properties.localPosition - }); - } - pressedItem = NONE; + Overlays.editOverlay(intersectionOverlays[pressedItem.index], { + localPosition: pressedItem.localPosition + }); + pressedItem = null; } isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); if (isButtonPressed && (intersectionEnabled === null || intersectionEnabled[intersectedItem])) { // Press new button. + localPosition = intersectionItems[intersectedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[intersectedItem], { - localPosition: Vec3.sum(intersectionItems[intersectedItem].properties.localPosition, - { x: 0, y: 0, z: BUTTON_PRESS_DELTA }) + localPosition: Vec3.sum(localPosition, { x: 0, y: 0, z: BUTTON_PRESS_DELTA }) }); - pressedItem = intersectedItem; pressedSource = intersectionOverlays; + pressedItem = { + index: intersectedItem, + localPosition: localPosition + }; // Button press actions. if (intersectionOverlays === menuOverlays) { @@ -652,7 +654,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { highlightedItem = NONE; highlightedSource = null; isHighlightingButton = false; - pressedItem = NONE; + pressedItem = null; pressedSource = null; isButtonPressed = false; isGroupButtonEnabled = false; From 1231491a722d218549ae5ee78c758e36badaa5ae Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 7 Aug 2017 17:29:58 +1200 Subject: [PATCH 146/504] Add color picker --- scripts/vr-edit/modules/selection.js | 37 +++++++++++++++++++++---- scripts/vr-edit/modules/toolIcon.js | 5 +++- scripts/vr-edit/modules/toolMenu.js | 20 ++++++++++++-- scripts/vr-edit/vr-edit.js | 41 ++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 5425fb7153..9836a1fed6 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -23,7 +23,8 @@ Selection = function (side) { scaleCenter, scaleRootOffset, scaleRootOrientation, - ENTITY_TYPE = "entity"; + ENTITY_TYPE = "entity", + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"]; if (!this instanceof Selection) { return new Selection(side); @@ -329,14 +330,39 @@ Selection = function (side) { function applyColor(color) { // Entities without a color property simply ignore the edit. - var i, + var properties, + isError = true, + i, length; for (i = 0, length = selection.length; i < length; i += 1) { - Entities.editEntity(selection[i].id, { - color: color - }); + properties = Entities.getEntityProperties(selection[i].id, "color"); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { + Entities.editEntity(selection[i].id, { + color: color + }); + isError = false; + } } + + if (isError) { + // TODO + print("TODO: Error beep"); + } + } + + function getColor(entityID) { + var properties; + + properties = Entities.getEntityProperties(entityID, "color"); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) === -1) { + // Some entities don't have a color property. + // TODO + print("TODO: Error beep"); + return null; + } + + return properties.color; } function clear() { @@ -374,6 +400,7 @@ Selection = function (side) { finishEditing: finishEditing, cloneEntities: cloneEntities, applyColor: applyColor, + getColor: getColor, deleteEntities: deleteEntities, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 41a6294820..a223fdb923 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -19,13 +19,15 @@ ToolIcon = function (side) { CLONE_TOOL = 1, GROUP_TOOL = 2, COLOR_TOOL = 3, - DELETE_TOOL = 4, + PICK_COLOR_TOOL = 4, + DELETE_TOOL = 5, ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, { red: 240, green: 240, blue: 0 }, { red: 220, green: 60, blue: 220 }, { red: 220, green: 220, blue: 220 }, + { red: 0, green: 0, blue: 0 }, { red: 240, green: 60, blue: 60 } ], @@ -111,6 +113,7 @@ ToolIcon = function (side) { CLONE_TOOL: CLONE_TOOL, GROUP_TOOL: GROUP_TOOL, COLOR_TOOL: COLOR_TOOL, + PICK_COLOR_TOOL: PICK_COLOR_TOOL, DELETE_TOOL: DELETE_TOOL, setHand: setHand, update: update, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 77d52ec7bb..322e75801b 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -103,7 +103,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { "circle": { overlay: "circle3d", properties: { - size: 0.015, + size: 0.01, localPosition: { x: 0.0, y: 0.0, z: -0.01 }, localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), color: { red: 128, green: 128, blue: 128 }, @@ -233,13 +233,26 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { id: "currentColor", type: "circle", properties: { - localPosition: { x: 0.025, y: 0.0325, z: -0.007 } + localPosition: { x: 0.025, y: 0.02, z: -0.007 } }, setting: { key: "VREdit.colorTool.currentColor", property: "color", defaultValue: { red: 128, green: 128, blue: 128 } } + }, + { + id: "pickColor", + type: "button", + properties: { + dimensions: { x: 0.04, y: 0.02, z: 0.01 }, + localPosition: { x: 0.025, y: 0.045, z: -0.005 }, + color: { red: 255, green: 255, blue: 255 } + }, + label: " PICK", + callback: { + method: "pickColorTool" + } } ] }, @@ -662,7 +675,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { // Special handling for Group options. for (i = 0, length = OPTONS_PANELS.groupOptions.length; i < length; i += 1) { - id = OPTONS_PANELS.groupOptions[i].id; + id = OPTONS_PANELS.groupOptions[i].id; if (id === "groupButton") { groupButtonIndex = i; } @@ -708,6 +721,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { setHand: setHand, entityIDs: getEntityIDs, clearTool: clearTool, + doCommand: doCommand, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 745cb15d04..34cf042398 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -27,7 +27,8 @@ TOOL_CLONE = 2, TOOL_GROUP = 3, TOOL_COLOR = 4, - TOOL_DELETE = 5, + TOOL_PICK_COLOR = 5, + TOOL_DELETE = 6, toolSelected = TOOL_NONE, colorToolColor = { red: 128, green: 128, blue: 128 }, @@ -243,6 +244,10 @@ } } + function doPickColor(color) { + toolMenu.doCommand("setCurrentColor", color); + } + function clear() { leftInputs.setUIEntities([]); rightInputs.setUIEntities([]); @@ -277,9 +282,11 @@ CLONE_TOOL: toolIcon.CLONE_TOOL, GROUP_TOOL: toolIcon.GROUP_TOOL, COLOR_TOOL: toolIcon.COLOR_TOOL, + PICK_COLOR_TOOL: toolIcon.PICK_COLOR_TOOL, DELETE_TOOL: toolIcon.DELETE_TOOL, display: display, updateUIEntities: setUIEntities, + doPickColor: doPickColor, update: update, clear: clear, destroy: destroy @@ -810,7 +817,8 @@ function update() { var previousState = editorState, - doUpdateState; + doUpdateState, + color; intersection = getIntersection(); isTriggerClicked = hand.triggerClicked(); @@ -856,6 +864,16 @@ setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { selection.applyColor(colorToolColor); + } else if (toolSelected === TOOL_PICK_COLOR) { + color = selection.getColor(intersection.entityID); + if (color) { + colorToolColor = color; + ui.doPickColor(colorToolColor); + ui.setToolColor(colorToolColor); + } + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_DELETE) { setState(EDITOR_HIGHLIGHTING); selection.deleteEntities(); @@ -913,6 +931,16 @@ setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { selection.applyColor(colorToolColor); + } else if (toolSelected === TOOL_PICK_COLOR) { + color = selection.getColor(intersection.entityID); + if (color) { + colorToolColor = color; + ui.doPickColor(colorToolColor); + ui.setToolColor(colorToolColor); + } + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_DELETE) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -1265,6 +1293,11 @@ colorToolColor = parameter; ui.updateUIEntities(); break; + case "pickColorTool": + toolSelected = TOOL_PICK_COLOR; + ui.setToolIcon(ui.PICK_COLOR_TOOL); + ui.updateUIEntities(); + break; case "deleteTool": grouping.clear(); toolSelected = TOOL_DELETE; @@ -1278,6 +1311,10 @@ grouping.ungroup(); break; case "setColor": + if (toolSelected === TOOL_PICK_COLOR) { + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + } ui.setToolColor(parameter); colorToolColor = parameter; break; From 6621b43fae91a2a12b7d30e341ff32d370eb8d82 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Aug 2017 16:34:45 +1200 Subject: [PATCH 147/504] Make some swatches start with "no color"; color them when clicked --- scripts/vr-edit/modules/toolMenu.js | 94 ++++++++++++++++++----------- scripts/vr-edit/vr-edit.js | 2 +- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 322e75801b..3385d1fd36 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -82,6 +82,17 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { visible: true } }, + "swatch": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + localRotation: Quat.ZERO, + alpha: 1.0, + solid: false, // False indicates "no color assigned" + ignoreRayIntersection: false, + visible: true + } + }, "label": { overlay: "text3d", properties: { @@ -163,69 +174,57 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { }, { id: "colorSwatch1", - type: "button", + type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.035, y: 0.02, z: -0.005 }, - color: { red: 255, green: 0, blue: 0 } + color: { red: 255, green: 0, blue: 0 }, + solid: true }, command: { - method: "setCurrentColor", - parameter: "colorSwatch1.color" - }, - callback: { - method: "setColor", + method: "setColorPerSwatch", parameter: "colorSwatch1.color" } }, { id: "colorSwatch2", - type: "button", + type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.01, y: 0.02, z: -0.005 }, - color: { red: 0, green: 255, blue: 0 } + color: { red: 0, green: 255, blue: 0 }, + solid: true }, command: { - method: "setCurrentColor", - parameter: "colorSwatch2.color" - }, - callback: { - method: "setColor", + method: "setColorPerSwatch", parameter: "colorSwatch2.color" } }, { id: "colorSwatch3", - type: "button", + type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.035, y: 0.045, z: -0.005 }, - color: { red: 0, green: 0, blue: 255 } + color: { red: 128, green: 128, blue: 128 }, + solid: false }, command: { - method: "setCurrentColor", - parameter: "colorSwatch3.color" - }, - callback: { - method: "setColor", + method: "setColorPerSwatch", parameter: "colorSwatch3.color" } }, { id: "colorSwatch4", - type: "button", + type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.01, y: 0.045, z: -0.005 }, - color: { red: 255, green: 255, blue: 255 } + color: { red: 128, green: 128, blue: 128 }, + solid: false }, command: { - method: "setCurrentColor", - parameter: "colorSwatch4.color" - }, - callback: { - method: "setColor", + method: "setColorPerSwatch", parameter: "colorSwatch4.color" } }, @@ -462,8 +461,34 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } function doCommand(command, parameter) { + var parameters, + hasColor, + value; + switch (command) { - case "setCurrentColor": + case "setColorPerSwatch": + parameters = parameter.split("."); + hasColor = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf(parameters[0])], "solid"); + if (hasColor) { + // Swatch has a color; set current fill color to swatch color. + value = evaluateParameter(parameter); + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { + color: value + }); + if (optionsSettings.currentColor) { + Settings.setValue(optionsSettings.currentColor.key, value); + } + doCallback("setColor", value); + } else { + // Swatch has no color; set swatch color to current fill color. + value = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], "color"); + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameters[0])], { + color: value, + solid: true + }); + } + break; + case "setColorFromPick": Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { color: parameter }); @@ -474,7 +499,6 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { default: // TODO: Log error. } - } function update(intersectionOverlayID, groupsCount, entitiesCount) { @@ -483,6 +507,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { parentProperties, localPosition, BUTTON_PRESS_DELTA = 0.004, + parameter, parameterValue, enableGroupButton, enableUngroupButton; @@ -509,8 +534,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { - if (intersectedItem !== -1 && intersectionItems[intersectedItem].callback !== undefined) { - // Highlight new button. (The existence of a callback infers that the item is a button.) + if (intersectedItem !== -1 && (intersectionItems[intersectedItem].command !== undefined + || intersectionItems[intersectedItem].callback !== undefined)) { + // Highlight new button. (The existence of a command or callback infers that the item is a button.) parentProperties = Overlays.getProperties(intersectionOverlays[intersectedItem], ["dimensions", "localPosition"]); Overlays.editOverlay(highlightOverlay, { @@ -567,9 +593,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } if (intersectionItems[intersectedItem].command) { if (intersectionItems[intersectedItem].command.parameter) { - parameterValue = evaluateParameter(intersectionItems[intersectedItem].command.parameter); + parameter = intersectionItems[intersectedItem].command.parameter; } - doCommand(intersectionItems[intersectedItem].command.method, parameterValue); + doCommand(intersectionItems[intersectedItem].command.method, parameter); } if (intersectionItems[intersectedItem].callback) { if (intersectionItems[intersectedItem].callback.parameter) { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 34cf042398..576ae44093 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -245,7 +245,7 @@ } function doPickColor(color) { - toolMenu.doCommand("setCurrentColor", color); + toolMenu.doCommand("setColorFromPick", color); } function clear() { From 0b8ea29193b1a329c9f392635dfd4d4a1ac1650f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Aug 2017 20:37:41 +1200 Subject: [PATCH 148/504] Clear swatch with grip click --- scripts/vr-edit/modules/hand.js | 13 +++++++ scripts/vr-edit/modules/toolMenu.js | 56 +++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 42d6751ed1..14994c226d 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -21,6 +21,7 @@ Hand = function (side) { controllerGrip, isGripClicked = false, + isGripClickedHandled = false, GRIP_ON_VALUE = 0.99, GRIP_OFF_VALUE = 0.95, @@ -87,6 +88,11 @@ Hand = function (side) { return isGripClicked; } + function setGripClickedHandled() { + isGripClicked = false; + isGripClickedHandled = true; + } + function getIntersection() { return intersection; } @@ -126,6 +132,12 @@ Hand = function (side) { } else { isGripClicked = gripValue > GRIP_ON_VALUE; } + // Grip clicked may be being handled by UI. + if (isGripClicked) { + isGripClicked = !isGripClickedHandled; + } else { + isGripClickedHandled = false; + } // Hand-overlay intersection, if any. overlayID = null; @@ -199,6 +211,7 @@ Hand = function (side) { triggerPressed: triggerPressed, triggerClicked: triggerClicked, gripClicked: gripClicked, + setGripClickedHandled: setGripClickedHandled, intersection: getIntersection, update: update, clear: clear, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3385d1fd36..5722c61bde 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -58,6 +58,8 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { visible: true }, + NO_SWATCH_COLOR = { red: 128, green: 128, blue: 128 }, + UI_ELEMENTS = { "panel": { overlay: "cube", @@ -87,8 +89,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { properties: { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, localRotation: Quat.ZERO, + color: NO_SWATCH_COLOR, alpha: 1.0, - solid: false, // False indicates "no color assigned" + solid: false, // False indicates "no swatch color assigned" ignoreRayIntersection: false, visible: true } @@ -184,6 +187,10 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { command: { method: "setColorPerSwatch", parameter: "colorSwatch1.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch1" } }, { @@ -198,6 +205,10 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { command: { method: "setColorPerSwatch", parameter: "colorSwatch2.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch2" } }, { @@ -205,13 +216,15 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.045, z: -0.005 }, - color: { red: 128, green: 128, blue: 128 }, - solid: false + localPosition: { x: -0.035, y: 0.045, z: -0.005 } }, command: { method: "setColorPerSwatch", parameter: "colorSwatch3.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch3" } }, { @@ -219,13 +232,15 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.045, z: -0.005 }, - color: { red: 128, green: 128, blue: 128 }, - solid: false + localPosition: { x: -0.01, y: 0.045, z: -0.005 } }, command: { method: "setColorPerSwatch", parameter: "colorSwatch4.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch4" } }, { @@ -356,6 +371,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { pressedItem = null, pressedSource, isButtonPressed, + isGripClicked, isGroupButtonEnabled, isUngroupButtonEnabled, @@ -501,6 +517,19 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } } + function doGripClicked(command, parameter) { + switch (command) { + case "clearSwatch": + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameter)], { + color: NO_SWATCH_COLOR, + solid: false + }); + break; + default: + // TODO: Log error. + } + } + function update(intersectionOverlayID, groupsCount, entitiesCount) { var intersectedItem = -1, intersectionItems, @@ -606,6 +635,18 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } } + // Grip click. + if (controlHand.gripClicked() !== isGripClicked) { + isGripClicked = !isGripClicked; + if (isGripClicked && intersectionItems && intersectedItem && intersectionItems[intersectedItem].onGripClicked) { + controlHand.setGripClickedHandled(); + if (intersectionItems[intersectedItem].onGripClicked.parameter) { + parameter = intersectionItems[intersectedItem].onGripClicked.parameter; + } + doGripClicked(intersectionItems[intersectedItem].onGripClicked.method, parameter); + } + } + // Special handling for Group options. if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { enableGroupButton = groupsCount > 1; @@ -696,6 +737,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { pressedItem = null; pressedSource = null; isButtonPressed = false; + isGripClicked = false; isGroupButtonEnabled = false; isUngroupButtonEnabled = false; From 42284796a153b886bc15ede7421dd4daead6c296 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Aug 2017 21:44:54 +1200 Subject: [PATCH 149/504] Remember swatch colors --- scripts/vr-edit/modules/toolMenu.js | 50 +++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 5722c61bde..19698ef4c4 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -184,6 +184,11 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { color: { red: 255, green: 0, blue: 0 }, solid: true }, + setting: { + key: "VREdit.colorTool.swatch1Color", + property: "color" + // Default value is set in properties, above. + }, command: { method: "setColorPerSwatch", parameter: "colorSwatch1.color" @@ -202,6 +207,11 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { color: { red: 0, green: 255, blue: 0 }, solid: true }, + setting: { + key: "VREdit.colorTool.swatch2Color", + property: "color" + // Default value is set in properties, above. + }, command: { method: "setColorPerSwatch", parameter: "colorSwatch2.color" @@ -217,6 +227,12 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.035, y: 0.045, z: -0.005 } + // Default to empty swatch. + }, + setting: { + key: "VREdit.colorTool.swatch3Color", + property: "color" + // Default value is set in properties, above. }, command: { method: "setColorPerSwatch", @@ -233,6 +249,12 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.01, y: 0.045, z: -0.005 } + // Default to empty swatch. + }, + setting: { + key: "VREdit.colorTool.swatch4Color", + property: "color" + // Default value is set in properties, above. }, command: { method: "setColorPerSwatch", @@ -435,9 +457,15 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { if (value === "") { value = optionsItems[i].setting.defaultValue; } - properties[optionsItems[i].setting.property] = value; - if (optionsItems[i].setting.callback) { - doCallback(optionsItems[i].setting.callback.method, value); + if (value) { + properties[optionsItems[i].setting.property] = value; + if (optionsItems[i].type === "swatch") { + // Special case for when swatch color is defined. + properties.solid = true; + } + if (optionsItems[i].setting.callback) { + doCallback(optionsItems[i].setting.callback.method, value); + } } } optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); @@ -478,13 +506,15 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { function doCommand(command, parameter) { var parameters, + overlayID, hasColor, value; switch (command) { case "setColorPerSwatch": parameters = parameter.split("."); - hasColor = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf(parameters[0])], "solid"); + overlayID = optionsOverlaysIDs.indexOf(parameters[0]); + hasColor = Overlays.getProperty(optionsOverlays[overlayID], "solid"); if (hasColor) { // Swatch has a color; set current fill color to swatch color. value = evaluateParameter(parameter); @@ -498,10 +528,13 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } else { // Swatch has no color; set swatch color to current fill color. value = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], "color"); - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameters[0])], { + Overlays.editOverlay(optionsOverlays[overlayID], { color: value, solid: true }); + if (optionsSettings[parameters[0]]) { + Settings.setValue(optionsSettings[parameters[0]].key, value); + } } break; case "setColorFromPick": @@ -518,12 +551,17 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { } function doGripClicked(command, parameter) { + var overlayID; switch (command) { case "clearSwatch": - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameter)], { + overlayID = optionsOverlaysIDs.indexOf(parameter); + Overlays.editOverlay(optionsOverlays[overlayID], { color: NO_SWATCH_COLOR, solid: false }); + if (optionsSettings[parameter]) { + Settings.setValue(optionsSettings[parameter].key, null); // Deleted settings value. + } break; default: // TODO: Log error. From 046ce353fd4a9b8160c5069b2965f671fe5b5edf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 8 Aug 2017 21:56:01 +1200 Subject: [PATCH 150/504] Fix highlight overlay getting deleted --- scripts/vr-edit/modules/toolMenu.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 19698ef4c4..d96c12096e 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -434,6 +434,9 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { length; // Close current panel, if any. + Overlays.editOverlay(highlightOverlay, { + parentID: menuOriginOverlay + }); for (i = 0, length = optionsOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(optionsOverlays[i]); } From 22432671ca1d15eae53ff89bc877faa9e422ad52 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 09:26:33 +1200 Subject: [PATCH 151/504] Fix newly created entity not being grabbed --- scripts/vr-edit/modules/createPalette.js | 8 +++++--- scripts/vr-edit/modules/toolMenu.js | 8 ++++---- scripts/vr-edit/vr-edit.js | 24 +++++++++++++++++++----- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index be9558e432..846c82dbcf 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -10,7 +10,7 @@ /* global CreatePalette */ -CreatePalette = function (side, leftInputs, rightInputs) { +CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -108,7 +108,8 @@ CreatePalette = function (side, leftInputs, rightInputs) { } function update(intersectionOverlayID) { - var CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }; + var entityID, + CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }; // Highlight cube. if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { isHighlightingCube = !isHighlightingCube; @@ -126,7 +127,8 @@ CreatePalette = function (side, leftInputs, rightInputs) { Vec3.multiplyQbyV(controlHand.orientation(), Vec3.sum({ x: 0, y: CUBE_ENTITY_PROPERTIES.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); CUBE_ENTITY_PROPERTIES.rotation = controlHand.orientation(); - Entities.addEntity(CUBE_ENTITY_PROPERTIES); + entityID = Entities.addEntity(CUBE_ENTITY_PROPERTIES); + uiCommandCallback("autoGrab"); } else { Overlays.editOverlay(cubeOverlay, { localPosition: CUBE_PROPERTIES.localPosition diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d96c12096e..63c0037e43 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,7 +10,7 @@ /* global ToolMenu */ -ToolMenu = function (side, leftInputs, rightInputs, doCallback) { +ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -467,7 +467,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { properties.solid = true; } if (optionsItems[i].setting.callback) { - doCallback(optionsItems[i].setting.callback.method, value); + uiCommandCallback(optionsItems[i].setting.callback.method, value); } } } @@ -527,7 +527,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { if (optionsSettings.currentColor) { Settings.setValue(optionsSettings.currentColor.key, value); } - doCallback("setColor", value); + uiCommandCallback("setColor", value); } else { // Swatch has no color; set swatch color to current fill color. value = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], "color"); @@ -671,7 +671,7 @@ ToolMenu = function (side, leftInputs, rightInputs, doCallback) { if (intersectionItems[intersectedItem].callback.parameter) { parameterValue = evaluateParameter(intersectionItems[intersectedItem].callback.parameter); } - doCallback(intersectionItems[intersectedItem].callback.method, parameterValue); + uiCommandCallback(intersectionItems[intersectedItem].callback.method, parameterValue); } } } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 576ae44093..baea506446 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -179,7 +179,7 @@ }; - UI = function (side, leftInputs, rightInputs, setToolCallback) { + UI = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu and Create palette. var // Primary objects. @@ -197,8 +197,8 @@ } toolIcon = new ToolIcon(otherHand(side)); - toolMenu = new ToolMenu(side, leftInputs, rightInputs, setToolCallback); - createPalette = new CreatePalette(side, leftInputs, rightInputs); + toolMenu = new ToolMenu(side, leftInputs, rightInputs, uiCommandCallback); + createPalette = new CreatePalette(side, leftInputs, rightInputs, uiCommandCallback); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -331,6 +331,7 @@ isGripClicked = false, wasGripClicked = false, hoveredOverlayID = null, + isAutoGrab = false, // Position values. initialHandOrientationInverse, @@ -382,6 +383,11 @@ handles.hover(overlayID); } + function enableAutoGrab() { + // Used to grab entity created from Create palette. + isAutoGrab = true; + } + function isHandle(overlayID) { return handles.isHandle(overlayID); } @@ -849,10 +855,12 @@ && otherEditor.isHandle(intersection.overlayID)) { highlightedEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && (wasTriggerClicked || !isTriggerClicked)) { + } else if (intersection.entityID && intersection.editableEntity + && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab) { highlightedEntityID = Entities.rootOf(intersection.entityID); setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && intersection.editableEntity && !wasTriggerClicked && isTriggerClicked) { + } else if (intersection.entityID && intersection.editableEntity + && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { highlightedEntityID = Entities.rootOf(intersection.entityID); if (otherEditor.isEditing(highlightedEntityID)) { if (toolSelected !== TOOL_SCALE) { @@ -1067,6 +1075,7 @@ wasTriggerClicked = isTriggerClicked; wasGripClicked = isGripClicked; + isAutoGrab = isAutoGrab && isTriggerClicked; if (DEBUG && editorState !== previousState) { debug(side, EDITOR_STATE_STRINGS[editorState]); @@ -1115,6 +1124,7 @@ return { setReferences: setReferences, hoverHandle: hoverHandle, + enableAutoGrab: enableAutoGrab, isHandle: isHandle, isEditing: isEditing, isScaling: isScaling, @@ -1318,6 +1328,10 @@ ui.setToolColor(parameter); colorToolColor = parameter; break; + case "autoGrab": + editors[LEFT_HAND].enableAutoGrab(); + editors[RIGHT_HAND].enableAutoGrab(); + break; default: debug("ERROR: Unexpected command in onUICommand()!"); } From 28f9f9e4d0593ff6fd5750ce2f3c506ed0ea09d1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 11:02:33 +1200 Subject: [PATCH 152/504] Add a "Physics" button and panel --- scripts/vr-edit/modules/toolMenu.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 63c0037e43..65647740fd 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -290,6 +290,15 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { method: "pickColorTool" } } + ], + physicsOptions: [ + { + id: "physicsOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + } ] }, @@ -352,6 +361,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { parameter: "currentColor.color" } }, + { + id: "physicsButton", + type: "button", + properties: { + localPosition: { x: -0.022, y: 0.04, z: -0.005 }, + color: { red: 60, green: 60, blue: 240 } + }, + label: "PHYSICS", + toolOptions: "physicsOptions", + callback: { + method: "physicsTool" + } + }, { id: "deleteButton", type: "button", From c79931106d45f34e0ec922d3d56da10d998707bb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 12:06:35 +1200 Subject: [PATCH 153/504] Add basic element of slider --- scripts/vr-edit/modules/selection.js | 5 +++++ scripts/vr-edit/modules/toolIcon.js | 5 ++++- scripts/vr-edit/modules/toolMenu.js | 22 ++++++++++++++++++++++ scripts/vr-edit/vr-edit.js | 18 ++++++++++++++++-- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 9836a1fed6..2b1e7e5a37 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -365,6 +365,10 @@ Selection = function (side) { return properties.color; } + function applyPhysics() { + // TODO + } + function clear() { selection = []; selectedEntityID = null; @@ -401,6 +405,7 @@ Selection = function (side) { cloneEntities: cloneEntities, applyColor: applyColor, getColor: getColor, + applyPhysics: applyPhysics, deleteEntities: deleteEntities, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index a223fdb923..6ff748ac59 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -20,7 +20,8 @@ ToolIcon = function (side) { GROUP_TOOL = 2, COLOR_TOOL = 3, PICK_COLOR_TOOL = 4, - DELETE_TOOL = 5, + PHYSICS_TOOL = 5, + DELETE_TOOL = 6, ICON_COLORS = [ { red: 0, green: 240, blue: 240 }, @@ -28,6 +29,7 @@ ToolIcon = function (side) { { red: 220, green: 60, blue: 220 }, { red: 220, green: 220, blue: 220 }, { red: 0, green: 0, blue: 0 }, + { red: 60, green: 60, blue: 240 }, { red: 240, green: 60, blue: 60 } ], @@ -114,6 +116,7 @@ ToolIcon = function (side) { GROUP_TOOL: GROUP_TOOL, COLOR_TOOL: COLOR_TOOL, PICK_COLOR_TOOL: PICK_COLOR_TOOL, + PHYSICS_TOOL: PHYSICS_TOOL, DELETE_TOOL: DELETE_TOOL, setHand: setHand, update: update, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 65647740fd..a90cb4f17c 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -126,6 +126,18 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ignoreRayIntersection: true, visible: true } + }, + "slider": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.1, z: 0.01 }, + localRotation: Quat.ZERO, + color: { red: 128, green: 128, blue: 255 }, + alpha: 0.1, + solid: true, + ignoreRayIntersection: false, + visible: true + } } }, @@ -298,6 +310,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: 0.055, y: 0.0, z: -0.005 } } + }, + { + id: "physicsSlider", + type: "slider", + properties: { + localPosition: { x: -0.02, y: 0.0, z: -0.005 } + }, + callback: { + method: "setSliderValue" + } } ] }, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index baea506446..7b93fd98b3 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -28,7 +28,8 @@ TOOL_GROUP = 3, TOOL_COLOR = 4, TOOL_PICK_COLOR = 5, - TOOL_DELETE = 6, + TOOL_PHYSICS = 6, + TOOL_DELETE = 7, toolSelected = TOOL_NONE, colorToolColor = { red: 128, green: 128, blue: 128 }, @@ -283,6 +284,7 @@ GROUP_TOOL: toolIcon.GROUP_TOOL, COLOR_TOOL: toolIcon.COLOR_TOOL, PICK_COLOR_TOOL: toolIcon.PICK_COLOR_TOOL, + PHYSICS_TOOL: toolIcon.PHYSICS_TOOL, DELETE_TOOL: toolIcon.DELETE_TOOL, display: display, updateUIEntities: setUIEntities, @@ -882,6 +884,8 @@ toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); ui.setToolColor(colorToolColor); + } else if (toolSelected === TOOL_PHYSICS) { + selection.applyPhysics(); } else if (toolSelected === TOOL_DELETE) { setState(EDITOR_HIGHLIGHTING); selection.deleteEntities(); @@ -949,6 +953,8 @@ toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); ui.setToolColor(colorToolColor); + } else if (toolSelected === TOOL_PHYSICS) { + selection.applyPhysics(); } else if (toolSelected === TOOL_DELETE) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -1308,6 +1314,11 @@ ui.setToolIcon(ui.PICK_COLOR_TOOL); ui.updateUIEntities(); break; + case "physicsTool": + toolSelected = TOOL_PHYSICS; + ui.setToolIcon(ui.PHYSICS_TOOL); + ui.updateUIEntities(); + break; case "deleteTool": grouping.clear(); toolSelected = TOOL_DELETE; @@ -1332,8 +1343,11 @@ editors[LEFT_HAND].enableAutoGrab(); editors[RIGHT_HAND].enableAutoGrab(); break; + case "setSliderValue": + print("$$$$$$$ setSliderValue = " + JSON.stringify(parameter)); + break; default: - debug("ERROR: Unexpected command in onUICommand()!"); + debug("ERROR: Unexpected command in onUICommand()! " + command); } } From 3d4cec63cd3a361dbea08e872112c405b96315ad Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 14:36:17 +1200 Subject: [PATCH 154/504] Calculate slider value from laser intersection --- scripts/vr-edit/modules/toolMenu.js | 29 ++++++++++++++++++++++++----- scripts/vr-edit/vr-edit.js | 12 +++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index a90cb4f17c..8c3f1dd8d9 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -615,7 +615,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } - function update(intersectionOverlayID, groupsCount, entitiesCount) { + function update(intersection, groupsCount, entitiesCount) { var intersectedItem = -1, intersectionItems, parentProperties, @@ -624,17 +624,21 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { parameter, parameterValue, enableGroupButton, - enableUngroupButton; + enableUngroupButton, + sliderProperties, + overlayDimensions, + basePoint, + fraction; // Intersection details. - if (intersectionOverlayID) { - intersectedItem = menuOverlays.indexOf(intersectionOverlayID); + if (intersection.overlayID) { + intersectedItem = menuOverlays.indexOf(intersection.overlayID); if (intersectedItem !== -1) { intersectionItems = MENU_ITEMS; intersectionOverlays = menuOverlays; intersectionEnabled = null; } else { - intersectedItem = optionsOverlays.indexOf(intersectionOverlayID); + intersectedItem = optionsOverlays.indexOf(intersection.overlayID); if (intersectedItem !== -1) { intersectionItems = optionsItems; intersectionOverlays = optionsOverlays; @@ -732,6 +736,21 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + // Slider update. + if (intersectionItems && intersectionItems[intersectedItem].type === "slider" && controlHand.triggerClicked()) { + sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); + overlayDimensions = intersectionItems[intersectedItem].properties.dimensions; + if (overlayDimensions === undefined) { + overlayDimensions = UI_ELEMENTS.slider.properties.dimensions; + } + basePoint = Vec3.sum(sliderProperties.position, + Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); + fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), + Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; + + uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + } + // Special handling for Group options. if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { enableGroupButton = groupsCount > 1; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7b93fd98b3..94b155e38c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -238,9 +238,12 @@ } function update() { + var intersection; + if (isDisplaying) { - toolMenu.update(getIntersection().overlayID, grouping.groupsCount(), grouping.entitiesCount()); - createPalette.update(getIntersection().overlayID); + intersection = getIntersection(); + toolMenu.update(intersection, grouping.groupsCount(), grouping.entitiesCount()); + createPalette.update(intersection.overlayID); toolIcon.update(); } } @@ -1344,7 +1347,10 @@ editors[RIGHT_HAND].enableAutoGrab(); break; case "setSliderValue": - print("$$$$$$$ setSliderValue = " + JSON.stringify(parameter)); + if (parameter !== undefined) { + // TODO + print("setSliderValue = " + parameter); + } break; default: debug("ERROR: Unexpected command in onUICommand()! " + command); From 938b09c1a42d26a6787666cebea20be10daee8f7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 15:19:25 +1200 Subject: [PATCH 155/504] Slider display per intersection value --- scripts/vr-edit/modules/toolMenu.js | 54 +++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 8c3f1dd8d9..95306c3326 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -24,6 +24,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = [], optionsOverlaysIDs = [], // Text ids (names) of options overlays. + optionsOverlaysAuxiliaries = [], optionsEnabled = [], optionsSettings = {}, @@ -133,11 +134,37 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.03, y: 0.1, z: 0.01 }, localRotation: Quat.ZERO, color: { red: 128, green: 128, blue: 255 }, - alpha: 0.1, + alpha: 0.0, solid: true, ignoreRayIntersection: false, visible: true } + }, + "sliderValue": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: 0.035, z: 0 }, + localRotation: Quat.ZERO, + color: { red: 100, green: 240, blue: 100 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: true, + visible: true + } + }, + "sliderRemainder": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.07, z: 0.01 }, + localPosition: { x: 0, y: -0.015, z: 0 }, + localRotation: Quat.ZERO, + color: { red: 64, green: 64, blue: 64 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: true, + visible: true + } } }, @@ -487,6 +514,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = []; optionsOverlaysIDs = []; + optionsOverlaysAuxiliaries = []; optionsEnabled = []; optionsItems = null; @@ -523,6 +551,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.parentID = optionsOverlays[optionsOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } + if (optionsItems[i].type === "slider") { + optionsOverlaysAuxiliaries[i] = {}; + properties = Object.clone(UI_ELEMENTS.sliderValue.properties); + properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderValue.overlay, properties); + properties = Object.clone(UI_ELEMENTS.sliderRemainder.properties); + properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.sliderRemainder.overlay, + properties); + } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. optionsEnabled.push(true); } @@ -628,7 +666,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { sliderProperties, overlayDimensions, basePoint, - fraction; + fraction, + otherFraction; // Intersection details. if (intersection.overlayID) { @@ -747,6 +786,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; + otherFraction = 1.0 - fraction; + + Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { + localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, + dimensions: { x: overlayDimensions.x, y: fraction * overlayDimensions.y, z: overlayDimensions.z } + }); + Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].remainder, { + localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, + dimensions: { x: overlayDimensions.x, y: otherFraction * overlayDimensions.y, z: overlayDimensions.z } + }); uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); } @@ -871,6 +920,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.deleteOverlay(highlightOverlay); for (i = 0, length = optionsOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(optionsOverlays[i]); + // Any auxiliary overlays parented to this overlay are automatically deleted. } optionsOverlays = []; From c19ec5822048889efb2b8a6268468cbd9a0d3f8a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 15:51:37 +1200 Subject: [PATCH 156/504] Halve hand highlight and grab sphere radius --- scripts/vr-edit/modules/hand.js | 2 +- scripts/vr-edit/modules/highlights.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 14994c226d..92e8b06e10 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -31,7 +31,7 @@ Hand = function (side) { TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. TRIGGER_CLICKED_VALUE = 1.0, - NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js. + NEAR_GRAB_RADIUS = 0.05, // Different from handControllerGrab.js's value of 0.1. NEAR_HOVER_RADIUS = 0.025, LEFT_HAND = 0, diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 9f0dc44275..333a68f04e 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -22,7 +22,7 @@ Highlights = function (side) { GROUP_COLOR = { red: 220, green: 60, blue: 220 }, HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, - HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, + HAND_HIGHLIGHT_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, LEFT_HAND = 0; From 1c2d3ced898c3f42d893b23753b036830380e89b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Aug 2017 17:28:28 +1200 Subject: [PATCH 157/504] Add some further entities to the Create palette --- scripts/vr-edit/modules/createPalette.js | 214 +++++++++++++++++------ 1 file changed, 160 insertions(+), 54 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 846c82dbcf..0ee7b6e4f7 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -17,8 +17,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { var paletteOriginOverlay, palettePanelOverlay, - cubeOverlay, - cubeHighlightOverlay, + highlightOverlay, + paletteItemOverlays = [], LEFT_HAND = 0, @@ -51,18 +51,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, - CUBE_PROPERTIES = { - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: Quat.ZERO, - color: { red: 240, green: 0, blue: 0 }, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true - }, - - CUBE_HIGHLIGHT_PROPERTIES = { + HIGHLIGHT_PROPERTIES = { dimensions: { x: 0.034, y: 0.034, z: 0.034 }, localPosition: { x: 0.02, y: 0.02, z: 0.0 }, localRotation: Quat.ZERO, @@ -74,16 +63,99 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }, - CUBE_ENTITY_PROPERTIES = { - type: "Box", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } - }, + PALETTE_ITEMS = [ + { + overlay: { + type: "cube", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localRotation: Quat.ZERO, + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + entity: { + type: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + } + }, + { + overlay: { + type: "shape", + properties: { + shape: "Cylinder", + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.06, y: 0.02, z: 0.0 }, + localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + entity: { + type: "Shape", + shape: "Cylinder", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + } + }, + { + overlay: { + type: "shape", + properties: { + shape: "Cone", + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.10, y: 0.02, z: 0.0 }, + localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + entity: { + type: "Shape", + shape: "Cone", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + } + }, + { + overlay: { + type: "sphere", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.03 }, + localPosition: { x: 0.14, y: 0.02, z: 0.0 }, + localRotation: Quat.ZERO, + color: { red: 240, green: 0, blue: 0 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + entity: { + type: "Sphere", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 192, green: 192, blue: 192 } + } + } + ], isDisplaying = false, - isHighlightingCube = false, - isCubePressed = false, + NONE = -1, + highlightedItem = NONE, + pressedItem = NONE, + wasTriggerClicked = false, // References. controlHand; @@ -104,43 +176,70 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); function getEntityIDs() { - return [palettePanelOverlay, cubeOverlay]; + return [palettePanelOverlay].concat(paletteItemOverlays); } function update(intersectionOverlayID) { - var entityID, - CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }; + var itemIndex, + isTriggerClicked, + properties, + PRESS_DELTA = { x: 0, y: 0, z: 0.01 }, + CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, + INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }); + // Highlight cube. - if (intersectionOverlayID === cubeOverlay !== isHighlightingCube) { - isHighlightingCube = !isHighlightingCube; - Overlays.editOverlay(cubeHighlightOverlay, { visible: isHighlightingCube }); + itemIndex = paletteItemOverlays.indexOf(intersectionOverlayID); + if (itemIndex !== NONE) { + if (highlightedItem !== itemIndex) { + Overlays.editOverlay(highlightOverlay, { + parentID: intersectionOverlayID, + localPosition: Vec3.ZERO, + visible: true + }); + highlightedItem = itemIndex; + } + } else { + Overlays.editOverlay(highlightOverlay, { + visible: false + }); + highlightedItem = NONE; } - // Cube click. - if (isHighlightingCube && controlHand.triggerClicked() !== isCubePressed) { - isCubePressed = controlHand.triggerClicked(); - if (isCubePressed) { - Overlays.editOverlay(cubeOverlay, { - localPosition: Vec3.sum(CUBE_PROPERTIES.localPosition, { x: 0, y: 0, z: 0.01 }) - }); - CUBE_ENTITY_PROPERTIES.position = Vec3.sum(controlHand.palmPosition(), - Vec3.multiplyQbyV(controlHand.orientation(), - Vec3.sum({ x: 0, y: CUBE_ENTITY_PROPERTIES.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); - CUBE_ENTITY_PROPERTIES.rotation = controlHand.orientation(); - entityID = Entities.addEntity(CUBE_ENTITY_PROPERTIES); - uiCommandCallback("autoGrab"); - } else { - Overlays.editOverlay(cubeOverlay, { - localPosition: CUBE_PROPERTIES.localPosition - }); - } + // Unpress currently pressed item. + if (pressedItem !== NONE && pressedItem !== itemIndex) { + Overlays.editOverlay(paletteItemOverlays[pressedItem], { + localPosition: PALETTE_ITEMS[pressedItem].overlay.properties.localPosition + }); + pressedItem = NONE; } + + // Press item and create new entity. + isTriggerClicked = controlHand.triggerClicked(); + if (highlightedItem !== NONE && pressedItem === NONE && isTriggerClicked && !wasTriggerClicked) { + Overlays.editOverlay(paletteItemOverlays[itemIndex], { + localPosition: Vec3.sum(PALETTE_ITEMS[itemIndex].overlay.properties.localPosition, PRESS_DELTA) + }); + pressedItem = itemIndex; + + properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); + properties.position = Vec3.sum(controlHand.palmPosition(), + Vec3.multiplyQbyV(controlHand.orientation(), + Vec3.sum({ x: 0, y: properties.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); + properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); + Entities.addEntity(properties); + + uiCommandCallback("autoGrab"); // TODO: Could pass entity ID through to autoGrab. + } + + wasTriggerClicked = isTriggerClicked; } function display() { // Creates and shows menu entities. var handJointIndex, - properties; + properties, + i, + length; if (isDisplaying) { return; @@ -161,30 +260,37 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.localPosition = Vec3.sum(PALETTE_ROOT_POSITION, { x: lateralOffset, y: 0, z: 0 }); paletteOriginOverlay = Overlays.addOverlay("sphere", properties); - // Create palette items. + // Create palette. properties = Object.clone(PALETTE_PANEL_PROPERTIES); properties.parentID = paletteOriginOverlay; palettePanelOverlay = Overlays.addOverlay("cube", properties); - properties = Object.clone(CUBE_PROPERTIES); - properties.parentID = paletteOriginOverlay; - cubeOverlay = Overlays.addOverlay("cube", properties); + for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { + properties = Object.clone(PALETTE_ITEMS[i].overlay.properties); + properties.parentID = paletteOriginOverlay; + paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEMS[i].overlay.type, properties); + } // Prepare cube highlight overlay. - properties = Object.clone(CUBE_HIGHLIGHT_PROPERTIES); + properties = Object.clone(HIGHLIGHT_PROPERTIES); properties.parentID = paletteOriginOverlay; - cubeHighlightOverlay = Overlays.addOverlay("cube", properties); + highlightOverlay = Overlays.addOverlay("cube", properties); isDisplaying = true; } function clear() { // Deletes menu entities. + var i, + length; + if (!isDisplaying) { return; } - Overlays.deleteOverlay(cubeHighlightOverlay); - Overlays.deleteOverlay(cubeOverlay); + Overlays.deleteOverlay(highlightOverlay); + for (i = 0, length = paletteItemOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(paletteItemOverlays[i]); + } Overlays.deleteOverlay(palettePanelOverlay); Overlays.deleteOverlay(paletteOriginOverlay); From 1aeaee8153db8d40abcc59627418b189e702b7dd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 09:04:28 +1200 Subject: [PATCH 158/504] Rename slider to barSlider --- scripts/vr-edit/modules/toolMenu.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 95306c3326..d86f84bf08 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -128,7 +128,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "slider": { + "barSlider": { overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.1, z: 0.01 }, @@ -140,7 +140,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "sliderValue": { + "barSliderValue": { overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, @@ -153,7 +153,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "sliderRemainder": { + "barSliderRemainder": { overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.07, z: 0.01 }, @@ -340,7 +340,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, { id: "physicsSlider", - type: "slider", + type: "barSlider", properties: { localPosition: { x: -0.02, y: 0.0, z: -0.005 } }, @@ -551,14 +551,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.parentID = optionsOverlays[optionsOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } - if (optionsItems[i].type === "slider") { + if (optionsItems[i].type === "barSlider") { optionsOverlaysAuxiliaries[i] = {}; - properties = Object.clone(UI_ELEMENTS.sliderValue.properties); + properties = Object.clone(UI_ELEMENTS.barSliderValue.properties); properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderValue.overlay, properties); - properties = Object.clone(UI_ELEMENTS.sliderRemainder.properties); + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, properties); + properties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.sliderRemainder.overlay, + optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, properties); } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. @@ -775,12 +775,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } - // Slider update. - if (intersectionItems && intersectionItems[intersectedItem].type === "slider" && controlHand.triggerClicked()) { + // Bar slider update. + if (intersectionItems && intersectionItems[intersectedItem].type === "barSlider" && controlHand.triggerClicked()) { sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); overlayDimensions = intersectionItems[intersectedItem].properties.dimensions; if (overlayDimensions === undefined) { - overlayDimensions = UI_ELEMENTS.slider.properties.dimensions; + overlayDimensions = UI_ELEMENTS.barSlider.properties.dimensions; } basePoint = Vec3.sum(sliderProperties.position, Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); From 397527d3f3468aaa4a5bbb523334e6ebd04402cd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 10:23:13 +1200 Subject: [PATCH 159/504] Add image UI element --- scripts/vr-edit/modules/toolMenu.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d86f84bf08..02b299ce75 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -128,6 +128,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, + "image": { + overlay: "image3d", + properties: { + dimensions: { x: 0.1, y: 0.1 }, + localPosition: { x: 0, y: 0, z: 0 }, + localRotation: Quat.ZERO, + color: { red: 255, green: 255, blue: 255 }, + alpha: 1.0, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + } + }, "barSlider": { overlay: "cube", properties: { @@ -526,6 +539,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); properties = Object.merge(properties, optionsItems[i].properties); properties.parentID = parentID; + if (properties.url) { + properties.url = Script.resolvePath(properties.url); + } if (optionsItems[i].setting) { optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; value = Settings.getValue(optionsItems[i].setting.key); From e1adb3a20ed063b22dfe66c55fc648e2c72f46f4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 12:44:30 +1200 Subject: [PATCH 160/504] Add image slider (for H, S, V or similar values) --- scripts/vr-edit/assets/slider-white-alpha.png | Bin 0 -> 564 bytes scripts/vr-edit/assets/slider-white.png | Bin 0 -> 187 bytes scripts/vr-edit/modules/toolMenu.js | 132 ++++++++++++++++-- 3 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 scripts/vr-edit/assets/slider-white-alpha.png create mode 100644 scripts/vr-edit/assets/slider-white.png diff --git a/scripts/vr-edit/assets/slider-white-alpha.png b/scripts/vr-edit/assets/slider-white-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..d61221d492a2c879057ec23d578ffda36258780e GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0y~yU=U$oU`XL$V_;xtTP<*gfq{XsILO_JVcj{ImkbOH zEa{HEjtmSN`?>!lvNA9*a29w(7BevL9R^{>|GE>&Eb)~Fh>-{no(Yl+0AwM$RFB~}1x1dqJ zjD@#myYh>mdv7?jYPKq`_;9pu!J%~PCa(D)fi21z51M5cD$ir945^T3b)H_7?jG=P zt*m3Kc^Qjn+-<>-id6T2hf8H0TgA&*MCaWW{Gjj3{8hV2cJJZM8WL6Q%R!4y~9?$}0*k z_I;2S-yODE*X7^=`N-`y@~qDNHXw(uk#%HFf5V}>bIMB%3B}W!G_9Ms)ZcO}j9Tk= zLomc5&z&Q4R^Ls*5Q{AL3kL+Po7nhn3z`(1=o8SIdTN2JV=Hr6OMu?=-)}jz-fUKG yQSqz-u{J7aBy5*;WZwOTL+VB*yT;Ty24_aq{j)-^$ucl7FnGH9xvX!lvNA9*a29w(7BevL9R^{>bP0 Hl+XkKX5mD1 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 02b299ce75..b7141b5440 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -144,9 +144,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "barSlider": { overlay: "cube", properties: { - dimensions: { x: 0.03, y: 0.1, z: 0.01 }, + dimensions: { x: 0.02, y: 0.1, z: 0.01 }, localRotation: Quat.ZERO, - color: { red: 128, green: 128, blue: 255 }, + color: { red: 128, green: 128, blue: 128 }, alpha: 0.0, solid: true, ignoreRayIntersection: false, @@ -156,7 +156,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "barSliderValue": { overlay: "cube", properties: { - dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + dimensions: { x: 0.02, y: 0.03, z: 0.01 }, localPosition: { x: 0, y: 0.035, z: 0 }, localRotation: Quat.ZERO, color: { red: 100, green: 240, blue: 100 }, @@ -169,7 +169,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "barSliderRemainder": { overlay: "cube", properties: { - dimensions: { x: 0.03, y: 0.07, z: 0.01 }, + dimensions: { x: 0.02, y: 0.07, z: 0.01 }, localPosition: { x: 0, y: -0.015, z: 0 }, localRotation: Quat.ZERO, color: { red: 64, green: 64, blue: 64 }, @@ -178,6 +178,34 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ignoreRayIntersection: true, visible: true } + }, + "imageSlider": { + overlay: "cube", + properties: { + dimensions: { x: 0.02, y: 0.1, z: 0.01 }, + localRotation: Quat.ZERO, + color: { red: 128, green: 128, blue: 128 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + useBaseColor: false, + imageURL: null, + imageOverlayURL: null + }, + "sliderPointer": { + overlay: "shape", + properties: { + shape: "Cone", + dimensions: { x: 0.01, y: 0.01, z: 0.01 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + color: { red: 180, green: 180, blue: 180 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: true, + visible: true + } } }, @@ -360,6 +388,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: { method: "setSliderValue" } + }, + { + id: "colorSlider", + type: "imageSlider", + properties: { + localPosition: { x: 0.02, y: 0.0, z: -0.005 }, + color: { red: 255, green: 0, blue: 0 } + }, + useBaseColor: true, + imageURL: "../assets/slider-white.png", + imageOverlayURL: "../assets/slider-white-alpha.png", + callback: { + method: "setSliderValue" + } } ] }, @@ -512,8 +554,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function openOptions(toolOptions) { var properties, + childProperties, + auxiliaryProperties, parentID, value, + imageOffset, + IMAGE_OFFSET = 0.0005, i, length; @@ -567,15 +613,62 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.parentID = optionsOverlays[optionsOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } + if (optionsItems[i].type === "barSlider") { optionsOverlaysAuxiliaries[i] = {}; - properties = Object.clone(UI_ELEMENTS.barSliderValue.properties); - properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, properties); - properties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); - properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, + auxiliaryProperties); + auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, - properties); + auxiliaryProperties); + } + if (optionsItems[i].type === "imageSlider") { + imageOffset = 0.0; + + // Primary image. + if (optionsItems[i].imageURL) { + childProperties = Object.clone(UI_ELEMENTS.image.properties); + childProperties.url = Script.resolvePath(optionsItems[i].imageURL); + childProperties.scale = properties.dimensions.y; + imageOffset += IMAGE_OFFSET; + childProperties.emissive = true; + if (optionsItems[i].useBaseColor) { + childProperties.color = properties.color; + } + childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + } + + // Overlay image. + if (optionsItems[i].imageOverlayURL) { + childProperties = Object.clone(UI_ELEMENTS.image.properties); + childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); + childProperties.drawInFront = true; // TODO: Work-around for rendering bug; remove when bug fixed. + childProperties.scale = properties.dimensions.y; + imageOffset += IMAGE_OFFSET; + childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + } + + // Value pointers. + optionsOverlaysAuxiliaries[i] = {}; + optionsOverlaysAuxiliaries[i].offset = + { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); + auxiliaryProperties.localPosition = optionsOverlaysAuxiliaries[i].offset; + auxiliaryProperties.drawInFront = true; // TODO: Accommodate work-around above; remove when bug fixed. + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, + auxiliaryProperties); + auxiliaryProperties.localPosition = { x: 0, y: properties.dimensions.x, z: 0 }; + auxiliaryProperties.localRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + auxiliaryProperties.parentID = optionsOverlaysAuxiliaries[i].value; + Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. optionsEnabled.push(true); @@ -816,6 +909,25 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); } + // Image slider update. + if (intersectionItems && intersectionItems[intersectedItem].type === "imageSlider" && controlHand.triggerClicked()) { + sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); + overlayDimensions = intersectionItems[intersectedItem].properties.dimensions; + if (overlayDimensions === undefined) { + overlayDimensions = UI_ELEMENTS.imageSlider.properties.dimensions; + } + basePoint = Vec3.sum(sliderProperties.position, + Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); + fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), + Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; + Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { + localPosition: Vec3.sum(optionsOverlaysAuxiliaries[intersectedItem].offset, + { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) + }); + + uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + } + // Special handling for Group options. if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { enableGroupButton = groupsCount > 1; From cc6464494665964e94aee8d356dfb7adab71ea92 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 15:02:19 +1200 Subject: [PATCH 161/504] Don't press sliders down when click on them --- scripts/vr-edit/modules/toolMenu.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index b7141b5440..da26a97e48 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -209,6 +209,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, + BUTTON_UI_ELEMENTS = ["button", "swatch"], + OPTONS_PANELS = { groupOptions: [ { @@ -818,7 +820,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }); highlightedItem = intersectedItem; - isHighlightingButton = true; + isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[intersectedItem].type) !== NONE; } else if (highlightedItem !== NONE) { // Un-highlight previous button. Overlays.editOverlay(highlightOverlay, { From 9442a667b4c9913355d589453712b0bddd07d189 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 15:32:48 +1200 Subject: [PATCH 162/504] Raise sliders on hover --- scripts/vr-edit/modules/toolMenu.js | 38 ++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index da26a97e48..afd7b75355 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -210,6 +210,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, BUTTON_UI_ELEMENTS = ["button", "swatch"], + BUTTON_PRESS_DELTA = { x: 0, y: 0, z: 0.004 }, + + SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], + SLIDER_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, OPTONS_PANELS = { groupOptions: [ @@ -516,8 +520,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { intersectionOverlays, intersectionEnabled, highlightedItem, + highlightedItems, highlightedSource, isHighlightingButton, + isHighlightingSlider, pressedItem = null, pressedSource, isButtonPressed, @@ -769,7 +775,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { intersectionItems, parentProperties, localPosition, - BUTTON_PRESS_DELTA = 0.004, parameter, parameterValue, enableGroupButton, @@ -819,15 +824,41 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); + // Lower old slider. + if (isHighlightingSlider) { + localPosition = highlightedItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(highlightedSource[highlightedItem], { + localPosition: localPosition + }); + } + // Update status variables. highlightedItem = intersectedItem; - isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[intersectedItem].type) !== NONE; + highlightedItems = intersectionItems; + isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; + isHighlightingSlider = SLIDER_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; + // Raise new slider. + if (isHighlightingSlider) { + localPosition = intersectionItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[highlightedItem], { + localPosition: Vec3.subtract(localPosition, SLIDER_RAISE_DELTA) + }); + } } else if (highlightedItem !== NONE) { // Un-highlight previous button. Overlays.editOverlay(highlightOverlay, { visible: false }); + // Lower slider. + if (isHighlightingSlider) { + localPosition = highlightedItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(highlightedSource[highlightedItem], { + localPosition: localPosition + }); + } + // Update status variables. highlightedItem = NONE; isHighlightingButton = false; + isHighlightingSlider = false; } highlightedSource = intersectionOverlays; } @@ -847,7 +878,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Press new button. localPosition = intersectionItems[intersectedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[intersectedItem], { - localPosition: Vec3.sum(localPosition, { x: 0, y: 0, z: BUTTON_PRESS_DELTA }) + localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) }); pressedSource = intersectionOverlays; pressedItem = { @@ -1017,6 +1048,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItem = NONE; highlightedSource = null; isHighlightingButton = false; + isHighlightingSlider = false; pressedItem = null; pressedSource = null; isButtonPressed = false; From 594d144210ac7ee545f39b8f08b0d8683ce43845 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 15:45:05 +1200 Subject: [PATCH 163/504] Finesse sliders to produce values that fully got to 0.0 and 1.0 --- scripts/vr-edit/modules/toolMenu.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index afd7b75355..2095117771 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -770,6 +770,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + function adjustSliderFraction(fraction) { + // Makes slider values achieve and saturate at 0.0 and 1.0. + return Math.min(1.0, Math.max(0.0, fraction * 1.01 - 0.005)); + } + function update(intersection, groupsCount, entitiesCount) { var intersectedItem = -1, intersectionItems, @@ -928,6 +933,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; + fraction = adjustSliderFraction(fraction); otherFraction = 1.0 - fraction; Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { @@ -953,6 +959,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; + fraction = adjustSliderFraction(fraction); Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { localPosition: Vec3.sum(optionsOverlaysAuxiliaries[intersectedItem].offset, { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) From a6af6e0bff547e6444d9009103df06244b16b550 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 16:27:19 +1200 Subject: [PATCH 164/504] Add extra color swatches for user tests --- scripts/vr-edit/modules/toolMenu.js | 108 +++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 2095117771..781c3fb7a4 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -266,8 +266,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.02, z: -0.005 }, - color: { red: 255, green: 0, blue: 0 }, + localPosition: { x: -0.035, y: -0.03, z: -0.005 }, + color: { red: 0, green: 255, blue: 255 }, solid: true }, setting: { @@ -289,8 +289,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.02, z: -0.005 }, - color: { red: 0, green: 255, blue: 0 }, + localPosition: { x: -0.01, y: -0.03, z: -0.005 }, + color: { red: 255, green: 0, blue: 255 }, solid: true }, setting: { @@ -312,8 +312,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.045, z: -0.005 } - // Default to empty swatch. + localPosition: { x: -0.035, y: -0.005, z: -0.005 }, + color: { red: 255, green: 255, blue: 0 }, + solid: true }, setting: { key: "VREdit.colorTool.swatch3Color", @@ -334,8 +335,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.045, z: -0.005 } - // Default to empty swatch. + localPosition: { x: -0.01, y: -0.005, z: -0.005 }, + color: { red: 255, green: 0, blue: 0 }, + solid: true }, setting: { key: "VREdit.colorTool.swatch4Color", @@ -351,6 +353,96 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { parameter: "colorSwatch4" } }, + { + id: "colorSwatch5", + type: "swatch", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.035, y: 0.02, z: -0.005 }, + color: { red: 0, green: 255, blue: 0 }, + solid: true + }, + setting: { + key: "VREdit.colorTool.swatch5Color", + property: "color" + // Default value is set in properties, above. + }, + command: { + method: "setColorPerSwatch", + parameter: "colorSwatch5.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch5" + } + }, + { + id: "colorSwatch6", + type: "swatch", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.01, y: 0.02, z: -0.005 }, + color: { red: 0, green: 0, blue: 255 }, + solid: true + }, + setting: { + key: "VREdit.colorTool.swatch6Color", + property: "color" + // Default value is set in properties, above. + }, + command: { + method: "setColorPerSwatch", + parameter: "colorSwatch6.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch6" + } + }, + { + id: "colorSwatch7", + type: "swatch", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.035, y: 0.045, z: -0.005 } + // Default to empty swatch. + }, + setting: { + key: "VREdit.colorTool.swatch7Color", + property: "color" + // Default value is set in properties, above. + }, + command: { + method: "setColorPerSwatch", + parameter: "colorSwatch7.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch7" + } + }, + { + id: "colorSwatch8", + type: "swatch", + properties: { + dimensions: { x: 0.02, y: 0.02, z: 0.01 }, + localPosition: { x: -0.01, y: 0.045, z: -0.005 } + // Default to empty swatch. + }, + setting: { + key: "VREdit.colorTool.swatch8Color", + property: "color" + // Default value is set in properties, above. + }, + command: { + method: "setColorPerSwatch", + parameter: "colorSwatch8.color" + }, + onGripClicked: { + method: "clearSwatch", + parameter: "colorSwatch8" + } + }, { id: "currentColor", type: "circle", From 4b3f7d6614b64be31984e98d8e0182e99e69d870 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 16:46:39 +1200 Subject: [PATCH 165/504] Reorient Tools menu and slightly adjust Create palette position --- scripts/vr-edit/modules/createPalette.js | 2 +- scripts/vr-edit/modules/toolMenu.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 0ee7b6e4f7..e8d5fe8989 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -25,7 +25,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { controlJointName, CANVAS_SIZE = { x: 0.21, y: 0.13 }, - PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2, y: 0.15, z: 0.09 }, + PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2, y: 0.15, z: 0.11 }, PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), lateralOffset, diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 781c3fb7a4..bd9d926dee 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -33,8 +33,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { LEFT_HAND = 0, CANVAS_SIZE = { x: 0.22, y: 0.13 }, - PANEL_ORIGIN_POSITION = { x: CANVAS_SIZE.x / 2, y: 0.15, z: -0.04 }, - PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), + PANEL_ORIGIN_POSITION = { x: -0.005 - CANVAS_SIZE.x / 2, y: 0.15, z: -CANVAS_SIZE.x / 2 }, + PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 180 }), panelLateralOffset, MENU_ORIGIN_PROPERTIES = { From b58cac1c5626489a4f76a6fb4a21953c1f9afee3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 17:28:43 +1200 Subject: [PATCH 166/504] Tidying --- scripts/vr-edit/modules/createPalette.js | 2 +- scripts/vr-edit/modules/toolMenu.js | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index e8d5fe8989..659e90d8cb 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -247,7 +247,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Joint index. handJointIndex = MyAvatar.getJointIndex(controlJointName); - if (handJointIndex === -1) { + if (handJointIndex === NONE) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. // TODO: Log error. diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index bd9d926dee..b921c8f7bb 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -868,7 +868,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } function update(intersection, groupsCount, entitiesCount) { - var intersectedItem = -1, + var intersectedItem = NONE, intersectionItems, parentProperties, localPosition, @@ -885,13 +885,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Intersection details. if (intersection.overlayID) { intersectedItem = menuOverlays.indexOf(intersection.overlayID); - if (intersectedItem !== -1) { + if (intersectedItem !== NONE) { intersectionItems = MENU_ITEMS; intersectionOverlays = menuOverlays; intersectionEnabled = null; } else { intersectedItem = optionsOverlays.indexOf(intersection.overlayID); - if (intersectedItem !== -1) { + if (intersectedItem !== NONE) { intersectionItems = optionsItems; intersectionOverlays = optionsOverlays; intersectionEnabled = optionsEnabled; @@ -904,7 +904,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { - if (intersectedItem !== -1 && (intersectionItems[intersectedItem].command !== undefined + if (intersectedItem !== NONE && (intersectionItems[intersectedItem].command !== undefined || intersectionItems[intersectedItem].callback !== undefined)) { // Highlight new button. (The existence of a command or callback infers that the item is a button.) parentProperties = Overlays.getProperties(intersectionOverlays[intersectedItem], @@ -1027,7 +1027,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); otherFraction = 1.0 - fraction; - Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, y: fraction * overlayDimensions.y, z: overlayDimensions.z } @@ -1101,7 +1100,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Joint index. handJointIndex = MyAvatar.getJointIndex(attachmentJointName); - if (handJointIndex === -1) { + if (handJointIndex === NONE) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. // TODO: Log error. From 20c7e17fb65b55ba4f653bce2d18c66bb2c23cbe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 21:13:40 +1200 Subject: [PATCH 167/504] Tidy up logging --- scripts/vr-edit/modules/createPalette.js | 4 +- scripts/vr-edit/modules/toolIcon.js | 4 +- scripts/vr-edit/modules/toolMenu.js | 8 ++-- scripts/vr-edit/vr-edit.js | 58 ++++++++++++++---------- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 659e90d8cb..4df39eb7ae 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -10,7 +10,7 @@ /* global CreatePalette */ -CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { +CreatePalette = function (App, side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -250,7 +250,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { if (handJointIndex === NONE) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. - // TODO: Log error. + App.log(side, "ERROR: CreatePalette: Hand joint index isn't available!"); return; } diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 6ff748ac59..ea729271c0 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -10,7 +10,7 @@ /* global ToolIcon */ -ToolIcon = function (side) { +ToolIcon = function (App, side) { // Tool icon displayed on non-dominant hand. "use strict"; @@ -80,7 +80,7 @@ ToolIcon = function (side) { if (handJointIndex === -1) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. - // TODO: Log error. + App.log(side, "ERROR: ToolIcon: Hand joint index isn't available!"); return; } diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index b921c8f7bb..b61056a063 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -10,7 +10,7 @@ /* global ToolMenu */ -ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { +ToolMenu = function (App, side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -840,7 +840,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; default: - // TODO: Log error. + App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } } @@ -858,7 +858,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; default: - // TODO: Log error. + App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } } @@ -1103,7 +1103,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (handJointIndex === NONE) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. - // TODO: Log error. + App.log(side, "ERROR: ToolMenu: Hand joint index isn't available!"); return; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 94b155e38c..b395596572 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -34,6 +34,7 @@ colorToolColor = { red: 128, green: 128, blue: 128 }, // Primary objects + App, Inputs, inputs = [], UI, @@ -79,21 +80,22 @@ Script.include("./modules/toolMenu.js"); - function log(message) { - print(APP_NAME + ": " + message); + function log(side, message) { + // Optional parameter: side. + var hand = "", + HAND_LETTERS = ["L", "R"]; + if (side === 0 || side === 1) { + hand = HAND_LETTERS[side] + " "; + } else { + message = side; + } + print(APP_NAME + ": " + hand + message); } function debug(side, message) { // Optional parameter: side. - var hand = "", - HAND_LETTERS = ["L", "R"]; if (DEBUG) { - if (side === 0 || side === 1) { - hand = HAND_LETTERS[side] + " "; - } else { - message = side; - } - log(hand + message); + log(side, message); } } @@ -101,6 +103,12 @@ return (hand + 1) % 2; } + App = { + log: log, + debug: debug, + DEBUG: DEBUG + }; + Inputs = function (side) { // A hand plus a laser. @@ -180,7 +188,7 @@ }; - UI = function (side, leftInputs, rightInputs, uiCommandCallback) { + UI = function (App, side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu and Create palette. var // Primary objects. @@ -197,9 +205,9 @@ return new UI(); } - toolIcon = new ToolIcon(otherHand(side)); - toolMenu = new ToolMenu(side, leftInputs, rightInputs, uiCommandCallback); - createPalette = new CreatePalette(side, leftInputs, rightInputs, uiCommandCallback); + toolIcon = new ToolIcon(App, otherHand(side)); + toolMenu = new ToolMenu(App, side, leftInputs, rightInputs, uiCommandCallback); + createPalette = new CreatePalette(App, side, leftInputs, rightInputs, uiCommandCallback); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -807,8 +815,8 @@ STATE_MACHINE[EDITOR_STATE_STRINGS[editorState]].exit(); STATE_MACHINE[EDITOR_STATE_STRINGS[state]].enter(); editorState = state; - } else if (DEBUG) { - log("ERROR: Null state transition: " + state + "!"); + } else { + log(side, "ERROR: Editor: Null state transition: " + state + "!"); } } @@ -897,7 +905,7 @@ setState(EDITOR_GRABBING); } } else { - debug(side, "ERROR: Unexpected condition in EDITOR_SEARCHING!"); + log(side, "ERROR: Editor: Unexpected condition in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: @@ -938,7 +946,7 @@ if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } else { - debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! A"); + log(side, "ERROR: Editor: Unexpected condition A in EDITOR_HIGHLIGHTING!"); } } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); @@ -967,7 +975,7 @@ } else if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { - debug(side, "ERROR: Unexpected condition in EDITOR_HIGHLIGHTING! B"); + log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); } break; case EDITOR_GRABBING: @@ -996,7 +1004,7 @@ setState(EDITOR_SEARCHING); } } else { - debug(side, "ERROR: Unexpected condition in EDITOR_GRABBING!"); + log(side, "ERROR: Editor: Unexpected condition in EDITOR_GRABBING!"); } break; case EDITOR_DIRECT_SCALING: @@ -1023,7 +1031,7 @@ // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); } else { - debug(side, "ERROR: Unexpected condition in EDITOR_DIRECT_SCALING!"); + log(side, "ERROR: Editor: Unexpected condition in EDITOR_DIRECT_SCALING!"); } break; case EDITOR_HANDLE_SCALING: @@ -1049,7 +1057,7 @@ // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); } else { - debug(side, "ERROR: Unexpected condition in EDITOR_HANDLE_SCALING!"); + log(side, "ERROR: Editor: Unexpected condition in EDITOR_HANDLE_SCALING!"); } break; case EDITOR_CLONING: @@ -1066,7 +1074,7 @@ setState(EDITOR_SEARCHING); } } else { - debug(side, "ERROR: Unexpected condition in EDITOR_CLONING!"); + log(side, "ERROR: Editor: Unexpected condition in EDITOR_CLONING!"); } break; case EDITOR_GROUPING: @@ -1353,7 +1361,7 @@ } break; default: - debug("ERROR: Unexpected command in onUICommand()! " + command); + log("ERROR: Unexpected command in onUICommand(): " + command); } } @@ -1432,7 +1440,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], onUICommand); + ui = new UI(App, otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], onUICommand); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From 6d61c188f8f7c6d2e97013c3d5266c5daecb1d41 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 21:22:11 +1200 Subject: [PATCH 168/504] Tighten up grabbing created entity --- scripts/vr-edit/modules/createPalette.js | 2 +- scripts/vr-edit/vr-edit.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 4df39eb7ae..ec1d4ed6d9 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -228,7 +228,7 @@ CreatePalette = function (App, side, leftInputs, rightInputs, uiCommandCallback) properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); Entities.addEntity(properties); - uiCommandCallback("autoGrab"); // TODO: Could pass entity ID through to autoGrab. + uiCommandCallback("autoGrab"); } wasTriggerClicked = isTriggerClicked; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index b395596572..74dcb30e0a 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1351,8 +1351,11 @@ colorToolColor = parameter; break; case "autoGrab": - editors[LEFT_HAND].enableAutoGrab(); - editors[RIGHT_HAND].enableAutoGrab(); + if (dominantHand === LEFT_HAND) { + editors[LEFT_HAND].enableAutoGrab(); + } else { + editors[RIGHT_HAND].enableAutoGrab(); + } break; case "setSliderValue": if (parameter !== undefined) { From 6977c35b6e32523b72b71d371d1fc43d7168bbac Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Aug 2017 22:17:46 +1200 Subject: [PATCH 169/504] Reduce number of Entities.getEntityProperties() calls --- scripts/vr-edit/utilities/utilities.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index 62343c5436..d5f9d556f8 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -48,15 +48,34 @@ if (typeof Entities.rootOf !== "function") { } if (typeof Entities.hasEditableRoot !== "function") { + Entities.hasEditableRootCache = { + CACHE_ENTRY_EXPIRY_TIME: 5000 // ms + }; + Entities.hasEditableRoot = function (entityID) { + if (Entities.hasEditableRootCache[entityID]) { + if (Date.now() - Entities.hasEditableRootCache[entityID].timeStamp + < Entities.hasEditableRootCache.CACHE_ENTRY_EXPIRY_TIME) { + return Entities.hasEditableRootCache[entityID].hasEditableRoot; + } + delete Entities.hasEditableRootCache[entityID]; + } + var EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"], NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"], - properties; + properties, + isEditable; + properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES); while (properties.parentID && properties.parentID !== Uuid.NULL) { properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); } - return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + isEditable = properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + Entities.hasEditableRootCache[entityID] = { + hasEditableRoot: isEditable, + timeStamp: Date.now() + }; + return isEditable; }; } From badd57c16506b47ab4f3426083e9902460f023a0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 09:26:05 +1200 Subject: [PATCH 170/504] Tidying --- scripts/vr-edit/vr-edit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 74dcb30e0a..6400ced0fb 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -431,7 +431,7 @@ selection.startEditing(); } - function stopEditing() { + function finishEditing() { selection.finishEditing(); } @@ -698,7 +698,7 @@ } function exitEditorGrabbing() { - stopEditing(); + finishEditing(); handles.clear(); laser.clearLength(); laser.enable(); @@ -924,7 +924,7 @@ highlightedEntityID = Entities.rootOf(intersection.entityID); doUpdateState = true; } - if (toolSelected === TOOL_SCALE !== wasScaleTool) { + if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { wasScaleTool = toolSelected === TOOL_SCALE; doUpdateState = true; } @@ -982,7 +982,7 @@ if (hand.valid() && isTriggerClicked && !isGripClicked) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // No transition. - if (toolSelected === TOOL_SCALE !== wasScaleTool) { + if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { updateState(); wasScaleTool = toolSelected === TOOL_SCALE; } From 37f57852da593b9f1413925183b95f6fe4d8212f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 09:56:47 +1200 Subject: [PATCH 171/504] Set hand highlight sphere size from grab radius --- scripts/vr-edit/modules/hand.js | 5 +++++ scripts/vr-edit/modules/highlights.js | 11 +++++++++-- scripts/vr-edit/vr-edit.js | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 92e8b06e10..35073173ff 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -97,6 +97,10 @@ Hand = function (side) { return intersection; } + function getNearGrabRadius() { + return NEAR_GRAB_RADIUS; + } + function update() { var gripValue, overlayID, @@ -213,6 +217,7 @@ Hand = function (side) { gripClicked: gripClicked, setGripClickedHandled: setGripClickedHandled, intersection: getIntersection, + getNearGrabRadius: getNearGrabRadius, update: update, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 333a68f04e..5e88575a73 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -22,7 +22,6 @@ Highlights = function (side) { GROUP_COLOR = { red: 220, green: 60, blue: 220 }, HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, - HAND_HIGHLIGHT_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, LEFT_HAND = 0; @@ -31,7 +30,7 @@ Highlights = function (side) { } handOverlay = Overlays.addOverlay("sphere", { - dimensions: HAND_HIGHLIGHT_DIMENSIONS, + dimension: Vec3.ZERO, parentID: Uuid.SELF, parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND ? "_CONTROLLER_LEFTHAND" @@ -44,6 +43,13 @@ Highlights = function (side) { visible: false }); + function setHandHighlightRadius(radius) { + var dimension = 2 * radius; + Overlays.editOverlay(handOverlay, { + dimensions: { x: dimension, y: dimension, z: dimension } + }); + } + function maybeAddEntityOverlay(index) { if (index >= entityOverlays.length) { entityOverlays.push(Overlays.addOverlay("cube", { @@ -115,6 +121,7 @@ Highlights = function (side) { HIGHLIGHT_COLOR: HIGHLIGHT_COLOR, SCALE_COLOR: SCALE_COLOR, GROUP_COLOR: GROUP_COLOR, + setHandHighlightRadius: setHandHighlightRadius, display: display, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6400ced0fb..6da49060e4 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -388,6 +388,8 @@ otherEditor = editor; // Object. laserOffset = laser.handOffset(); // Value. + + highlights.setHandHighlightRadius(hand.getNearGrabRadius()); } From c34344161ee5965f837fa3e18bc79df74a936bcd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 10:09:52 +1200 Subject: [PATCH 172/504] Improve App object handling --- scripts/vr-edit/modules/createPalette.js | 4 ++-- scripts/vr-edit/modules/toolIcon.js | 4 ++-- scripts/vr-edit/modules/toolMenu.js | 4 ++-- scripts/vr-edit/vr-edit.js | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index ec1d4ed6d9..d1c52d41f9 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global CreatePalette */ +/* global App, CreatePalette */ -CreatePalette = function (App, side, leftInputs, rightInputs, uiCommandCallback) { +CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index ea729271c0..9b0d81052d 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global ToolIcon */ +/* global App, ToolIcon */ -ToolIcon = function (App, side) { +ToolIcon = function (side) { // Tool icon displayed on non-dominant hand. "use strict"; diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index b61056a063..4198e9b7b0 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global ToolMenu */ +/* global App, ToolMenu */ -ToolMenu = function (App, side, leftInputs, rightInputs, uiCommandCallback) { +ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 6da49060e4..187bfd3fb7 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -188,7 +188,7 @@ }; - UI = function (App, side, leftInputs, rightInputs, uiCommandCallback) { + UI = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu and Create palette. var // Primary objects. @@ -205,9 +205,9 @@ return new UI(); } - toolIcon = new ToolIcon(App, otherHand(side)); - toolMenu = new ToolMenu(App, side, leftInputs, rightInputs, uiCommandCallback); - createPalette = new CreatePalette(App, side, leftInputs, rightInputs, uiCommandCallback); + toolIcon = new ToolIcon(otherHand(side)); + toolMenu = new ToolMenu(side, leftInputs, rightInputs, uiCommandCallback); + createPalette = new CreatePalette(side, leftInputs, rightInputs, uiCommandCallback); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -1445,7 +1445,7 @@ inputs[RIGHT_HAND] = new Inputs(RIGHT_HAND); // UI object. - ui = new UI(App, otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], onUICommand); + ui = new UI(otherHand(dominantHand), inputs[LEFT_HAND], inputs[RIGHT_HAND], onUICommand); // Editor objects. editors[LEFT_HAND] = new Editor(LEFT_HAND); From 821f5de3e325d9b19c4c31b3a9f7a0a28444cc41 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 10:20:36 +1200 Subject: [PATCH 173/504] Further reduce number of Entites.getEntityProperties() calls --- scripts/vr-edit/utilities/utilities.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index d5f9d556f8..8dacec8c2d 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -33,7 +33,19 @@ if (typeof Uuid.SELF !== "string") { } if (typeof Entities.rootOf !== "function") { + Entities.rootOfCache = { + CACHE_ENTRY_EXPIRY_TIME: 1000 // ms + }; + Entities.rootOf = function (entityID) { + if (Entities.rootOfCache[entityID]) { + if (Date.now() - Entities.rootOfCache[entityID].timeStamp + < Entities.rootOfCache.CACHE_ENTRY_EXPIRY_TIME) { + return Entities.rootOfCache[entityID].rootOf; + } + delete Entities.rootOfCache[entityID]; + } + var rootEntityID, entityProperties, PARENT_PROPERTIES = ["parentID"]; @@ -43,6 +55,11 @@ if (typeof Entities.rootOf !== "function") { rootEntityID = entityProperties.parentID; entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); } + + Entities.rootOfCache[entityID] = { + rootOf: rootEntityID, + timeStamp: Date.now() + }; return rootEntityID; }; } @@ -71,6 +88,7 @@ if (typeof Entities.hasEditableRoot !== "function") { properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES); } isEditable = properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1; + Entities.hasEditableRootCache[entityID] = { hasEditableRoot: isEditable, timeStamp: Date.now() From 0b063926b6f63807cc25f76415565d2597e21fbd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 11:41:33 +1200 Subject: [PATCH 174/504] Move image slider to Color options --- scripts/vr-edit/modules/toolMenu.js | 34 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 4198e9b7b0..6bb2b8f9e5 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -182,7 +182,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "imageSlider": { overlay: "cube", properties: { - dimensions: { x: 0.02, y: 0.1, z: 0.01 }, + dimensions: { x: 0.01, y: 0.05, z: 0.01 }, localRotation: Quat.ZERO, color: { red: 128, green: 128, blue: 128 }, alpha: 1.0, @@ -198,7 +198,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { overlay: "shape", properties: { shape: "Cone", - dimensions: { x: 0.01, y: 0.01, z: 0.01 }, + dimensions: { x: 0.005, y: 0.005, z: 0.005 }, localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), color: { red: 180, green: 180, blue: 180 }, alpha: 1.0, @@ -261,6 +261,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.055, y: 0.0, z: -0.005 } } }, + { + id: "colorSlider", + type: "imageSlider", + properties: { + localPosition: { x: 0.025, y: -0.025, z: -0.005 }, + color: { red: 255, green: 0, blue: 0 } + }, + useBaseColor: true, + imageURL: "../assets/slider-white.png", + imageOverlayURL: "../assets/slider-white-alpha.png", + callback: { + method: "setSliderValue" + } + }, { id: "colorSwatch1", type: "swatch", @@ -486,20 +500,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: { method: "setSliderValue" } - }, - { - id: "colorSlider", - type: "imageSlider", - properties: { - localPosition: { x: 0.02, y: 0.0, z: -0.005 }, - color: { red: 255, green: 0, blue: 0 } - }, - useBaseColor: true, - imageURL: "../assets/slider-white.png", - imageOverlayURL: "../assets/slider-white-alpha.png", - callback: { - method: "setSliderValue" - } } ] }, @@ -732,6 +732,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (optionsItems[i].imageURL) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = Script.resolvePath(optionsItems[i].imageURL); + delete childProperties.dimensions; childProperties.scale = properties.dimensions.y; imageOffset += IMAGE_OFFSET; childProperties.emissive = true; @@ -748,6 +749,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); childProperties.drawInFront = true; // TODO: Work-around for rendering bug; remove when bug fixed. + delete childProperties.dimensions; childProperties.scale = properties.dimensions.y; imageOffset += IMAGE_OFFSET; childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; From c82a4d6d56e639b98ca9067812f835ddc701bafa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 13:15:07 +1200 Subject: [PATCH 175/504] Layout mockup of Physics options --- scripts/vr-edit/modules/toolMenu.js | 68 ++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6bb2b8f9e5..3a1a60fca1 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -491,15 +491,74 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.055, y: 0.0, z: -0.005 } } }, + { - id: "physicsSlider", + id: "gravityToggle", + type: "panel", + properties: { + localPosition: { x: -0.0325, y: -0.03, z: -0.005 }, + dimensions: { x: 0.03, y: 0.02, z: 0.01 } + } + }, + { + id: "grabToggle", + type: "panel", + properties: { + localPosition: { x: -0.0325, y: -0.005, z: -0.005 }, + dimensions: { x: 0.03, y: 0.02, z: 0.01 } + } + }, + { + id: "collideToggle", + type: "panel", + properties: { + localPosition: { x: -0.0325, y: 0.02, z: -0.005 }, + dimensions: { x: 0.03, y: 0.02, z: 0.01 } + } + }, + + { + id: "presets", + type: "panel", + properties: { + localPosition: { x: 0.016, y: -0.03, z: -0.005 }, + dimensions: { x: 0.06, y: 0.02, z: 0.01 } + } + }, + { + id: "gravitySlider", type: "barSlider", properties: { - localPosition: { x: -0.02, y: 0.0, z: -0.005 } + localPosition: { x: -0.007, y: 0.016, z: -0.005 }, + dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, callback: { method: "setSliderValue" } + }, + { + id: "bounceSlider", + type: "panel", + properties: { + localPosition: { x: 0.009, y: 0.016, z: -0.005 }, + dimensions: { x: 0.014, y: 0.06, z: 0.01 } + } + }, + { + id: "dampingSlider", + type: "panel", + properties: { + localPosition: { x: 0.024, y: 0.016, z: -0.005 }, + dimensions: { x: 0.014, y: 0.06, z: 0.01 } + } + }, + { + id: "densitySlider", + type: "panel", + properties: { + localPosition: { x: 0.039, y: 0.016, z: -0.005 }, + dimensions: { x: 0.014, y: 0.06, z: 0.01 } + } } ] }, @@ -715,12 +774,17 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } if (optionsItems[i].type === "barSlider") { + // Initial value = 0. optionsOverlaysAuxiliaries[i] = {}; auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); + auxiliaryProperties.dimensions = Vec3.multiplyVbyV({ x: 1, y: 0, z: 0 }, properties.dimensions); + auxiliaryProperties.localPosition = Vec3.ZERO; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, auxiliaryProperties); auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); + auxiliaryProperties.dimensions = properties.dimensions; + auxiliaryProperties.localPosition = Vec3.ZERO; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, auxiliaryProperties); From ee225e6a8d15c6e88c84b188ffd116a47c75a5e5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 18:23:20 +1200 Subject: [PATCH 176/504] Implement toggle buttons --- scripts/vr-edit/modules/toolMenu.js | 100 +++++++++++++++++++++++++--- scripts/vr-edit/vr-edit.js | 2 +- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3a1a60fca1..042635bbc7 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -85,6 +85,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, + "toggleButton": { + overlay: "cube", + properties: { + dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + localRotation: Quat.ZERO, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + onColor: { red: 100, green: 240, blue: 100 }, + offColor: { red: 64, green: 64, blue: 64 } + }, "swatch": { overlay: "cube", properties: { @@ -209,7 +222,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, - BUTTON_UI_ELEMENTS = ["button", "swatch"], + BUTTON_UI_ELEMENTS = ["button", "toggleButton", "swatch"], BUTTON_PRESS_DELTA = { x: 0, y: 0, z: 0.004 }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], @@ -492,28 +505,70 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, + { + id: "propertiesLabel", + type: "label", + properties: { + text: "PROPERTIES", + lineHeight: 0.005, + localPosition: { x: -0.0325, y: -0.0475, z: -0.0075} + } + }, { id: "gravityToggle", - type: "panel", + type: "toggleButton", properties: { localPosition: { x: -0.0325, y: -0.03, z: -0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } + }, + label: "GRAVITY", + setting: { + key: "VREdit.physicsTool.gravityOn", + // No property + defaultValue: false, + command: "setGravity" + }, + command: { + method: "setGravity", + parameter: "gravityToggle" } }, { id: "grabToggle", - type: "panel", + type: "toggleButton", properties: { localPosition: { x: -0.0325, y: -0.005, z: -0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } + }, + label: " GRAB", + setting: { + key: "VREdit.physicsTool.grabOn", + // No property + defaultValue: false, + command: "setGrab" + }, + command: { + method: "setGrab", + parameter: "grabToggle" } }, { id: "collideToggle", - type: "panel", + type: "toggleButton", properties: { localPosition: { x: -0.0325, y: 0.02, z: -0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } + }, + label: "COLLIDE", + setting: { + key: "VREdit.physicsTool.collideOn", + // No property + defaultValue: false, + command: "setCollide" + }, + command: { + method: "setCollide", + parameter: "collideToggle" } }, @@ -753,14 +808,21 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (value === "") { value = optionsItems[i].setting.defaultValue; } - if (value) { + if (value !== "") { properties[optionsItems[i].setting.property] = value; if (optionsItems[i].type === "swatch") { // Special case for when swatch color is defined. properties.solid = true; } - if (optionsItems[i].setting.callback) { - uiCommandCallback(optionsItems[i].setting.callback.method, value); + if (optionsItems[i].type === "toggleButton") { + // Store value in optionsSettings rather than using overlay property. + optionsSettings[optionsItems[i].id].value = value; + properties.color = value + ? UI_ELEMENTS[optionsItems[i].type].onColor + : UI_ELEMENTS[optionsItems[i].type].offColor; + } + if (optionsItems[i].setting.command) { + uiCommandCallback(optionsItems[i].setting.command, value); } } } @@ -866,15 +928,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function doCommand(command, parameter) { var parameters, - overlayID, + index, hasColor, value; switch (command) { + case "setColorPerSwatch": parameters = parameter.split("."); - overlayID = optionsOverlaysIDs.indexOf(parameters[0]); - hasColor = Overlays.getProperty(optionsOverlays[overlayID], "solid"); + index = optionsOverlaysIDs.indexOf(parameters[0]); + hasColor = Overlays.getProperty(optionsOverlays[index], "solid"); if (hasColor) { // Swatch has a color; set current fill color to swatch color. value = evaluateParameter(parameter); @@ -888,7 +951,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } else { // Swatch has no color; set swatch color to current fill color. value = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], "color"); - Overlays.editOverlay(optionsOverlays[overlayID], { + Overlays.editOverlay(optionsOverlays[index], { color: value, solid: true }); @@ -897,6 +960,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } break; + case "setColorFromPick": Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { color: parameter @@ -905,6 +969,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Settings.setValue(optionsSettings.currentColor.key, parameter); } break; + + case "setGravity": + case "setGrab": + case "setCollide": + value = !optionsSettings[parameter].value; + optionsSettings[parameter].value = value; + Settings.setValue(optionsSettings[parameter].key, value); + index = optionsOverlaysIDs.indexOf(parameter); + Overlays.editOverlay(optionsOverlays[index], { + color: value ? UI_ELEMENTS[optionsItems[index].type].onColor : UI_ELEMENTS[optionsItems[index].type].offColor + }); + uiCommandCallback(command, value); + break; + default: App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 187bfd3fb7..1935bd0443 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1366,7 +1366,7 @@ } break; default: - log("ERROR: Unexpected command in onUICommand(): " + command); + log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); } } From a96d9ab85b76ffe63c7a520890de90b60820b5ab Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 22:11:34 +1200 Subject: [PATCH 177/504] Apply physics toggle button values --- scripts/vr-edit/modules/selection.js | 57 +++++++++++++++++++++++----- scripts/vr-edit/vr-edit.js | 33 +++++++++++++++- 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 2b1e7e5a37..561dafedb5 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -24,7 +24,10 @@ Selection = function (side) { scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"]; + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"], + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }; + if (!this instanceof Selection) { return new Selection(side); @@ -36,7 +39,7 @@ Selection = function (side) { var children, properties, SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "parentID", "localPosition", - "dynamic", "collisionless"], + "dynamic", "collisionless", "userData"], i, length; @@ -50,7 +53,8 @@ Selection = function (side) { rotation: properties.rotation, dimensions: properties.dimensions, dynamic: properties.dynamic, - collisionless: properties.collisionless + collisionless: properties.collisionless, + userData: properties.userData }); children = Entities.getChildrenIDs(id); @@ -187,8 +191,6 @@ Selection = function (side) { function finishEditing() { var firstDynamicEntityID = null, properties, - VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }, count, i; @@ -206,8 +208,8 @@ Selection = function (side) { // If dynamic with gravity, and velocity is zero, give the entity set a little kick to set off physics. if (firstDynamicEntityID) { properties = Entities.getEntityProperties(firstDynamicEntityID, ["velocity", "gravity"]); - if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < VELOCITY_THRESHOLD) { - Entities.editEntity(firstDynamicEntityID, { velocity: VELOCITY_KICK }); + if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(firstDynamicEntityID, { velocity: DYNAMIC_VELOCITY_KICK }); } } } @@ -365,8 +367,45 @@ Selection = function (side) { return properties.color; } - function applyPhysics() { - // TODO + function updatePhysicsUserData(userDataString, physicsUserData) { + var userData = {}; + + if (userDataString !== "") { + try { + userData = JSON.parse(userDataString); + } catch (e) { + App.log(side, "ERROR: Invalid userData in entity being updated! " + userDataString); + } + } + + if (!userData.hasOwnProperty("grabbableKey")) { + userData.grabbableKey = {}; + } + userData.grabbableKey.grabbable = physicsUserData.grabbableKey.grabbable; + + return JSON.stringify(userData); + } + + function applyPhysics(physicsProperties) { + // Applies physics to the current selection (i.e., the selection made when entity was trigger-clicked to apply physics). + var properties, + i, + length; + + properties = Object.clone(physicsProperties); + + for (i = 0, length = selection.length; i < length; i += 1) { + properties.userData = updatePhysicsUserData(selection[i].userData, physicsProperties.userData); + Entities.editEntity(selection[i].id, properties); + } + + if (physicsProperties.dynamic) { + // Give dynamic entities with zero a little kick to set off physics. + properties = Entities.getEntityProperties(selection[0].id, ["velocity"]); + if (Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(selection[0].id, { velocity: DYNAMIC_VELOCITY_KICK }); + } + } } function clear() { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 1935bd0443..d24c7a986e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -32,6 +32,7 @@ TOOL_DELETE = 7, toolSelected = TOOL_NONE, colorToolColor = { red: 128, green: 128, blue: 128 }, + physicsToolPhysics = { userData: { grabbableKey: {} } }, // Primary objects App, @@ -886,6 +887,7 @@ } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { + setState(EDITOR_HIGHLIGHTING); selection.applyColor(colorToolColor); } else if (toolSelected === TOOL_PICK_COLOR) { color = selection.getColor(intersection.entityID); @@ -898,7 +900,8 @@ ui.setToolIcon(ui.COLOR_TOOL); ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_PHYSICS) { - selection.applyPhysics(); + setState(EDITOR_HIGHLIGHTING); + selection.applyPhysics(physicsToolPhysics); } else if (toolSelected === TOOL_DELETE) { setState(EDITOR_HIGHLIGHTING); selection.deleteEntities(); @@ -967,7 +970,7 @@ ui.setToolIcon(ui.COLOR_TOOL); ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_PHYSICS) { - selection.applyPhysics(); + selection.applyPhysics(physicsToolPhysics); } else if (toolSelected === TOOL_DELETE) { selection.deleteEntities(); setState(EDITOR_SEARCHING); @@ -1338,12 +1341,14 @@ ui.setToolIcon(ui.DELETE_TOOL); ui.updateUIEntities(); break; + case "groupButton": grouping.group(); break; case "ungroupButton": grouping.ungroup(); break; + case "setColor": if (toolSelected === TOOL_PICK_COLOR) { toolSelected = TOOL_COLOR; @@ -1352,6 +1357,29 @@ ui.setToolColor(parameter); colorToolColor = parameter; break; + + case "setGravity": + if (parameter) { + physicsToolPhysics.gravity = { x: 0, y: -9.8, z: 0 }; // Earth gravity. + physicsToolPhysics.dynamic = true; + } else { + physicsToolPhysics.gravity = Vec3.ZERO; + physicsToolPhysics.dynamic = false; + } + break; + case "setGrab": + physicsToolPhysics.userData.grabbableKey.grabbable = parameter; + break; + case "setCollide": + if (parameter) { + physicsToolPhysics.collisionless = false; + physicsToolPhysics.collidesWith = "static,dynamic,kinematic,myAvatar,otherAvatar"; + } else { + physicsToolPhysics.collisionless = true; + physicsToolPhysics.collidesWith = ""; + } + break; + case "autoGrab": if (dominantHand === LEFT_HAND) { editors[LEFT_HAND].enableAutoGrab(); @@ -1359,6 +1387,7 @@ editors[RIGHT_HAND].enableAutoGrab(); } break; + case "setSliderValue": if (parameter !== undefined) { // TODO From 2bf0ea1dff6ab2d4ada712e4be7c31d71c556867 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 11 Aug 2017 22:31:04 +1200 Subject: [PATCH 178/504] Fix display artifact that occurs when overlay dimension is 0 --- scripts/vr-edit/modules/toolMenu.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 042635bbc7..1dd0eb486c 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -227,6 +227,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], SLIDER_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, + MIN_BAR_SLIDER_DIMENSION = 0.0001, + OPTONS_PANELS = { groupOptions: [ @@ -839,8 +841,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Initial value = 0. optionsOverlaysAuxiliaries[i] = {}; auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); - auxiliaryProperties.dimensions = Vec3.multiplyVbyV({ x: 1, y: 0, z: 0 }, properties.dimensions); - auxiliaryProperties.localPosition = Vec3.ZERO; + auxiliaryProperties.dimensions = { + x: properties.dimensions.x, + y: MIN_BAR_SLIDER_DIMENSION, // Avoid visual artifact if 0. + z: properties.dimensions.z + }; + auxiliaryProperties.localPosition = { + x: 0, + y: properties.dimensions.y / 2, + z: 0 + }; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, auxiliaryProperties); @@ -1173,11 +1183,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { otherFraction = 1.0 - fraction; Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, - dimensions: { x: overlayDimensions.x, y: fraction * overlayDimensions.y, z: overlayDimensions.z } + dimensions: { + x: overlayDimensions.x, + y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), // Avoid visual artifact if 0. + z: overlayDimensions.z + } }); Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].remainder, { localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, - dimensions: { x: overlayDimensions.x, y: otherFraction * overlayDimensions.y, z: overlayDimensions.z } + dimensions: { + x: overlayDimensions.x, + y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), // Avoid visual artifact if 0. + z: overlayDimensions.z + } }); uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); From 541402b559f6872a10da521c36076753e5735d7c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Aug 2017 14:56:22 +1200 Subject: [PATCH 179/504] Distinguish properly between intersected and root entities --- scripts/vr-edit/modules/selection.js | 26 ++++--- scripts/vr-edit/vr-edit.js | 104 ++++++++++++++++----------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 561dafedb5..d5965b3552 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -16,7 +16,7 @@ Selection = function (side) { "use strict"; var selection = [], - selectedEntityID = null, + intersectedEntityID = null, rootEntityID = null, rootPosition, rootOrientation, @@ -65,12 +65,14 @@ Selection = function (side) { } } - function select(entityID) { + function select(intersectionEntityID) { var entityProperties, PARENT_PROPERTIES = ["parentID", "position", "rotation", "dymamic", "collisionless"]; + intersectedEntityID = intersectionEntityID; + // Find root parent. - rootEntityID = Entities.rootOf(entityID); + rootEntityID = Entities.rootOf(intersectedEntityID); // Selection position and orientation is that of the root entity. entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); @@ -80,8 +82,10 @@ Selection = function (side) { // Find all children. selection = []; traverseEntityTree(rootEntityID, selection); + } - selectedEntityID = entityID; + function getIntersectedEntityID() { + return intersectedEntityID; } function getRootEntityID() { @@ -263,7 +267,7 @@ Selection = function (side) { } function finishDirectScaling() { - select(selectedEntityID); // Refresh. + select(intersectedEntityID); // Refresh. } function startHandleScaling() { @@ -296,19 +300,23 @@ Selection = function (side) { } function finishHandleScaling() { - select(selectedEntityID); // Refresh. + select(intersectedEntityID); // Refresh. } function cloneEntities() { var parentIDIndexes = [], + intersectedEntityIndex = 0, parentID, properties, i, j, length; - // Map parent IDs. + // Map parent IDs; find intersectedEntityID's index. for (i = 1, length = selection.length; i < length; i += 1) { + if (selection[i].id === intersectedEntityID) { + intersectedEntityIndex = i; + } parentID = selection[i].parentID; for (j = 0; j < i; j += 1) { if (parentID === selection[j].id) { @@ -327,6 +335,7 @@ Selection = function (side) { selection[i].id = Entities.addEntity(properties); } + intersectedEntityID = selection[intersectedEntityIndex].id; rootEntityID = selection[0].id; } @@ -410,7 +419,7 @@ Selection = function (side) { function clear() { selection = []; - selectedEntityID = null; + intersectedEntityID = null; rootEntityID = null; } @@ -429,6 +438,7 @@ Selection = function (side) { select: select, selection: getSelection, count: count, + intersectedEntityID: getIntersectedEntityID, rootEntityID: getRootEntityID, boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d24c7a986e..9664a346cb 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -337,7 +337,8 @@ // State machine. STATE_MACHINE, - highlightedEntityID = null, // Root entity of highlighted entity set. + intersectedEntityID = null, // Intersected entity of highlighted entity set. + rootEntityID = null, // Root entity of highlighted entity set. wasScaleTool = false, isOtherEditorEditingEntityID = false, isTriggerClicked = false, @@ -408,18 +409,22 @@ return handles.isHandle(overlayID); } - function isEditing(rootEntityID) { - // rootEntityID is an optional parameter. + function isEditing(aRootEntityID) { + // aRootEntityID is an optional parameter. return editorState > EDITOR_HIGHLIGHTING - && (rootEntityID === undefined || rootEntityID === selection.rootEntityID()); + && (aRootEntityID === undefined || aRootEntityID === rootEntityID); } function isScaling() { return editorState === EDITOR_DIRECT_SCALING || editorState === EDITOR_HANDLE_SCALING; } - function rootEntityID() { - return selection.rootEntityID(); + function getIntersectedEntityID() { + return intersectedEntityID; + } + + function getRootEntityID() { + return rootEntityID; } @@ -634,6 +639,8 @@ function enterEditorSearching() { selection.clear(); + intersectedEntityID = null; + rootEntityID = null; hoveredOverlayID = intersection.overlayID; otherEditor.hoverHandle(hoveredOverlayID); } @@ -650,21 +657,21 @@ } function enterEditorHighlighting() { - selection.select(highlightedEntityID); - if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { + selection.select(intersectedEntityID); + if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) + toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } - isOtherEditorEditingEntityID = otherEditor.isEditing(highlightedEntityID); + isOtherEditorEditingEntityID = otherEditor.isEditing(rootEntityID); wasScaleTool = toolSelected === TOOL_SCALE; } function updateEditorHighlighting() { - selection.select(highlightedEntityID); - if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(highlightedEntityID)) { + selection.select(intersectedEntityID); + if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), - toolSelected === TOOL_SCALE || otherEditor.isEditing(highlightedEntityID) + toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } else { highlights.clear(); @@ -678,23 +685,23 @@ } function enterEditorGrabbing() { - selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. if (intersection.laserIntersected) { laser.setLength(laser.length()); } else { laser.disable(); } if (toolSelected === TOOL_SCALE) { - handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); + handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); } startEditing(); wasScaleTool = toolSelected === TOOL_SCALE; } function updateEditorGrabbing() { - selection.select(highlightedEntityID); + selection.select(intersectedEntityID); if (toolSelected === TOOL_SCALE) { - handles.display(highlightedEntityID, selection.boundingBox(), selection.count() > 1); + handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); } else { handles.clear(); } @@ -708,7 +715,7 @@ } function enterEditorDirectScaling() { - selection.select(highlightedEntityID); // In case need to transition to EDITOR_GRABBING. + selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -729,7 +736,7 @@ } function enterEditorHandleScaling() { - selection.select(highlightedEntityID); // In case need to transition to EDITOR_GRABBING. + selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -750,9 +757,11 @@ } function enterEditorCloning() { - selection.select(highlightedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. selection.cloneEntities(); - highlightedEntityID = selection.rootEntityID(); + intersectedEntityID = selection.intersectedEntityID(); + rootEntityID = selection.rootEntityID(); + intersectedEntityID = rootEntityID; } function exitEditorCloning() { @@ -760,7 +769,7 @@ } function enterEditorGrouping() { - if (!grouping.includes(highlightedEntityID)) { + if (!grouping.includes(rootEntityID)) { highlights.display(false, selection.selection(), highlights.GROUP_COLOR); } grouping.toggle(selection.selection()); @@ -869,16 +878,19 @@ setState(EDITOR_IDLE); } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { - highlightedEntityID = otherEditor.rootEntityID(); + intersectedEntityID = otherEditor.intersectedEntityID(); + rootEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && intersection.editableEntity && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab) { - highlightedEntityID = Entities.rootOf(intersection.entityID); + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); } else if (intersection.entityID && intersection.editableEntity && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { - highlightedEntityID = Entities.rootOf(intersection.entityID); - if (otherEditor.isEditing(highlightedEntityID)) { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + if (otherEditor.isEditing(rootEntityID)) { if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } @@ -917,16 +929,17 @@ if (hand.valid() && intersection.entityID && intersection.editableEntity && !(!wasTriggerClicked && isTriggerClicked - && (!otherEditor.isEditing(highlightedEntityID) || toolSelected !== TOOL_SCALE)) + && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID && otherEditor.isHandle(intersection.overlayID))) { // No transition. doUpdateState = false; - if (otherEditor.isEditing(highlightedEntityID) !== isOtherEditorEditingEntityID) { + if (otherEditor.isEditing(rootEntityID) !== isOtherEditorEditingEntityID) { doUpdateState = true; } - if (Entities.rootOf(intersection.entityID) !== highlightedEntityID) { - highlightedEntityID = Entities.rootOf(intersection.entityID); + if (Entities.rootOf(intersection.entityID) !== rootEntityID) { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); doUpdateState = true; } if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { @@ -943,11 +956,13 @@ setState(EDITOR_IDLE); } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked && otherEditor.isHandle(intersection.overlayID)) { - highlightedEntityID = otherEditor.rootEntityID(); + intersectedEntityID = otherEditor.intersectedEntityID(); + rootEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && intersection.editableEntity && !wasTriggerClicked && isTriggerClicked) { - highlightedEntityID = Entities.rootOf(intersection.entityID); // May be a different entityID. - if (otherEditor.isEditing(highlightedEntityID)) { + intersectedEntityID = intersection.entityID; // May be a different entityID. + rootEntityID = Entities.rootOf(intersectedEntityID); + if (otherEditor.isEditing(rootEntityID)) { if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } else { @@ -998,7 +1013,8 @@ setState(EDITOR_IDLE); } else if (!isTriggerClicked) { if (intersection.entityID && intersection.editableEntity) { - highlightedEntityID = Entities.rootOf(intersection.entityID); + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); } else { setState(EDITOR_SEARCHING); @@ -1014,7 +1030,7 @@ break; case EDITOR_DIRECT_SCALING: if (hand.valid() && isTriggerClicked - && (otherEditor.isEditing(highlightedEntityID) || otherEditor.isHandle(intersection.overlayID))) { + && (otherEditor.isEditing(rootEntityID) || otherEditor.isHandle(intersection.overlayID))) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while // scaling with two hands. @@ -1029,10 +1045,11 @@ if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { - highlightedEntityID = Entities.rootOf(intersection.entityID); + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); } - } else if (!otherEditor.isEditing(highlightedEntityID)) { + } else if (!otherEditor.isEditing(rootEntityID)) { // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); } else { @@ -1040,7 +1057,7 @@ } break; case EDITOR_HANDLE_SCALING: - if (hand.valid() && isTriggerClicked && otherEditor.isEditing(highlightedEntityID)) { + if (hand.valid() && isTriggerClicked && otherEditor.isEditing(rootEntityID)) { // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while // scaling with two hands. @@ -1055,10 +1072,11 @@ if (!intersection.entityID || !intersection.editableEntity) { setState(EDITOR_SEARCHING); } else { - highlightedEntityID = Entities.rootOf(intersection.entityID); + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); } - } else if (!otherEditor.isEditing(highlightedEntityID)) { + } else if (!otherEditor.isEditing(rootEntityID)) { // Grab highlightEntityID that was scaling and has already been set. setState(EDITOR_GRABBING); } else { @@ -1073,7 +1091,8 @@ setState(EDITOR_IDLE); } else if (!isTriggerClicked) { if (intersection.entityID && intersection.editableEntity) { - highlightedEntityID = Entities.rootOf(intersection.entityID); + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); } else { setState(EDITOR_SEARCHING); @@ -1150,7 +1169,8 @@ isHandle: isHandle, isEditing: isEditing, isScaling: isScaling, - rootEntityID: rootEntityID, + intersectedEntityID: getIntersectedEntityID, + rootEntityID: getRootEntityID, startDirectScaling: startDirectScaling, updateDirectScaling: updateDirectScaling, stopDirectScaling: stopDirectScaling, From c281d5f94bd4cd8064859cebb3f2151962872ff4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Aug 2017 15:46:53 +1200 Subject: [PATCH 180/504] Highlight and color single entities with Color tool --- scripts/vr-edit/modules/highlights.js | 17 +++++++++++----- scripts/vr-edit/modules/selection.js | 28 +++++++++++++++++++++++---- scripts/vr-edit/vr-edit.js | 15 ++++++++++---- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 5e88575a73..7c8cb0e99f 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -75,7 +75,8 @@ Highlights = function (side) { }); } - function display(handIntersected, selection, overlayColor) { + function display(handIntersected, selection, entityIndex, overlayColor) { + // Displays highlight for just entityIndex if non-null, otherwise highlights whole selection. var i, length; @@ -85,10 +86,16 @@ Highlights = function (side) { visible: handIntersected }); - // Add/edit entity overlay. - for (i = 0, length = selection.length; i < length; i += 1) { - maybeAddEntityOverlay(i); - editEntityOverlay(i, selection[i], overlayColor); + if (entityIndex !== null) { + // Add/edit entity overlay for just entityIndex. + maybeAddEntityOverlay(0); + editEntityOverlay(0, selection[entityIndex], overlayColor); + } else { + // Add/edit entity overlays for all entities in selection. + for (i = 0, length = selection.length; i < length; i += 1) { + maybeAddEntityOverlay(i); + editEntityOverlay(i, selection[i], overlayColor); + } } // Delete extra entity overlays. diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index d5965b3552..935841698f 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -17,6 +17,7 @@ Selection = function (side) { var selection = [], intersectedEntityID = null, + intersectedEntityIndex, rootEntityID = null, rootPosition, rootOrientation, @@ -57,6 +58,10 @@ Selection = function (side) { userData: properties.userData }); + if (id === intersectedEntityID) { + intersectedEntityIndex = result.length - 1; + } + children = Entities.getChildrenIDs(id); for (i = 0, length = children.length; i < length; i += 1) { if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { @@ -88,6 +93,10 @@ Selection = function (side) { return intersectedEntityID; } + function getIntersectedEntityIndex() { + return intersectedEntityIndex; + } + function getRootEntityID() { return rootEntityID; } @@ -339,17 +348,27 @@ Selection = function (side) { rootEntityID = selection[0].id; } - function applyColor(color) { + function applyColor(color, isApplyToAll) { // Entities without a color property simply ignore the edit. var properties, isError = true, i, length; - for (i = 0, length = selection.length; i < length; i += 1) { - properties = Entities.getEntityProperties(selection[i].id, "color"); + if (isApplyToAll) { + for (i = 0, length = selection.length; i < length; i += 1) { + properties = Entities.getEntityProperties(selection[i].id, "color"); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { + Entities.editEntity(selection[i].id, { + color: color + }); + isError = false; + } + } + } else { + properties = Entities.getEntityProperties(intersectedEntityID, "type"); if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { - Entities.editEntity(selection[i].id, { + Entities.editEntity(intersectedEntityID, { color: color }); isError = false; @@ -439,6 +458,7 @@ Selection = function (side) { selection: getSelection, count: count, intersectedEntityID: getIntersectedEntityID, + intersectedEntityIndex: getIntersectedEntityIndex, rootEntityID: getRootEntityID, boundingBox: getBoundingBox, getPositionAndOrientation: getPositionAndOrientation, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 9664a346cb..471bd2c24e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -660,6 +660,7 @@ selection.select(intersectedEntityID); if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), + toolSelected === TOOL_COLOR ? selection.intersectedEntityIndex() : null, toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } @@ -671,6 +672,7 @@ selection.select(intersectedEntityID); if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), + toolSelected === TOOL_COLOR ? selection.intersectedEntityIndex() : null, toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } else { @@ -770,7 +772,7 @@ function enterEditorGrouping() { if (!grouping.includes(rootEntityID)) { - highlights.display(false, selection.selection(), highlights.GROUP_COLOR); + highlights.display(false, selection.selection(), null, highlights.GROUP_COLOR); } grouping.toggle(selection.selection()); } @@ -849,6 +851,7 @@ function update() { var previousState = editorState, doUpdateState, + doRefreshSelection, color; intersection = getIntersection(); @@ -900,7 +903,7 @@ setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { setState(EDITOR_HIGHLIGHTING); - selection.applyColor(colorToolColor); + selection.applyColor(colorToolColor, false); } else if (toolSelected === TOOL_PICK_COLOR) { color = selection.getColor(intersection.entityID); if (color) { @@ -946,6 +949,10 @@ wasScaleTool = toolSelected === TOOL_SCALE; doUpdateState = true; } + if (toolSelected === TOOL_COLOR && intersection.entityID !== intersectedEntityID) { + intersectedEntityID = intersection.entityID; + doUpdateState = true; + } if (doUpdateState) { updateState(); } @@ -973,7 +980,7 @@ } else if (toolSelected === TOOL_GROUP) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { - selection.applyColor(colorToolColor); + selection.applyColor(colorToolColor, false); } else if (toolSelected === TOOL_PICK_COLOR) { color = selection.getColor(intersection.entityID); if (color) { @@ -1256,7 +1263,7 @@ exludedrightRootEntityID = rightRootEntityID; } - highlights.display(false, groups.selection(excludedRootEntityIDs), highlights.GROUP_COLOR); + highlights.display(false, groups.selection(excludedRootEntityIDs), null, highlights.GROUP_COLOR); hasSelectionChanged = false; } From a5407398e701c799a2c10a03c835ba04b93cd318 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Aug 2017 16:21:55 +1200 Subject: [PATCH 181/504] Fix Group tool highlights not being cleared when switch to other tools --- scripts/vr-edit/vr-edit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 471bd2c24e..ca2d429ab5 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1346,6 +1346,7 @@ ui.updateUIEntities(); break; case "colorTool": + grouping.clear(); toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); ui.setToolColor(parameter); @@ -1353,11 +1354,13 @@ ui.updateUIEntities(); break; case "pickColorTool": + grouping.clear(); toolSelected = TOOL_PICK_COLOR; ui.setToolIcon(ui.PICK_COLOR_TOOL); ui.updateUIEntities(); break; case "physicsTool": + grouping.clear(); toolSelected = TOOL_PHYSICS; ui.setToolIcon(ui.PHYSICS_TOOL); ui.updateUIEntities(); From bc00bcd02ceed1b0565d16bc3cbf047bc84c98d4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 12 Aug 2017 16:59:28 +1200 Subject: [PATCH 182/504] For development testing, apply physics to the entity intersected --- scripts/vr-edit/modules/selection.js | 10 ++++++++++ scripts/vr-edit/vr-edit.js | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 935841698f..1682ce0bbc 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -415,6 +415,15 @@ Selection = function (side) { } function applyPhysics(physicsProperties) { + // TODO: For development testing, apply physics to the currently intersected entity. + var properties; + + properties = Object.clone(physicsProperties); + properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); + Entities.editEntity(selection[intersectedEntityIndex].id, properties); + + // TODO: Original functionality applied physics to all entities in the selection. + /* // Applies physics to the current selection (i.e., the selection made when entity was trigger-clicked to apply physics). var properties, i, @@ -434,6 +443,7 @@ Selection = function (side) { Entities.editEntity(selection[0].id, { velocity: DYNAMIC_VELOCITY_KICK }); } } + */ } function clear() { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index ca2d429ab5..c10bb748f7 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -851,7 +851,6 @@ function update() { var previousState = editorState, doUpdateState, - doRefreshSelection, color; intersection = getIntersection(); @@ -953,6 +952,12 @@ intersectedEntityID = intersection.entityID; doUpdateState = true; } + // TODO: For development testing, update intersectedEntityID so that physics can be applied to it. + if ((toolSelected === TOOL_COLOR || toolSelected === TOOL_PHYSICS) + && intersection.entityID !== intersectedEntityID) { + intersectedEntityID = intersection.entityID; + doUpdateState = true; + } if (doUpdateState) { updateState(); } From 71993972a214f618c5e3a3895a52c3ab2a16a8f1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 13 Aug 2017 14:04:01 +1200 Subject: [PATCH 183/504] Improve ungrouping behavior --- scripts/vr-edit/modules/groups.js | 131 ++++++++++++++++++------------ 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index 036df1a71c..8cfb2b971e 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -15,26 +15,26 @@ Groups = function () { "use strict"; - var groupRootEntityIDs = [], - groupSelectionDetails = [], - numberOfEntitiesSelected = 0; + var rootEntityIDs = [], + selections = [], + entitiesSelectedCount = 0; if (!this instanceof Groups) { return new Groups(); } function add(selection) { - groupRootEntityIDs.push(selection[0].id); - groupSelectionDetails.push(Object.clone(selection)); - numberOfEntitiesSelected += selection.length; + rootEntityIDs.push(selection[0].id); + selections.push(Object.clone(selection)); + entitiesSelectedCount += selection.length; } function remove(selection) { - var index = groupRootEntityIDs.indexOf(selection[0].id); + var index = rootEntityIDs.indexOf(selection[0].id); - numberOfEntitiesSelected -= groupSelectionDetails[index].length; - groupRootEntityIDs.splice(index, 1); - groupSelectionDetails.splice(index, 1); + entitiesSelectedCount -= selections[index].length; + rootEntityIDs.splice(index, 1); + selections.splice(index, 1); } function toggle(selection) { @@ -42,7 +42,7 @@ Groups = function () { return; } - if (groupRootEntityIDs.indexOf(selection[0].id) === -1) { + if (rootEntityIDs.indexOf(selection[0].id) === -1) { add(selection); } else { remove(selection); @@ -56,10 +56,10 @@ Groups = function () { j, lengthJ; - for (i = 0, lengthI = groupRootEntityIDs.length; i < lengthI; i += 1) { - if (excludes.indexOf(groupRootEntityIDs[i]) === -1) { - for (j = 0, lengthJ = groupSelectionDetails[i].length; j < lengthJ; j += 1) { - result.push(groupSelectionDetails[i][j]); + for (i = 0, lengthI = rootEntityIDs.length; i < lengthI; i += 1) { + if (excludes.indexOf(rootEntityIDs[i]) === -1) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + result.push(selections[i][j]); } } } @@ -68,78 +68,107 @@ Groups = function () { } function includes(rootEntityID) { - return groupRootEntityIDs.indexOf(rootEntityID) !== -1; + return rootEntityIDs.indexOf(rootEntityID) !== -1; } function groupsCount() { - return groupSelectionDetails.length; + return selections.length; } function entitiesCount() { - return numberOfEntitiesSelected; + return entitiesSelectedCount; } function group() { + // Groups all selections into one. var rootID, i, count; // Make the first entity in the first group the root and link the first entities of all other groups to it. - rootID = groupRootEntityIDs[0]; - for (i = 1, count = groupRootEntityIDs.length; i < count; i += 1) { - Entities.editEntity(groupRootEntityIDs[i], { + rootID = rootEntityIDs[0]; + for (i = 1, count = rootEntityIDs.length; i < count; i += 1) { + Entities.editEntity(rootEntityIDs[i], { parentID: rootID }); } // Update selection. - groupRootEntityIDs.splice(1, groupRootEntityIDs.length - 1); - for (i = 1, count = groupSelectionDetails.length; i < count; i += 1) { - groupSelectionDetails[i][0].parentID = rootID; - groupSelectionDetails[0] = groupSelectionDetails[0].concat(groupSelectionDetails[i]); + rootEntityIDs.splice(1, rootEntityIDs.length - 1); + for (i = 1, count = selections.length; i < count; i += 1) { + selections[i][0].parentID = rootID; + selections[0] = selections[0].concat(selections[i]); } - groupSelectionDetails.splice(1, groupSelectionDetails.length - 1); + selections.splice(1, selections.length - 1); } function ungroup() { + // Ungroups the first and assumed to be only selection. + // If the first entity in the selection has a mix of solo and group children then just the group children are unlinked, + // otherwise all are unlinked. var rootID, - childrenIDs, - childrenIDIndexes, + childrenIDs = [], + childrenIndexes = [], + childrenIndexIsGroup = [], + previousWasGroup, + hasSoloChildren = false, + hasGroupChildren = false, + isUngroupAll, i, count; - // Compile information on children. - rootID = groupRootEntityIDs[0]; - childrenIDs = []; - childrenIDIndexes = []; - for (i = 1, count = groupSelectionDetails[0].length; i < count; i += 1) { - if (groupSelectionDetails[0][i].parentID === rootID) { - childrenIDs.push(groupSelectionDetails[0][i].id); - childrenIDIndexes.push(i); + function updateGroupInformation() { + var childrenIndexesLength = childrenIndexes.length; + if (childrenIndexesLength > 1) { + previousWasGroup = childrenIndexes[childrenIndexesLength - 2] < i - 1; + childrenIndexIsGroup.push(previousWasGroup); + if (previousWasGroup) { + hasGroupChildren = true; + } else { + hasSoloChildren = true; + } } } - childrenIDIndexes.push(groupSelectionDetails[0].length); // Extra item at end to aid updating selection. - // Unlink direct children from root entity. - for (i = 0, count = childrenIDs.length; i < count; i += 1) { - Entities.editEntity(childrenIDs[i], { - parentID: Uuid.NULL - }); + if (entitiesSelectedCount === 0) { + App.log("ERROR: Groups: Nothing to ungroup!"); + return; + } + if (entitiesSelectedCount === 1) { + App.log("ERROR: Groups: Cannot ungroup sole entity!"); + return; } - // Update selection. - groupRootEntityIDs = groupRootEntityIDs.concat(childrenIDs); - for (i = 0, count = childrenIDs.length; i < count; i += 1) { - groupSelectionDetails.push(groupSelectionDetails[0].slice(childrenIDIndexes[i], childrenIDIndexes[i + 1])); - groupSelectionDetails[i + 1][0].parentID = Uuid.NULL; + // Compile information on immediate children. + rootID = rootEntityIDs[0]; + for (i = 1, count = selections[0].length; i < count; i += 1) { + if (selections[0][i].parentID === rootID) { + childrenIDs.push(selections[0][i].id); + childrenIndexes.push(i); + updateGroupInformation(); + } + } + childrenIndexes.push(selections[0].length); // Special extra item at end to aid updating selection. + updateGroupInformation(); + + // Unlink children. + isUngroupAll = hasSoloChildren !== hasGroupChildren; + for (i = childrenIDs.length - 1; i >= 0; i -= 1) { + if (isUngroupAll || childrenIndexIsGroup[i]) { + Entities.editEntity(childrenIDs[i], { + parentID: Uuid.NULL + }); + rootEntityIDs.push(childrenIDs[i]); + selections[0][childrenIndexes[i]].parentID = Uuid.NULL; + selections.push(selections[0].splice(childrenIndexes[i], childrenIndexes[i + 1] - childrenIndexes[i])); + } } - groupSelectionDetails[0].splice(1, groupSelectionDetails[0].length - childrenIDIndexes[0]); } function clear() { - groupRootEntityIDs = []; - groupSelectionDetails = []; - numberOfEntitiesSelected = 0; + rootEntityIDs = []; + selections = []; + entitiesSelectedCount = 0; } function destroy() { From 441ba157083cae37cf0789db5ae5c26be96ad45f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 14 Aug 2017 17:23:02 +1200 Subject: [PATCH 184/504] Fix swatch colors --- scripts/vr-edit/modules/toolMenu.js | 56 +++++++++++------------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 1dd0eb486c..ea8106c769 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -295,14 +295,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.03, z: -0.005 }, - color: { red: 0, green: 255, blue: 255 }, - solid: true + localPosition: { x: -0.035, y: -0.03, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch1Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 0, green: 255, blue: 255 } }, command: { method: "setColorPerSwatch", @@ -318,14 +316,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.03, z: -0.005 }, - color: { red: 255, green: 0, blue: 255 }, - solid: true + localPosition: { x: -0.01, y: -0.03, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch2Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 255, green: 0, blue: 255 } }, command: { method: "setColorPerSwatch", @@ -341,14 +337,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.005, z: -0.005 }, - color: { red: 255, green: 255, blue: 0 }, - solid: true + localPosition: { x: -0.035, y: -0.005, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch3Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 255, green: 255, blue: 0 } }, command: { method: "setColorPerSwatch", @@ -364,14 +358,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.005, z: -0.005 }, - color: { red: 255, green: 0, blue: 0 }, - solid: true + localPosition: { x: -0.01, y: -0.005, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch4Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 255, green: 0, blue: 0 } }, command: { method: "setColorPerSwatch", @@ -387,14 +379,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.02, z: -0.005 }, - color: { red: 0, green: 255, blue: 0 }, - solid: true + localPosition: { x: -0.035, y: 0.02, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch5Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 0, green: 255, blue: 0 } }, command: { method: "setColorPerSwatch", @@ -410,14 +400,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.02, z: -0.005 }, - color: { red: 0, green: 0, blue: 255 }, - solid: true + localPosition: { x: -0.01, y: 0.02, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch6Color", - property: "color" - // Default value is set in properties, above. + property: "color", + defaultValue: { red: 0, green: 0, blue: 255 } }, command: { method: "setColorPerSwatch", @@ -434,12 +422,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.035, y: 0.045, z: -0.005 } - // Default to empty swatch. }, setting: { key: "VREdit.colorTool.swatch7Color", property: "color" - // Default value is set in properties, above. + // Default to empty swatch. }, command: { method: "setColorPerSwatch", @@ -456,12 +443,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.01, y: 0.045, z: -0.005 } - // Default to empty swatch. }, setting: { key: "VREdit.colorTool.swatch8Color", property: "color" - // Default value is set in properties, above. + // Default to empty swatch. }, command: { method: "setColorPerSwatch", @@ -807,7 +793,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (optionsItems[i].setting) { optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; value = Settings.getValue(optionsItems[i].setting.key); - if (value === "") { + if (value === "" && optionsItems[i].setting.defaultValue) { value = optionsItems[i].setting.defaultValue; } if (value !== "") { From 504857a1b8f2e8e00fe56c661d47969590652e2a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 15 Aug 2017 10:23:36 +1200 Subject: [PATCH 185/504] Set up Physics sliders --- scripts/vr-edit/modules/toolMenu.js | 66 ++++++++++++++++++++++++++--- scripts/vr-edit/vr-edit.js | 26 ++++++++++++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index ea8106c769..fb80330dc5 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -498,8 +498,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "label", properties: { text: "PROPERTIES", - lineHeight: 0.005, - localPosition: { x: -0.0325, y: -0.0475, z: -0.0075} + lineHeight: 0.0045, + localPosition: { x: -0.031, y: -0.0475, z: -0.0075} } }, { @@ -560,6 +560,15 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, + { + id: "propertiesLabel", + type: "label", + properties: { + text: "PRESETS", + lineHeight: 0.0045, + localPosition: { x: 0.002, y: -0.0475, z: -0.0075 } + } + }, { id: "presets", type: "panel", @@ -576,31 +585,76 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, callback: { - method: "setSliderValue" + method: "setGravityValue" + } + }, + { + id: "gravityLabel", + type: "label", + properties: { + text: "GRAVITY", + lineHeight: 0.0045, + localPosition: { x: -0.003, y: 0.052, z: -0.0075 } } }, { id: "bounceSlider", - type: "panel", + type: "barSlider", properties: { localPosition: { x: 0.009, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } + }, + callback: { + method: "setBounceValue" + } + }, + { + id: "bounceLabel", + type: "label", + properties: { + text: "BOUNCE", + lineHeight: 0.0045, + localPosition: { x: 0.015, y: 0.057, z: -0.0075 } } }, { id: "dampingSlider", - type: "panel", + type: "barSlider", properties: { localPosition: { x: 0.024, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } + }, + callback: { + method: "setDampingValue" + } + }, + { + id: "dampingLabel", + type: "label", + properties: { + text: "DAMPING", + lineHeight: 0.0045, + localPosition: { x: 0.030, y: 0.052, z: -0.0075 } } }, { id: "densitySlider", - type: "panel", + type: "barSlider", properties: { localPosition: { x: 0.039, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } + }, + callback: { + method: "setDensityValue" + } + }, + { + id: "densityLabel", + type: "label", + properties: { + text: "DENSITY", + lineHeight: 0.0045, + localPosition: { x: 0.045, y: 0.057, z: -0.0075 } } } ] diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c10bb748f7..3d0ec93b4d 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1415,6 +1415,31 @@ } break; + case "setGravityValue": + if (parameter !== undefined) { + // TODO + print("setGravityValue = " + parameter); + } + break; + case "setBounceValue": + if (parameter !== undefined) { + // TODO + print("setBounceValue = " + parameter); + } + break; + case "setDampingValue": + if (parameter !== undefined) { + // TODO + print("setDampingValue = " + parameter); + } + break; + case "setDensityValue": + if (parameter !== undefined) { + // TODO + print("setDensityValue = " + parameter); + } + break; + case "autoGrab": if (dominantHand === LEFT_HAND) { editors[LEFT_HAND].enableAutoGrab(); @@ -1429,6 +1454,7 @@ print("setSliderValue = " + parameter); } break; + default: log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); } From 3a1fc1f11c6b2ef2c8b282b6f6ece8d3bad7bb44 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 15 Aug 2017 17:44:42 +1200 Subject: [PATCH 186/504] Remember Physics slider values --- scripts/vr-edit/modules/toolMenu.js | 122 ++++++++++++++++++---------- scripts/vr-edit/vr-edit.js | 6 +- 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index fb80330dc5..d93567ef72 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -227,7 +227,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], SLIDER_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, - MIN_BAR_SLIDER_DIMENSION = 0.0001, + MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. OPTONS_PANELS = { @@ -512,12 +512,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { label: "GRAVITY", setting: { key: "VREdit.physicsTool.gravityOn", - // No property defaultValue: false, - command: "setGravity" + callback: "setGravityOn" }, command: { - method: "setGravity", + method: "setGravityOn", parameter: "gravityToggle" } }, @@ -531,12 +530,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { label: " GRAB", setting: { key: "VREdit.physicsTool.grabOn", - // No property defaultValue: false, - command: "setGrab" + callback: "setGrabOn" }, command: { - method: "setGrab", + method: "setGrabOn", parameter: "grabToggle" } }, @@ -550,12 +548,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { label: "COLLIDE", setting: { key: "VREdit.physicsTool.collideOn", - // No property defaultValue: false, - command: "setCollide" + callback: "setCollideOn" }, command: { - method: "setCollide", + method: "setCollideOn", parameter: "collideToggle" } }, @@ -584,8 +581,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: -0.007, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, - callback: { - method: "setGravityValue" + setting: { + key: "VREdit.physicsTool.gravity", + defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + callback: "setGravity" + }, + command: { + method: "setGravity" } }, { @@ -604,8 +606,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.009, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, - callback: { - method: "setBounceValue" + setting: { + key: "VREdit.physicsTool.bounce", + defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + callback: "setBounce" + }, + command: { + method: "setBounce" } }, { @@ -624,8 +631,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.024, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, - callback: { - method: "setDampingValue" + setting: { + key: "VREdit.physicsTool.damping", + defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + callback: "setDamping" + }, + command: { + method: "setDamping" } }, { @@ -644,8 +656,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.039, y: 0.016, z: -0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, - callback: { - method: "setDensityValue" + setting: { + key: "VREdit.physicsTool.density", + defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + callback: "setDensity" + }, + command: { + method: "setDensity" } }, { @@ -847,7 +864,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (optionsItems[i].setting) { optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; value = Settings.getValue(optionsItems[i].setting.key); - if (value === "" && optionsItems[i].setting.defaultValue) { + if (value === "" && optionsItems[i].setting.defaultValue !== undefined) { value = optionsItems[i].setting.defaultValue; } if (value !== "") { @@ -863,8 +880,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ? UI_ELEMENTS[optionsItems[i].type].onColor : UI_ELEMENTS[optionsItems[i].type].offColor; } - if (optionsItems[i].setting.command) { - uiCommandCallback(optionsItems[i].setting.command, value); + if (optionsItems[i].type === "barSlider") { + // Store value in optionsSettings rather than using overlay property. + optionsSettings[optionsItems[i].id].value = value; + } + if (optionsItems[i].setting.callback) { + uiCommandCallback(optionsItems[i].setting.callback, value); } } } @@ -878,29 +899,29 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } if (optionsItems[i].type === "barSlider") { - // Initial value = 0. optionsOverlaysAuxiliaries[i] = {}; auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); + auxiliaryProperties.localPosition = { x: 0, y: (0.5 - value / 2) * properties.dimensions.y, z: 0 }; auxiliaryProperties.dimensions = { x: properties.dimensions.x, - y: MIN_BAR_SLIDER_DIMENSION, // Avoid visual artifact if 0. + y: Math.max(value * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), z: properties.dimensions.z }; - auxiliaryProperties.localPosition = { - x: 0, - y: properties.dimensions.y / 2, - z: 0 - }; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, auxiliaryProperties); auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); - auxiliaryProperties.dimensions = properties.dimensions; - auxiliaryProperties.localPosition = Vec3.ZERO; + auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; + auxiliaryProperties.dimensions = { + x: properties.dimensions.x, + y: Math.max((1.0 - value) * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), + z: properties.dimensions.z + }; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, auxiliaryProperties); } + if (optionsItems[i].type === "imageSlider") { imageOffset = 0.0; @@ -1020,9 +1041,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; - case "setGravity": - case "setGrab": - case "setCollide": + case "setGravityOn": + case "setGrabOn": + case "setCollideOn": value = !optionsSettings[parameter].value; optionsSettings[parameter].value = value; Settings.setValue(optionsSettings[parameter].key, value); @@ -1033,6 +1054,23 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { uiCommandCallback(command, value); break; + case "setGravity": + Settings.setValue(optionsSettings.gravitySlider.key, parameter); + uiCommandCallback("setGravity", parameter); + break; + case "setBounce": + Settings.setValue(optionsSettings.bounceSlider.key, parameter); + uiCommandCallback("setBounce", parameter); + break; + case "setDamping": + Settings.setValue(optionsSettings.dampingSlider.key, parameter); + uiCommandCallback("setDamping", parameter); + break; + case "setDensity": + Settings.setValue(optionsSettings.densitySlider.key, parameter); + uiCommandCallback("setDensity", parameter); + break; + default: App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } @@ -1182,9 +1220,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { openOptions(intersectionItems[intersectedItem].toolOptions); } if (intersectionItems[intersectedItem].command) { - if (intersectionItems[intersectedItem].command.parameter) { - parameter = intersectionItems[intersectedItem].command.parameter; - } + parameter = intersectionItems[intersectedItem].id; doCommand(intersectionItems[intersectedItem].command.method, parameter); } if (intersectionItems[intersectedItem].callback) { @@ -1225,7 +1261,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, - y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), // Avoid visual artifact if 0. + y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), z: overlayDimensions.z } }); @@ -1233,12 +1269,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, - y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), // Avoid visual artifact if 0. + y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), z: overlayDimensions.z } }); - - uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + if (intersectionItems[intersectedItem].command) { + doCommand(intersectionItems[intersectedItem].command.method, fraction); + } } // Image slider update. @@ -1257,8 +1294,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: Vec3.sum(optionsOverlaysAuxiliaries[intersectedItem].offset, { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) }); - - uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + if (intersectionItems[intersectedItem].callback) { + uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + } } // Special handling for Group options. diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3d0ec93b4d..d0d307c07e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1393,7 +1393,7 @@ colorToolColor = parameter; break; - case "setGravity": + case "setGravityOn": if (parameter) { physicsToolPhysics.gravity = { x: 0, y: -9.8, z: 0 }; // Earth gravity. physicsToolPhysics.dynamic = true; @@ -1402,10 +1402,10 @@ physicsToolPhysics.dynamic = false; } break; - case "setGrab": + case "setGrabOn": physicsToolPhysics.userData.grabbableKey.grabbable = parameter; break; - case "setCollide": + case "setCollideOn": if (parameter) { physicsToolPhysics.collisionless = false; physicsToolPhysics.collidesWith = "static,dynamic,kinematic,myAvatar,otherAvatar"; From 8cf18c1b033bb4f24b7fa4072c80c4ecc29168f5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 15 Aug 2017 17:47:59 +1200 Subject: [PATCH 187/504] Tidy applying color swatches --- scripts/vr-edit/modules/toolMenu.js | 37 ++++++++++------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d93567ef72..0df93cce3c 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -303,8 +303,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 0, green: 255, blue: 255 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch1.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -324,8 +323,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 255, green: 0, blue: 255 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch2.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -345,8 +343,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 255, green: 255, blue: 0 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch3.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -366,8 +363,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 255, green: 0, blue: 0 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch4.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -387,8 +383,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 0, green: 255, blue: 0 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch5.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -408,8 +403,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { defaultValue: { red: 0, green: 0, blue: 255 } }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch6.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -429,8 +423,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Default to empty swatch. }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch7.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -450,8 +443,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Default to empty swatch. }, command: { - method: "setColorPerSwatch", - parameter: "colorSwatch8.color" + method: "setColorPerSwatch" }, onGripClicked: { method: "clearSwatch", @@ -998,20 +990,17 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } function doCommand(command, parameter) { - var parameters, - index, + var index, hasColor, value; switch (command) { case "setColorPerSwatch": - parameters = parameter.split("."); - index = optionsOverlaysIDs.indexOf(parameters[0]); + index = optionsOverlaysIDs.indexOf(parameter); hasColor = Overlays.getProperty(optionsOverlays[index], "solid"); if (hasColor) { - // Swatch has a color; set current fill color to swatch color. - value = evaluateParameter(parameter); + value = Overlays.getProperty(optionsOverlays[index], "color"); Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { color: value }); @@ -1026,8 +1015,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: value, solid: true }); - if (optionsSettings[parameters[0]]) { - Settings.setValue(optionsSettings[parameters[0]].key, value); + if (optionsSettings[parameter]) { + Settings.setValue(optionsSettings[parameter].key, value); } } break; From 8d01c02c29bb48f46e1aaacb9bfdec8d0ca67c84 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 15 Aug 2017 17:54:21 +1200 Subject: [PATCH 188/504] Tidy clearing color swatches --- scripts/vr-edit/modules/toolMenu.js | 48 ++++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 0df93cce3c..9f6ff12382 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -305,9 +305,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch1" + clear: { + method: "clearSwatch" } }, { @@ -325,9 +324,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch2" + clear: { + method: "clearSwatch" } }, { @@ -345,9 +343,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch3" + clear: { + method: "clearSwatch" } }, { @@ -365,9 +362,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch4" + clear: { + method: "clearSwatch" } }, { @@ -385,9 +381,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch5" + clear: { + method: "clearSwatch" } }, { @@ -405,9 +400,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch6" + clear: { + method: "clearSwatch" } }, { @@ -425,9 +419,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch7" + clear: { + method: "clearSwatch" } }, { @@ -445,9 +438,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "setColorPerSwatch" }, - onGripClicked: { - method: "clearSwatch", - parameter: "colorSwatch8" + clear: { + method: "clearSwatch" } }, { @@ -1224,12 +1216,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Grip click. if (controlHand.gripClicked() !== isGripClicked) { isGripClicked = !isGripClicked; - if (isGripClicked && intersectionItems && intersectedItem && intersectionItems[intersectedItem].onGripClicked) { + if (isGripClicked && intersectionItems && intersectedItem && intersectionItems[intersectedItem].clear) { controlHand.setGripClickedHandled(); - if (intersectionItems[intersectedItem].onGripClicked.parameter) { - parameter = intersectionItems[intersectedItem].onGripClicked.parameter; - } - doGripClicked(intersectionItems[intersectedItem].onGripClicked.method, parameter); + parameter = intersectionItems[intersectedItem].id; + doGripClicked(intersectionItems[intersectedItem].clear.method, parameter); } } From cb53ad43e089024d4aa08aa0807c617d311ed9b6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 15 Aug 2017 18:16:21 +1200 Subject: [PATCH 189/504] Tidying --- scripts/vr-edit/modules/toolMenu.js | 277 ++++++++++++++-------------- 1 file changed, 142 insertions(+), 135 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 9f6ff12382..9a95425433 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -500,8 +500,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: "setGravityOn" }, command: { - method: "setGravityOn", - parameter: "gravityToggle" + method: "setGravityOn" } }, { @@ -518,8 +517,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: "setGrabOn" }, command: { - method: "setGrabOn", - parameter: "grabToggle" + method: "setGrabOn" } }, { @@ -536,8 +534,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: "setCollideOn" }, command: { - method: "setCollideOn", - parameter: "collideToggle" + method: "setCollideOn" } }, @@ -809,18 +806,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { return [menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); } - function openOptions(toolOptions) { - var properties, - childProperties, - auxiliaryProperties, - parentID, - value, - imageOffset, - IMAGE_OFFSET = 0.0005, - i, + function closeOptions() { + var i, length; - // Close current panel, if any. Overlays.editOverlay(highlightOverlay, { parentID: menuOriginOverlay }); @@ -833,129 +822,147 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlaysAuxiliaries = []; optionsEnabled = []; optionsItems = null; + } - // Open specified panel, if any. - if (toolOptions) { - optionsItems = OPTONS_PANELS[toolOptions]; - parentID = menuPanelOverlay; // Menu panel parents to background panel. - for (i = 0, length = optionsItems.length; i < length; i += 1) { - properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); - properties = Object.merge(properties, optionsItems[i].properties); - properties.parentID = parentID; - if (properties.url) { - properties.url = Script.resolvePath(properties.url); - } - if (optionsItems[i].setting) { - optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; - value = Settings.getValue(optionsItems[i].setting.key); - if (value === "" && optionsItems[i].setting.defaultValue !== undefined) { - value = optionsItems[i].setting.defaultValue; - } - if (value !== "") { - properties[optionsItems[i].setting.property] = value; - if (optionsItems[i].type === "swatch") { - // Special case for when swatch color is defined. - properties.solid = true; - } - if (optionsItems[i].type === "toggleButton") { - // Store value in optionsSettings rather than using overlay property. - optionsSettings[optionsItems[i].id].value = value; - properties.color = value - ? UI_ELEMENTS[optionsItems[i].type].onColor - : UI_ELEMENTS[optionsItems[i].type].offColor; - } - if (optionsItems[i].type === "barSlider") { - // Store value in optionsSettings rather than using overlay property. - optionsSettings[optionsItems[i].id].value = value; - } - if (optionsItems[i].setting.callback) { - uiCommandCallback(optionsItems[i].setting.callback, value); - } - } - } - optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); - optionsOverlaysIDs.push(optionsItems[i].id); - if (optionsItems[i].label) { - properties = Object.clone(UI_ELEMENTS.label.properties); - properties.text = optionsItems[i].label; - properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); - } + function openOptions(toolOptions) { + var properties, + childProperties, + auxiliaryProperties, + parentID, + value, + imageOffset, + IMAGE_OFFSET = 0.0005, + i, + length; - if (optionsItems[i].type === "barSlider") { - optionsOverlaysAuxiliaries[i] = {}; - auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); - auxiliaryProperties.localPosition = { x: 0, y: (0.5 - value / 2) * properties.dimensions.y, z: 0 }; - auxiliaryProperties.dimensions = { - x: properties.dimensions.x, - y: Math.max(value * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), - z: properties.dimensions.z - }; - auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, - auxiliaryProperties); - auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); - auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; - auxiliaryProperties.dimensions = { - x: properties.dimensions.x, - y: Math.max((1.0 - value) * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), - z: properties.dimensions.z - }; - auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, - auxiliaryProperties); - } + // Close current panel, if any. + closeOptions(); - if (optionsItems[i].type === "imageSlider") { - imageOffset = 0.0; + // TODO: Remove once all tools have an options panel. + if (OPTONS_PANELS[toolOptions] === undefined) { + return; + } - // Primary image. - if (optionsItems[i].imageURL) { - childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageURL); - delete childProperties.dimensions; - childProperties.scale = properties.dimensions.y; - imageOffset += IMAGE_OFFSET; - childProperties.emissive = true; - if (optionsItems[i].useBaseColor) { - childProperties.color = properties.color; - } - childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; - childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); - } - - // Overlay image. - if (optionsItems[i].imageOverlayURL) { - childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); - childProperties.drawInFront = true; // TODO: Work-around for rendering bug; remove when bug fixed. - delete childProperties.dimensions; - childProperties.scale = properties.dimensions.y; - imageOffset += IMAGE_OFFSET; - childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; - childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); - } - - // Value pointers. - optionsOverlaysAuxiliaries[i] = {}; - optionsOverlaysAuxiliaries[i].offset = - { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; - auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); - auxiliaryProperties.localPosition = optionsOverlaysAuxiliaries[i].offset; - auxiliaryProperties.drawInFront = true; // TODO: Accommodate work-around above; remove when bug fixed. - auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, - auxiliaryProperties); - auxiliaryProperties.localPosition = { x: 0, y: properties.dimensions.x, z: 0 }; - auxiliaryProperties.localRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - auxiliaryProperties.parentID = optionsOverlaysAuxiliaries[i].value; - Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); - } - parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. - optionsEnabled.push(true); + // Open specified panel. + optionsItems = OPTONS_PANELS[toolOptions]; + parentID = menuPanelOverlay; // Menu panel parents to background panel. + for (i = 0, length = optionsItems.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); + properties = Object.merge(properties, optionsItems[i].properties); + properties.parentID = parentID; + if (properties.url) { + properties.url = Script.resolvePath(properties.url); } + if (optionsItems[i].setting) { + optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; + value = Settings.getValue(optionsItems[i].setting.key); + if (value === "" && optionsItems[i].setting.defaultValue !== undefined) { + value = optionsItems[i].setting.defaultValue; + } + if (value !== "") { + properties[optionsItems[i].setting.property] = value; + if (optionsItems[i].type === "swatch") { + // Special case for when swatch color is defined. + properties.solid = true; + } + if (optionsItems[i].type === "toggleButton") { + // Store value in optionsSettings rather than using overlay property. + optionsSettings[optionsItems[i].id].value = value; + properties.color = value + ? UI_ELEMENTS[optionsItems[i].type].onColor + : UI_ELEMENTS[optionsItems[i].type].offColor; + } + if (optionsItems[i].type === "barSlider") { + // Store value in optionsSettings rather than using overlay property. + optionsSettings[optionsItems[i].id].value = value; + } + if (optionsItems[i].setting.callback) { + uiCommandCallback(optionsItems[i].setting.callback, value); + } + } + } + optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); + optionsOverlaysIDs.push(optionsItems[i].id); + if (optionsItems[i].label) { + properties = Object.clone(UI_ELEMENTS.label.properties); + properties.text = optionsItems[i].label; + properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + } + + if (optionsItems[i].type === "barSlider") { + optionsOverlaysAuxiliaries[i] = {}; + auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); + auxiliaryProperties.localPosition = { x: 0, y: (0.5 - value / 2) * properties.dimensions.y, z: 0 }; + auxiliaryProperties.dimensions = { + x: properties.dimensions.x, + y: Math.max(value * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), + z: properties.dimensions.z + }; + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, + auxiliaryProperties); + auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); + auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; + auxiliaryProperties.dimensions = { + x: properties.dimensions.x, + y: Math.max((1.0 - value) * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), + z: properties.dimensions.z + }; + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, + auxiliaryProperties); + } + + if (optionsItems[i].type === "imageSlider") { + imageOffset = 0.0; + + // Primary image. + if (optionsItems[i].imageURL) { + childProperties = Object.clone(UI_ELEMENTS.image.properties); + childProperties.url = Script.resolvePath(optionsItems[i].imageURL); + delete childProperties.dimensions; + childProperties.scale = properties.dimensions.y; + imageOffset += IMAGE_OFFSET; + childProperties.emissive = true; + if (optionsItems[i].useBaseColor) { + childProperties.color = properties.color; + } + childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + } + + // Overlay image. + if (optionsItems[i].imageOverlayURL) { + childProperties = Object.clone(UI_ELEMENTS.image.properties); + childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); + childProperties.drawInFront = true; // TODO: Work-around for rendering bug; remove when bug fixed. + delete childProperties.dimensions; + childProperties.scale = properties.dimensions.y; + imageOffset += IMAGE_OFFSET; + childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + } + + // Value pointers. + optionsOverlaysAuxiliaries[i] = {}; + optionsOverlaysAuxiliaries[i].offset = + { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); + auxiliaryProperties.localPosition = optionsOverlaysAuxiliaries[i].offset; + auxiliaryProperties.drawInFront = true; // TODO: Accommodate work-around above; remove when bug fixed. + auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; + optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, + auxiliaryProperties); + auxiliaryProperties.localPosition = { x: 0, y: properties.dimensions.x, z: 0 }; + auxiliaryProperties.localRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + auxiliaryProperties.parentID = optionsOverlaysAuxiliaries[i].value; + Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); + } + parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. + optionsEnabled.push(true); } // Special handling for Group options. @@ -966,7 +973,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } function clearTool() { - openOptions(); + closeOptions(); } function evaluateParameter(parameter) { From b1938bafea0190376e9f484ed434e49c5e297752 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 13:40:06 +1200 Subject: [PATCH 190/504] Apply slider values to entities --- scripts/vr-edit/modules/toolMenu.js | 12 +++++----- scripts/vr-edit/vr-edit.js | 35 ++++++++++++++++++----------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 9a95425433..c6b7527056 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -154,7 +154,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "barSlider": { + "barSlider": { // Values range between 0.0 and 1.0. overlay: "cube", properties: { dimensions: { x: 0.02, y: 0.1, z: 0.01 }, @@ -192,7 +192,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "imageSlider": { + "imageSlider": { // Values range between 0.0 and 1.0. overlay: "cube", properties: { dimensions: { x: 0.01, y: 0.05, z: 0.01 }, @@ -564,7 +564,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, setting: { key: "VREdit.physicsTool.gravity", - defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + defaultValue: 0.5, callback: "setGravity" }, command: { @@ -589,7 +589,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, setting: { key: "VREdit.physicsTool.bounce", - defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + defaultValue: 0.5, callback: "setBounce" }, command: { @@ -614,7 +614,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, setting: { key: "VREdit.physicsTool.damping", - defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + defaultValue: 0.5, callback: "setDamping" }, command: { @@ -639,7 +639,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, setting: { key: "VREdit.physicsTool.density", - defaultValue: 0, // Slider value in range 0.0 .. 1.0. TODO: Default value. + defaultValue: 0.5, callback: "setDensity" }, command: { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d0d307c07e..dd93ed420f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -33,6 +33,8 @@ toolSelected = TOOL_NONE, colorToolColor = { red: 128, green: 128, blue: 128 }, physicsToolPhysics = { userData: { grabbableKey: {} } }, + EARTH_GRAVITY = -9.80665, + physicsToolGravity = EARTH_GRAVITY, // Primary objects App, @@ -1395,7 +1397,7 @@ case "setGravityOn": if (parameter) { - physicsToolPhysics.gravity = { x: 0, y: -9.8, z: 0 }; // Earth gravity. + physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; physicsToolPhysics.dynamic = true; } else { physicsToolPhysics.gravity = Vec3.ZERO; @@ -1415,28 +1417,35 @@ } break; - case "setGravityValue": + case "setGravity": if (parameter !== undefined) { - // TODO - print("setGravityValue = " + parameter); + // Power range 0.0, 0.5, 1.0 maps to -50.0, -9.80665, 50.0. + physicsToolGravity = 82.36785162 * Math.pow(2.214065901, parameter) - 132.36785; + if (physicsToolPhysics.dynamic === true) { // Only apply if gravity is turned on. + physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; + } } break; - case "setBounceValue": + case "setBounce": if (parameter !== undefined) { - // TODO - print("setBounceValue = " + parameter); + // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; + physicsToolPhysics.restitution = parameter; } break; - case "setDampingValue": + case "setDamping": if (parameter !== undefined) { - // TODO - print("setDampingValue = " + parameter); + // Power range 0.0, 0.5, 1.0 maps to 0, 0.39, 1.0. + physicsToolPhysics.linearDamping = 0.69136364 * Math.pow(2.446416831, parameter) - 0.691364; + // Power range 0.0, 0.5, 1.0 maps to 0, 0.3935, 1.0. + physicsToolPhysics.angularDamping = 0.72695892 * Math.pow(2.375594, parameter) - 0.726959; + // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; + physicsToolPhysics.friction = parameter; } break; - case "setDensityValue": + case "setDensity": if (parameter !== undefined) { - // TODO - print("setDensityValue = " + parameter); + // Power range 0.0, 0.5, 1.0 maps to 100, 1000, 10000. + physicsToolPhysics.density = Math.pow(10, 2 + 2 * parameter); } break; From cca862b6baedcd679793892f2b317acbb675210d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 13:47:14 +1200 Subject: [PATCH 191/504] Apply physics to just the root entity --- scripts/vr-edit/modules/selection.js | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 1682ce0bbc..75e7b7e105 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -415,35 +415,20 @@ Selection = function (side) { } function applyPhysics(physicsProperties) { - // TODO: For development testing, apply physics to the currently intersected entity. + // Apply physics to just the root entity. var properties; properties = Object.clone(physicsProperties); properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); - Entities.editEntity(selection[intersectedEntityIndex].id, properties); - - // TODO: Original functionality applied physics to all entities in the selection. - /* - // Applies physics to the current selection (i.e., the selection made when entity was trigger-clicked to apply physics). - var properties, - i, - length; - - properties = Object.clone(physicsProperties); - - for (i = 0, length = selection.length; i < length; i += 1) { - properties.userData = updatePhysicsUserData(selection[i].userData, physicsProperties.userData); - Entities.editEntity(selection[i].id, properties); - } + Entities.editEntity(rootEntityID, properties); if (physicsProperties.dynamic) { - // Give dynamic entities with zero a little kick to set off physics. - properties = Entities.getEntityProperties(selection[0].id, ["velocity"]); + // Give dynamic entities with zero velocity a little kick to set off physics. + properties = Entities.getEntityProperties(rootEntityID, ["velocity"]); if (Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { - Entities.editEntity(selection[0].id, { velocity: DYNAMIC_VELOCITY_KICK }); + Entities.editEntity(rootEntityID, { velocity: DYNAMIC_VELOCITY_KICK }); } } - */ } function clear() { From c705bb7fad1e5d94ffd362a132a7495dff2a95ca Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 14:55:44 +1200 Subject: [PATCH 192/504] Add picklist button --- scripts/vr-edit/modules/toolMenu.js | 78 ++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c6b7527056..0f0fe62771 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -61,6 +61,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { NO_SWATCH_COLOR = { red: 128, green: 128, blue: 128 }, + UI_BASE_COLOR = { red: 64, green: 64, blue: 64 }, + UI_HIGHLIGHT_COLOR = { red: 100, green: 240, blue: 100 }, + UI_ELEMENTS = { "panel": { overlay: "cube", @@ -95,8 +98,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ignoreRayIntersection: false, visible: true }, - onColor: { red: 100, green: 240, blue: 100 }, - offColor: { red: 64, green: 64, blue: 64 } + onColor: UI_HIGHLIGHT_COLOR, + offColor: UI_BASE_COLOR }, "swatch": { overlay: "cube", @@ -118,7 +121,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), topMargin: 0, leftMargin: 0, - color: { red: 128, green: 128, blue: 128 }, + color: { red: 240, green: 240, blue: 240 }, alpha: 1.0, lineHeight: 0.007, backgroundAlpha: 0, @@ -172,7 +175,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.02, y: 0.03, z: 0.01 }, localPosition: { x: 0, y: 0.035, z: 0 }, localRotation: Quat.ZERO, - color: { red: 100, green: 240, blue: 100 }, + color: UI_HIGHLIGHT_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -185,7 +188,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.02, y: 0.07, z: 0.01 }, localPosition: { x: 0, y: -0.015, z: 0 }, localRotation: Quat.ZERO, - color: { red: 64, green: 64, blue: 64 }, + color: UI_BASE_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -219,6 +222,24 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ignoreRayIntersection: true, visible: true } + }, + "picklist": { + overlay: "cube", + properties: { + dimensions: { x: 0.10, y: 0.12, z: 0.01 }, + localRotation: Quat.ZERO, + color: UI_BASE_COLOR, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + } + }, + "picklistBackground": { + + }, + "picklistOption": { + } }, @@ -229,6 +250,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SLIDER_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. + PICKLIST_UI_ELEMENTS = ["picklist"], + PICKLIST_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, + OPTONS_PANELS = { groupOptions: [ @@ -549,10 +573,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, { id: "presets", - type: "panel", + type: "picklist", properties: { localPosition: { x: 0.016, y: -0.03, z: -0.005 }, dimensions: { x: 0.06, y: 0.02, z: 0.01 } + }, + label: "DEFAULT", + command: { + method: "togglePresets" } }, { @@ -770,9 +798,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedSource, isHighlightingButton, isHighlightingSlider, + isHighlightingPicklist, pressedItem = null, pressedSource, isButtonPressed, + isPicklistPressed, isGripClicked, isGroupButtonEnabled, @@ -1148,11 +1178,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: localPosition }); } + //Lower old picklist. + if (isHighlightingPicklist) { + localPosition = highlightedItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(highlightedSource[highlightedItem], { + localPosition: localPosition + }); + } // Update status variables. highlightedItem = intersectedItem; highlightedItems = intersectionItems; isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingSlider = SLIDER_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; + isHighlightingPicklist = PICKLIST_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; // Raise new slider. if (isHighlightingSlider) { localPosition = intersectionItems[highlightedItem].properties.localPosition; @@ -1160,6 +1198,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: Vec3.subtract(localPosition, SLIDER_RAISE_DELTA) }); } + // Raise new picklist. + if (isHighlightingPicklist) { + localPosition = intersectionItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[highlightedItem], { + localPosition: Vec3.subtract(localPosition, PICKLIST_RAISE_DELTA), + color: UI_HIGHLIGHT_COLOR + }); + } } else if (highlightedItem !== NONE) { // Un-highlight previous button. Overlays.editOverlay(highlightOverlay, { @@ -1172,10 +1218,19 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: localPosition }); } + // Lower picklist. + if (isHighlightingPicklist) { + localPosition = highlightedItems[highlightedItem].properties.localPosition; + Overlays.editOverlay(highlightedSource[highlightedItem], { + localPosition: localPosition, + color: UI_BASE_COLOR + }); + } // Update status variables. highlightedItem = NONE; isHighlightingButton = false; isHighlightingSlider = false; + isHighlightingPicklist = false; } highlightedSource = intersectionOverlays; } @@ -1285,6 +1340,15 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + // Picklist update. + if (intersectionItems && intersectionItems[intersectedItem].type === "picklist" && controlHand.triggerClicked() + && !isPicklistPressed) { + isPicklistPressed = true; + if (intersectionItems[intersectedItem].command) { + doCommand(intersectionItems[intersectedItem].command.method); + } + } + // Special handling for Group options. if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { enableGroupButton = groupsCount > 1; @@ -1373,9 +1437,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedSource = null; isHighlightingButton = false; isHighlightingSlider = false; + isHighlightingPicklist = false; pressedItem = null; pressedSource = null; isButtonPressed = false; + isPicklistPressed = false; isGripClicked = false; isGroupButtonEnabled = false; isUngroupButtonEnabled = false; From bfaae7c220d2363f7a7d14de7d44a233172ee247 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 20:22:24 +1200 Subject: [PATCH 193/504] Add picklist options --- scripts/vr-edit/modules/toolMenu.js | 258 +++++++++++++++++++++------- 1 file changed, 200 insertions(+), 58 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 0f0fe62771..b1483ab673 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -24,7 +24,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = [], optionsOverlaysIDs = [], // Text ids (names) of options overlays. - optionsOverlaysAuxiliaries = [], + optionsSliderData = [], // Uses same index values as optionsOverlays. + optionsPicklistItemLabelOverlays = [], optionsEnabled = [], optionsSettings = {}, @@ -226,7 +227,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "picklist": { overlay: "cube", properties: { - dimensions: { x: 0.10, y: 0.12, z: 0.01 }, + dimensions: { x: 0.06, y: 0.02, z: 0.01 }, localRotation: Quat.ZERO, color: UI_BASE_COLOR, alpha: 1.0, @@ -235,11 +236,18 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "picklistBackground": { - - }, - "picklistOption": { - + "picklistItem": { + overlay: "cube", + properties: { + dimensions: { x: 0.06, y: 0.02, z: 0.01 }, + localPosition: Vec3.ZERO, + localRotation: Quat.ZERO, + color: { red: 100, green: 100, blue: 100 }, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: false + } } }, @@ -250,7 +258,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SLIDER_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. - PICKLIST_UI_ELEMENTS = ["picklist"], + PICKLIST_UI_ELEMENTS = ["picklist", "picklistItem"], PICKLIST_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, @@ -580,8 +588,73 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, label: "DEFAULT", command: { - method: "togglePresets" - } + method: "togglePhysicsPresets" + }, + items: [ + "presetDefault", + "presetLead", + "presetWood", + "presetIce", + "presetRubber", + "presetCotton", + "presetTumbleWeed", + "presetZeroG", + "presetBallon" + ] + }, + { + id: "presetDefault", + type: "picklistItem", + label: "DEFAULT", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetLead", + type: "picklistItem", + label: "LEAD", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetWood", + type: "picklistItem", + label: "WOOD", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetIce", + type: "picklistItem", + label: "ICE", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetRubber", + type: "picklistItem", + label: "RUBBER", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetCotton", + type: "picklistItem", + label: "COTTON", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetTumbleWeed", + type: "picklistItem", + label: "TUMBLEWEED", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetZeroG", + type: "picklistItem", + label: "ZERO-G", + command: { method: "pickPhysicsPreset" } + }, + { + id: "presetBallon", + type: "picklistItem", + label: "BALLOON", + command: { method: "pickPhysicsPreset" } }, { id: "gravitySlider", @@ -799,10 +872,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isHighlightingButton, isHighlightingSlider, isHighlightingPicklist, + isPicklistOpen, pressedItem = null, pressedSource, isButtonPressed, isPicklistPressed, + isPicklistItemPressed, isGripClicked, isGroupButtonEnabled, @@ -849,9 +924,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = []; optionsOverlaysIDs = []; - optionsOverlaysAuxiliaries = []; + optionsSliderData = []; optionsEnabled = []; optionsItems = null; + + optionsPicklistItemLabelOverlays = []; } function openOptions(toolOptions) { @@ -862,6 +939,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { value, imageOffset, IMAGE_OFFSET = 0.0005, + id, i, length; @@ -878,7 +956,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { parentID = menuPanelOverlay; // Menu panel parents to background panel. for (i = 0, length = optionsItems.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); - properties = Object.merge(properties, optionsItems[i].properties); + if (optionsItems[i].properties) { + properties = Object.merge(properties, optionsItems[i].properties); + } properties.parentID = parentID; if (properties.url) { properties.url = Script.resolvePath(properties.url); @@ -917,11 +997,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS.label.properties); properties.text = optionsItems[i].label; properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + id = Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + if (optionsItems[i].type === "picklistItem") { + optionsPicklistItemLabelOverlays.push(id); + } } if (optionsItems[i].type === "barSlider") { - optionsOverlaysAuxiliaries[i] = {}; + optionsSliderData[i] = {}; auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); auxiliaryProperties.localPosition = { x: 0, y: (0.5 - value / 2) * properties.dimensions.y, z: 0 }; auxiliaryProperties.dimensions = { @@ -930,7 +1013,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { z: properties.dimensions.z }; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, + optionsSliderData[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, auxiliaryProperties); auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; @@ -940,7 +1023,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { z: properties.dimensions.z }; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, + optionsSliderData[i].remainder = Overlays.addOverlay(UI_ELEMENTS.barSliderRemainder.overlay, auxiliaryProperties); } @@ -977,18 +1060,18 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Value pointers. - optionsOverlaysAuxiliaries[i] = {}; - optionsOverlaysAuxiliaries[i].offset = + optionsSliderData[i] = {}; + optionsSliderData[i].offset = { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); - auxiliaryProperties.localPosition = optionsOverlaysAuxiliaries[i].offset; + auxiliaryProperties.localPosition = optionsSliderData[i].offset; auxiliaryProperties.drawInFront = true; // TODO: Accommodate work-around above; remove when bug fixed. auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsOverlaysAuxiliaries[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, + optionsSliderData[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); auxiliaryProperties.localPosition = { x: 0, y: properties.dimensions.x, z: 0 }; auxiliaryProperties.localRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - auxiliaryProperties.parentID = optionsOverlaysAuxiliaries[i].value; + auxiliaryProperties.parentID = optionsSliderData[i].value; Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); } parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. @@ -1021,7 +1104,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function doCommand(command, parameter) { var index, hasColor, - value; + value, + items, + parentID, + i, + length; switch (command) { @@ -1072,6 +1159,73 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { uiCommandCallback(command, value); break; + case "togglePhysicsPresets": + if (isPicklistOpen) { + // Close picklist. + index = optionsOverlaysIDs.indexOf(parameter); + + // Lower picklist. + Overlays.editOverlay(optionsOverlays[index], { + localPosition: optionsItems[index].properties.localPosition + }); + + // Hide options. + items = optionsItems[index].items; + for (i = 0, length = items.length; i < length; i += 1) { + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(items[i])], { + localPosition: Vec3.ZERO, + visible: false + }); + } + + // Hide labels. + for (i = 0, length = optionsPicklistItemLabelOverlays.length; i < length; i += 1) { + Overlays.editOverlay(optionsPicklistItemLabelOverlays[i], { + visible: false + }); + } + } + + isPicklistOpen = !isPicklistOpen; + + if (isPicklistOpen) { + // Open picklist. + index = optionsOverlaysIDs.indexOf(parameter); + parentID = optionsOverlays[index]; + + // Raise picklist. + Overlays.editOverlay(parentID, { + localPosition: Vec3.subtract(optionsItems[index].properties.localPosition, PICKLIST_RAISE_DELTA) + }); + + // Show options. + items = optionsItems[index].items; + for (i = 0, length = items.length; i < length; i += 1) { + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(items[i])], { + parentID: parentID, + localPosition: { x: 0, y: (i + 1) * -UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, + visible: true + }); + } + + // Show labels. + for (i = 0, length = optionsPicklistItemLabelOverlays.length; i < length; i += 1) { + Overlays.editOverlay(optionsPicklistItemLabelOverlays[i], { + visible: true + }); + } + } + break; + + case "pickPhysicsPreset": + doCommand("togglePhysicsPresets", "presets"); // Close picklist. + + // TODO: Update picklist label. + // TODO: Set picklist setting to record picklist label. + // TODO: Set physics parameters - update sliders, settings, and to-apply-values. + + break; + case "setGravity": Settings.setValue(optionsSettings.gravitySlider.key, parameter); uiCommandCallback("setGravity", parameter); @@ -1178,13 +1332,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: localPosition }); } - //Lower old picklist. - if (isHighlightingPicklist) { - localPosition = highlightedItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: localPosition - }); - } // Update status variables. highlightedItem = intersectedItem; highlightedItems = intersectionItems; @@ -1198,14 +1345,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: Vec3.subtract(localPosition, SLIDER_RAISE_DELTA) }); } - // Raise new picklist. - if (isHighlightingPicklist) { - localPosition = intersectionItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[highlightedItem], { - localPosition: Vec3.subtract(localPosition, PICKLIST_RAISE_DELTA), - color: UI_HIGHLIGHT_COLOR - }); - } } else if (highlightedItem !== NONE) { // Un-highlight previous button. Overlays.editOverlay(highlightOverlay, { @@ -1218,14 +1357,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: localPosition }); } - // Lower picklist. - if (isHighlightingPicklist) { - localPosition = highlightedItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: localPosition, - color: UI_BASE_COLOR - }); - } // Update status variables. highlightedItem = NONE; isHighlightingButton = false; @@ -1275,6 +1406,24 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + // Picklist update. + if (intersectionItems && ((intersectionItems[intersectedItem].type === "picklist" + && controlHand.triggerClicked() !== isPicklistPressed) + || (intersectionItems[intersectedItem].type !== "picklist" && isPicklistPressed))) { + isPicklistPressed = isHighlightingPicklist && controlHand.triggerClicked(); + if (isPicklistPressed) { + doCommand(intersectionItems[intersectedItem].command.method, intersectionItems[intersectedItem].id); + } + } + if (intersectionItems && ((intersectionItems[intersectedItem].type === "picklistItem" + && controlHand.triggerClicked() !== isPicklistItemPressed) + || (intersectionItems[intersectedItem].type !== "picklistItem" && isPicklistItemPressed))) { + isPicklistItemPressed = isHighlightingPicklist && controlHand.triggerClicked(); + if (isPicklistItemPressed) { + doCommand(intersectionItems[intersectedItem].command.method, intersectionItems[intersectedItem].id); + } + } + // Grip click. if (controlHand.gripClicked() !== isGripClicked) { isGripClicked = !isGripClicked; @@ -1298,7 +1447,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); otherFraction = 1.0 - fraction; - Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { + Overlays.editOverlay(optionsSliderData[intersectedItem].value, { localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, @@ -1306,7 +1455,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { z: overlayDimensions.z } }); - Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].remainder, { + Overlays.editOverlay(optionsSliderData[intersectedItem].remainder, { localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, @@ -1331,8 +1480,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); - Overlays.editOverlay(optionsOverlaysAuxiliaries[intersectedItem].value, { - localPosition: Vec3.sum(optionsOverlaysAuxiliaries[intersectedItem].offset, + Overlays.editOverlay(optionsSliderData[intersectedItem].value, { + localPosition: Vec3.sum(optionsSliderData[intersectedItem].offset, { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) }); if (intersectionItems[intersectedItem].callback) { @@ -1340,15 +1489,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } - // Picklist update. - if (intersectionItems && intersectionItems[intersectedItem].type === "picklist" && controlHand.triggerClicked() - && !isPicklistPressed) { - isPicklistPressed = true; - if (intersectionItems[intersectedItem].command) { - doCommand(intersectionItems[intersectedItem].command.method); - } - } - // Special handling for Group options. if (optionsItems && optionsItems === OPTONS_PANELS.groupOptions) { enableGroupButton = groupsCount > 1; @@ -1438,10 +1578,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isHighlightingButton = false; isHighlightingSlider = false; isHighlightingPicklist = false; + isPicklistOpen = false; pressedItem = null; pressedSource = null; isButtonPressed = false; isPicklistPressed = false; + isPicklistItemPressed = false; isGripClicked = false; isGroupButtonEnabled = false; isUngroupButtonEnabled = false; From 30e9b8ea458768d53f681a18be989607f9934878 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 20:56:08 +1200 Subject: [PATCH 194/504] Update picklist label when item is picked --- scripts/vr-edit/modules/toolMenu.js | 50 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index b1483ab673..fbbfecc287 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -24,8 +24,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = [], optionsOverlaysIDs = [], // Text ids (names) of options overlays. + optionsOverlaysLabels = [], // Overlay IDs of labels for optionsOverlays. optionsSliderData = [], // Uses same index values as optionsOverlays. - optionsPicklistItemLabelOverlays = [], optionsEnabled = [], optionsSettings = {}, @@ -587,6 +587,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.06, y: 0.02, z: 0.01 } }, label: "DEFAULT", + setting: { + key: "VREdit.physicsTool.presetLabel", + command: "XXX" + }, command: { method: "togglePhysicsPresets" }, @@ -924,11 +928,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlays = []; optionsOverlaysIDs = []; + optionsOverlaysLabels = []; optionsSliderData = []; optionsEnabled = []; optionsItems = null; - - optionsPicklistItemLabelOverlays = []; } function openOptions(toolOptions) { @@ -986,6 +989,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Store value in optionsSettings rather than using overlay property. optionsSettings[optionsItems[i].id].value = value; } + if (optionsItems[i].type === "picklist") { + // Value is picklist label. + optionsItems[i].label = value; + } if (optionsItems[i].setting.callback) { uiCommandCallback(optionsItems[i].setting.callback, value); } @@ -998,9 +1005,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.text = optionsItems[i].label; properties.parentID = optionsOverlays[optionsOverlays.length - 1]; id = Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); - if (optionsItems[i].type === "picklistItem") { - optionsPicklistItemLabelOverlays.push(id); - } + optionsOverlaysLabels[i] = id; } if (optionsItems[i].type === "barSlider") { @@ -1107,6 +1112,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { value, items, parentID, + label, i, length; @@ -1172,15 +1178,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Hide options. items = optionsItems[index].items; for (i = 0, length = items.length; i < length; i += 1) { - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(items[i])], { + index = optionsOverlaysIDs.indexOf(items[i]); + Overlays.editOverlay(optionsOverlays[index], { localPosition: Vec3.ZERO, visible: false }); - } - - // Hide labels. - for (i = 0, length = optionsPicklistItemLabelOverlays.length; i < length; i += 1) { - Overlays.editOverlay(optionsPicklistItemLabelOverlays[i], { + Overlays.editOverlay(optionsOverlaysLabels[index], { visible: false }); } @@ -1201,27 +1204,32 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Show options. items = optionsItems[index].items; for (i = 0, length = items.length; i < length; i += 1) { - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(items[i])], { + index = optionsOverlaysIDs.indexOf(items[i]); + Overlays.editOverlay(optionsOverlays[index], { parentID: parentID, localPosition: { x: 0, y: (i + 1) * -UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, visible: true }); - } - - // Show labels. - for (i = 0, length = optionsPicklistItemLabelOverlays.length; i < length; i += 1) { - Overlays.editOverlay(optionsPicklistItemLabelOverlays[i], { + Overlays.editOverlay(optionsOverlaysLabels[index], { visible: true }); } + } break; case "pickPhysicsPreset": - doCommand("togglePhysicsPresets", "presets"); // Close picklist. + // Close picklist. + doCommand("togglePhysicsPresets", "presets"); + + // Update picklist label. + index = optionsOverlaysIDs.indexOf(parameter); + label = optionsItems[index].label; + Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("presets")], { + text: label + }); + Settings.setValue(optionsSettings.presets.key, label); - // TODO: Update picklist label. - // TODO: Set picklist setting to record picklist label. // TODO: Set physics parameters - update sliders, settings, and to-apply-values. break; From f9de451b309a0f940ef9d6edc798945c2f4dc675 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 21:11:10 +1200 Subject: [PATCH 195/504] Make picklist label display "CUSTOM" when slider value is changed --- scripts/vr-edit/modules/toolMenu.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index fbbfecc287..3b59715c6f 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -990,7 +990,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSettings[optionsItems[i].id].value = value; } if (optionsItems[i].type === "picklist") { - // Value is picklist label. + optionsSettings[optionsItems[i].id].value = value; optionsItems[i].label = value; } if (optionsItems[i].setting.callback) { @@ -1094,6 +1094,17 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { closeOptions(); } + function setPresetsLabelToCustom() { + var CUSTOM = "CUSTOM"; + if (optionsSettings.presets.value !== CUSTOM) { + optionsSettings.presets.value = CUSTOM; + Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("presets")], { + text: CUSTOM + }); + Settings.setValue(optionsSettings.presets.key, CUSTOM); + } + } + function evaluateParameter(parameter) { var parameters, overlayID, @@ -1223,8 +1234,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand("togglePhysicsPresets", "presets"); // Update picklist label. - index = optionsOverlaysIDs.indexOf(parameter); - label = optionsItems[index].label; + label = optionsItems[optionsOverlaysIDs.indexOf(parameter)].label; + optionsSettings.presets.value = label; Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("presets")], { text: label }); @@ -1235,18 +1246,22 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "setGravity": + setPresetsLabelToCustom(); Settings.setValue(optionsSettings.gravitySlider.key, parameter); uiCommandCallback("setGravity", parameter); break; case "setBounce": + setPresetsLabelToCustom(); Settings.setValue(optionsSettings.bounceSlider.key, parameter); uiCommandCallback("setBounce", parameter); break; case "setDamping": + setPresetsLabelToCustom(); Settings.setValue(optionsSettings.dampingSlider.key, parameter); uiCommandCallback("setDamping", parameter); break; case "setDensity": + setPresetsLabelToCustom(); Settings.setValue(optionsSettings.densitySlider.key, parameter); uiCommandCallback("setDensity", parameter); break; From 2c2f866c8204ec02b5a0c9fd39b27da0e42d4bca Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 16 Aug 2017 21:33:34 +1200 Subject: [PATCH 196/504] Close picklist if click elsewhere --- scripts/vr-edit/modules/toolMenu.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 3b59715c6f..07f7a863ca 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -588,8 +588,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, label: "DEFAULT", setting: { - key: "VREdit.physicsTool.presetLabel", - command: "XXX" + key: "VREdit.physicsTool.presetLabel" }, command: { method: "togglePhysicsPresets" @@ -932,6 +931,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSliderData = []; optionsEnabled = []; optionsItems = null; + + isPicklistOpen = false; } function openOptions(toolOptions) { @@ -1225,7 +1226,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }); } - } break; @@ -1446,6 +1446,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand(intersectionItems[intersectedItem].command.method, intersectionItems[intersectedItem].id); } } + if (intersectionItems && isPicklistOpen && controlHand.triggerClicked() + && intersectionItems[intersectedItem].type !== "picklist" + && intersectionItems[intersectedItem].type !== "picklistItem") { + doCommand("togglePhysicsPresets", "presets"); // TODO: This is a bit hacky. + } // Grip click. if (controlHand.gripClicked() !== isGripClicked) { From 2287e16ea75e9dd8e51c450926dce222c0645733 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 17 Aug 2017 00:04:14 +1200 Subject: [PATCH 197/504] Set physics values per picklist items --- scripts/vr-edit/modules/toolMenu.js | 88 +++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 07f7a863ca..130aa68e3a 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -261,6 +261,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { PICKLIST_UI_ELEMENTS = ["picklist", "picklistItem"], PICKLIST_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, + PHYSICS_SLIDER_PRESETS = { + // Slider values in the range 0.0 to 1.0. + // Note: Damping values give the desired linear and angular damping values but friction values are a somewhat out, + // especially for the balloon. + presetDefault: { gravity: 0.5, bounce: 0.5, damping: 0.5, density: 0.5 }, + presetLead: { gravity: 0.5, bounce: 0.0, damping: 0.5, density: 1.0 }, + presetWood: { gravity: 0.5, bounce: 0.4, damping: 0.5, density: 0.5 }, + presetIce: { gravity: 0.5, bounce: 0.99, damping: 0.151004, density: 0.349485 }, + presetRubber: { gravity: 0.5, bounce: 0.99, damping: 0.5, density: 0.5 }, + presetCotton: { gravity: 0.587303, bounce: 0.0, damping: 0.931878, density: 0.0 }, + presetTumbleweed: { gravity: 0.595893, bounce: 0.7, damping: 0.5, density: 0.0 }, + presetZeroG: { gravity: 0.596844, bounce: 0.5, damping: 0.5, density: 0.5 }, + presetBalloon: { gravity: 0.606313, bounce: 0.99, damping: 0.151004, density: 0.0 } + }, OPTONS_PANELS = { groupOptions: [ @@ -600,9 +614,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "presetIce", "presetRubber", "presetCotton", - "presetTumbleWeed", + "presetTumbleweed", "presetZeroG", - "presetBallon" + "presetBalloon" ] }, { @@ -642,7 +656,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "pickPhysicsPreset" } }, { - id: "presetTumbleWeed", + id: "presetTumbleweed", type: "picklistItem", label: "TUMBLEWEED", command: { method: "pickPhysicsPreset" } @@ -654,7 +668,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { command: { method: "pickPhysicsPreset" } }, { - id: "presetBallon", + id: "presetBalloon", type: "picklistItem", label: "BALLOON", command: { method: "pickPhysicsPreset" } @@ -1106,6 +1120,35 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + function setBarSliderValue(item, fraction) { + var overlayDimensions, + otherFraction; + + overlayDimensions = optionsItems[item].properties.dimensions; + if (overlayDimensions === undefined) { + overlayDimensions = UI_ELEMENTS.barSlider.properties.dimensions; + } + + otherFraction = 1.0 - fraction; + + Overlays.editOverlay(optionsSliderData[item].value, { + localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, + dimensions: { + x: overlayDimensions.x, + y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), + z: overlayDimensions.z + } + }); + Overlays.editOverlay(optionsSliderData[item].remainder, { + localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, + dimensions: { + x: overlayDimensions.x, + y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), + z: overlayDimensions.z + } + }); + } + function evaluateParameter(parameter) { var parameters, overlayID, @@ -1125,6 +1168,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { items, parentID, label, + values, i, length; @@ -1241,7 +1285,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); Settings.setValue(optionsSettings.presets.key, label); - // TODO: Set physics parameters - update sliders, settings, and to-apply-values. + // Update sliders. + values = PHYSICS_SLIDER_PRESETS[parameter]; + setBarSliderValue(optionsOverlaysIDs.indexOf("gravitySlider"), values.gravity); + Settings.setValue(optionsSettings.gravitySlider.key, values.gravity); + uiCommandCallback("setGravity", values.gravity); + setBarSliderValue(optionsOverlaysIDs.indexOf("bounceSlider"), values.bounce); + Settings.setValue(optionsSettings.bounceSlider.key, values.bounce); + uiCommandCallback("setBounce", values.bounce); + setBarSliderValue(optionsOverlaysIDs.indexOf("dampingSlider"), values.damping); + Settings.setValue(optionsSettings.dampingSlider.key, values.damping); + uiCommandCallback("setDamping", values.damping); + setBarSliderValue(optionsOverlaysIDs.indexOf("densitySlider"), values.density); + Settings.setValue(optionsSettings.densitySlider.key, values.density); + uiCommandCallback("setDensity", values.density); break; @@ -1306,8 +1363,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { sliderProperties, overlayDimensions, basePoint, - fraction, - otherFraction; + fraction; // Intersection details. if (intersection.overlayID) { @@ -1474,23 +1530,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); - otherFraction = 1.0 - fraction; - Overlays.editOverlay(optionsSliderData[intersectedItem].value, { - localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, - dimensions: { - x: overlayDimensions.x, - y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), - z: overlayDimensions.z - } - }); - Overlays.editOverlay(optionsSliderData[intersectedItem].remainder, { - localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, - dimensions: { - x: overlayDimensions.x, - y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), - z: overlayDimensions.z - } - }); + setBarSliderValue(intersectedItem, fraction); if (intersectionItems[intersectedItem].command) { doCommand(intersectionItems[intersectedItem].command.method, fraction); } From 90c2036be77e602712e2de1eccb679bffeb6bc64 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 17 Aug 2017 00:08:09 +1200 Subject: [PATCH 198/504] Fix physics options labels being displayed when options pane opens --- scripts/vr-edit/modules/toolMenu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 130aa68e3a..744b11ae05 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -1019,6 +1019,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS.label.properties); properties.text = optionsItems[i].label; properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + properties.visible = optionsItems[i].type !== "picklistItem"; id = Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); optionsOverlaysLabels[i] = id; } From 35d06c0244da377521b5ccdf80e53fb3e5972a50 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 17 Aug 2017 10:12:28 +1200 Subject: [PATCH 199/504] Make space for color circle --- scripts/vr-edit/modules/toolMenu.js | 92 +++-------------------------- 1 file changed, 8 insertions(+), 84 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 744b11ae05..de57bcd231 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -341,12 +341,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.03, z: -0.005 } + localPosition: { x: -0.035, y: 0.02, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch1Color", property: "color", - defaultValue: { red: 0, green: 255, blue: 255 } + defaultValue: { red: 0, green: 255, blue: 0 } }, command: { method: "setColorPerSwatch" @@ -360,12 +360,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.03, z: -0.005 } + localPosition: { x: -0.01, y: 0.02, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch2Color", property: "color", - defaultValue: { red: 255, green: 0, blue: 255 } + defaultValue: { red: 0, green: 0, blue: 255 } }, command: { method: "setColorPerSwatch" @@ -379,12 +379,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.005, z: -0.005 } + localPosition: { x: -0.035, y: 0.045, z: -0.005 } }, setting: { key: "VREdit.colorTool.swatch3Color", - property: "color", - defaultValue: { red: 255, green: 255, blue: 0 } + property: "color" + // Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -396,88 +396,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { { id: "colorSwatch4", type: "swatch", - properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.005, z: -0.005 } - }, - setting: { - key: "VREdit.colorTool.swatch4Color", - property: "color", - defaultValue: { red: 255, green: 0, blue: 0 } - }, - command: { - method: "setColorPerSwatch" - }, - clear: { - method: "clearSwatch" - } - }, - { - id: "colorSwatch5", - type: "swatch", - properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.02, z: -0.005 } - }, - setting: { - key: "VREdit.colorTool.swatch5Color", - property: "color", - defaultValue: { red: 0, green: 255, blue: 0 } - }, - command: { - method: "setColorPerSwatch" - }, - clear: { - method: "clearSwatch" - } - }, - { - id: "colorSwatch6", - type: "swatch", - properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.02, z: -0.005 } - }, - setting: { - key: "VREdit.colorTool.swatch6Color", - property: "color", - defaultValue: { red: 0, green: 0, blue: 255 } - }, - command: { - method: "setColorPerSwatch" - }, - clear: { - method: "clearSwatch" - } - }, - { - id: "colorSwatch7", - type: "swatch", - properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.045, z: -0.005 } - }, - setting: { - key: "VREdit.colorTool.swatch7Color", - property: "color" - // Default to empty swatch. - }, - command: { - method: "setColorPerSwatch" - }, - clear: { - method: "clearSwatch" - } - }, - { - id: "colorSwatch8", - type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, localPosition: { x: -0.01, y: 0.045, z: -0.005 } }, setting: { - key: "VREdit.colorTool.swatch8Color", + key: "VREdit.colorTool.swatch4Color", property: "color" // Default to empty swatch. }, From 069102f68b00e83d835235bbda8982e2f799f119 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 17 Aug 2017 13:59:56 +1200 Subject: [PATCH 200/504] Initial color circle UI elements --- scripts/vr-edit/assets/color-circle-black.png | Bin 0 -> 5270 bytes scripts/vr-edit/assets/color-circle.png | Bin 0 -> 57223 bytes scripts/vr-edit/assets/slider-white-alpha.png | Bin 564 -> 537 bytes scripts/vr-edit/assets/slider-white.png | Bin 187 -> 196 bytes scripts/vr-edit/modules/toolMenu.js | 216 +++++++++++++++--- scripts/vr-edit/vr-edit.js | 7 - 6 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 scripts/vr-edit/assets/color-circle-black.png create mode 100644 scripts/vr-edit/assets/color-circle.png diff --git a/scripts/vr-edit/assets/color-circle-black.png b/scripts/vr-edit/assets/color-circle-black.png new file mode 100644 index 0000000000000000000000000000000000000000..3494b63b708f8ba2945cc6ac445c0c5bc7a13444 GIT binary patch literal 5270 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 Q006+~tofNTJ=Yum2Y0~9`Tzg` literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/color-circle.png b/scripts/vr-edit/assets/color-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..b1ba4fe80fb76afa2e6877c8a850b27729ad90d6 GIT binary patch literal 57223 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

=Syo>!mhk!h%2ZyWNNLNjIjD1KPrsD|0yF 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 RpECde002ovPDHLkV1lt8)G`17 delta 502 zcmV(RA@u(meEE8VHk$@FA|YRBodKG zq?3qr(s3jb-9aR}gGh7-k?0O0(OD!CiF6W?NF*|3+nAl*+3A0I?_i&$v=2Ye(dU`@ zzhOe7(I`qF8kpM=LQxc%9YKYu9YKZPb_5lE*%4IuX-Ck&#EzhWA9e%{e77U$Qr_%; z2r7KFBdGAjj-bM4JAw+I>^#qpqF`uk4($jkJg_6EaNmy5UhL+7PfHeiFA9d%X4j6O!W}z;3b*YD?Zs~Xv}Ccj zqF`ukHth&1+^{34aNUm3UhL)%zu5J9eJu)x)@Id?pu!b9f(n=I2r68%BdBoEj_@BB zyH=|$M8UuxS+_hN1w)TKXGc)stQ|oEXY2?qxs>TB7$M8DDLaA+C+!HKTCGlD*b!7X zZbt}dnvU5KR5)r!(7+Kpf(8!T5j1efju0x9%Ag%Vg#&hkP%fAI?FcIDvm=BgNqX%F sD(tZ%XkfP;K?A$&2pZUF2Pl<(07C&KznWFn9RL6T07*qoM6N<$f@8zjdH?_b diff --git a/scripts/vr-edit/assets/slider-white.png b/scripts/vr-edit/assets/slider-white.png index 2097e1bcdad46d74ddf06744cb85f7cf4208f2d7..5d55f7c71d524d8985c5222f62d0cc41480337ad 100644 GIT binary patch delta 54 zcmV-60LlNm0mK227YcX?1^@s6^1sq7ks&n!S&>&vk&Xx%?(f5y13F0r>^cC7h5!Hn M07*qoM6N<$g74cB2mk;8 delta 50 zcmX@YxSMf;I9Cb>8v_GF+iHO`6BSJv115(1NbKiN=j(Q1IkNKC^ 1; @@ -1570,6 +1723,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedSource = null; isHighlightingButton = false; isHighlightingSlider = false; + isHighlightingColorCircle = false; isHighlightingPicklist = false; isPicklistOpen = false; pressedItem = null; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index dd93ed420f..eca85994b9 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1457,13 +1457,6 @@ } break; - case "setSliderValue": - if (parameter !== undefined) { - // TODO - print("setSliderValue = " + parameter); - } - break; - default: log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); } From 32113e84e483dbba4099bef5bd45326118fd7846 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 17 Aug 2017 14:45:47 +1200 Subject: [PATCH 201/504] Move color circle crosshairs witih laser --- scripts/vr-edit/modules/toolMenu.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6d444fe423..45e6ce7869 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -1118,6 +1118,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; auxiliaryProperties.visible = false; optionsColorData[i].value = Overlays.addOverlay(UI_ELEMENTS.sphere.overlay, auxiliaryProperties); + optionsColorData[i].maxRadius = childProperties.scale / 2; auxiliaryProperties = Object.clone(UI_ELEMENTS.circlePointer.properties); auxiliaryProperties.parentID = optionsColorData[i].value; @@ -1413,7 +1414,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { sliderProperties, overlayDimensions, basePoint, - fraction; + fraction, + delta, + radius; // Intersection details. if (intersection.overlayID) { @@ -1476,7 +1479,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: HIGHLIGHT_PROPERTIES.properties.localPosition.x, y: HIGHLIGHT_PROPERTIES.properties.localPosition.z, - z: HIGHLIGHT_PROPERTIES.properties.localPosition.y + z: HIGHLIGHT_PROPERTIES.properties.localPosition.y }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }), color: HIGHLIGHT_PROPERTIES.properties.color, @@ -1632,7 +1635,20 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Color circle update. if (intersectionItems && intersectionItems[intersectedItem].type === "colorCircle" && controlHand.triggerClicked()) { - // TODO + sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); + delta = Vec3.multiplyQbyV(Quat.inverse(sliderProperties.orientation), + Vec3.subtract(intersection.intersection, sliderProperties.position)); + radius = Vec3.length(delta); + if (radius > optionsColorData[intersectedItem].maxRadius) { + delta = Vec3.multiply(optionsColorData[intersectedItem].maxRadius / radius, delta); + } + Overlays.editOverlay(optionsColorData[intersectedItem].value, { + localPosition: Vec3.sum(optionsColorData[intersectedItem].offset, + { x: delta.x, y: 0, z: delta.z }) + }); + if (intersectionItems[intersectedItem].callback) { + uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + } } // Special handling for Group options. From 5dd74d71fb9feb242b559b6ffd93763e8526582f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 18 Aug 2017 09:55:09 +1200 Subject: [PATCH 202/504] Activate the color picker --- scripts/vr-edit/assets/color-circle.png | Bin 57223 -> 76746 bytes scripts/vr-edit/assets/slider-v-alpha.png | Bin 0 -> 911 bytes scripts/vr-edit/assets/slider-white-alpha.png | Bin 537 -> 0 bytes scripts/vr-edit/modules/toolMenu.js | 198 +++++++++++++++--- 4 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 scripts/vr-edit/assets/slider-v-alpha.png delete mode 100644 scripts/vr-edit/assets/slider-white-alpha.png diff --git a/scripts/vr-edit/assets/color-circle.png b/scripts/vr-edit/assets/color-circle.png index b1ba4fe80fb76afa2e6877c8a850b27729ad90d6..408139972ebd4fc7254c19a67dbc9ab20c1e3cc3 100644 GIT binary patch literal 76746 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

=Syo>!mhk!h%2ZyWNNLNjIjD1KPrsD|0yF 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 b==FX9(_r-}=Hs6;00000NkvXXu0mjfHR9i$ diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 45e6ce7869..8bd39f0e35 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -151,7 +151,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.1, y: 0.1 }, localPosition: { x: 0, y: 0, z: 0 }, - localRotation: Quat.ZERO, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, ignoreRayIntersection: true, @@ -379,12 +379,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorSlider", type: "imageSlider", properties: { - localPosition: { x: 0.035, y: -0.025, z: -0.005 }, - color: { red: 255, green: 0, blue: 0 } + localPosition: { x: 0.035, y: -0.025, z: -0.005 } }, useBaseColor: true, imageURL: "../assets/slider-white.png", - imageOverlayURL: "../assets/slider-white-alpha.png", + imageOverlayURL: "../assets/slider-v-alpha.png", command: { method: "setColorPerSlider" } @@ -474,7 +473,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.currentColor", property: "color", - defaultValue: { red: 128, green: 128, blue: 128 } + defaultValue: { red: 128, green: 128, blue: 128 }, + command: "setPickColor" } }, { @@ -880,6 +880,12 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { groupButtonIndex, ungroupButtonIndex, + hsvControl = { + hsv: { h: 0, s: 0, v: 0 }, + circle: {}, + slider: {} + }, + isDisplaying = false, // References. @@ -988,6 +994,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSettings[optionsItems[i].id].value = value; optionsItems[i].label = value; } + if (optionsItems[i].setting.command) { + doCommand(optionsItems[i].setting.command, value); + } if (optionsItems[i].setting.callback) { uiCommandCallback(optionsItems[i].setting.callback, value); } @@ -1043,8 +1052,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties.color = properties.color; } childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + hsvControl.slider.localPosition = childProperties.localPosition; childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + hsvControl.slider.colorOverlay = Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + hsvControl.slider.length = properties.dimensions.y; } // Overlay image. @@ -1066,10 +1077,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); auxiliaryProperties.localPosition = optionsSliderData[i].offset; + hsvControl.slider.localPosition = auxiliaryProperties.localPosition; auxiliaryProperties.drawInFront = true; // TODO: Accommodate work-around above; remove when bug fixed. auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; - optionsSliderData[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, - auxiliaryProperties); + optionsSliderData[i].value = Overlays.addOverlay(UI_ELEMENTS.sliderPointer.overlay, auxiliaryProperties); + hsvControl.slider.pointerOverlay = optionsSliderData[i].value; auxiliaryProperties.localPosition = { x: 0, y: properties.dimensions.x, z: 0 }; auxiliaryProperties.localRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); auxiliaryProperties.parentID = optionsSliderData[i].value; @@ -1102,10 +1114,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties.scale = 0.95 * properties.dimensions.x; // TODO: Magic number. imageOffset += IMAGE_OFFSET; childProperties.localPosition = { x: 0, y: -properties.dimensions.y / 2 - imageOffset, z: 0 }; - childProperties.localRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }); + childProperties.localRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; childProperties.alpha = 0.0; - Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); + hsvControl.circle.overlay = Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); } // Value pointers. @@ -1118,7 +1130,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; auxiliaryProperties.visible = false; optionsColorData[i].value = Overlays.addOverlay(UI_ELEMENTS.sphere.overlay, auxiliaryProperties); - optionsColorData[i].maxRadius = childProperties.scale / 2; + hsvControl.circle.radius = childProperties.scale / 2; + hsvControl.circle.localPosition = auxiliaryProperties.localPosition; + hsvControl.circle.cursorOverlay = optionsColorData[i].value; auxiliaryProperties = Object.clone(UI_ELEMENTS.circlePointer.properties); auxiliaryProperties.parentID = optionsColorData[i].value; @@ -1196,8 +1210,103 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); } - function setColorCircleValue() { - // TODO + function hsvToRGB(hsv) { + // https://en.wikipedia.org/wiki/HSL_and_HSV + var c, h, x, rgb, m; + + c = hsv.v * hsv.s; + h = hsv.h * 6.0; + x = c * (1 - Math.abs(h % 2 - 1)); + if (0 <= h && h <= 1) { + rgb = { red: c, green: x, blue: 0 }; + } else if (1 < h && h <= 2) { + rgb = { red: x, green: c, blue: 0 }; + } else if (2 < h && h <= 3) { + rgb = { red: 0, green: c, blue: x }; + } else if (3 < h && h <= 4) { + rgb = { red: 0, green: x, blue: c }; + } else if (4 < h && h <= 5) { + rgb = { red: x, green: 0, blue: c }; + } else { + rgb = { red: c, green: 0, blue: x }; + } + m = hsv.v - c; + rgb = { + red: Math.round((rgb.red + m) * 255), + green: Math.round((rgb.green + m) * 255), + blue: Math.round((rgb.blue + m) * 255) + }; + return rgb; + } + + function rgbToHSV(rgb) { + // https://en.wikipedia.org/wiki/HSL_and_HSV + var mMax, mMin, c, h, v, s; + + mMax = Math.max(rgb.red, rgb.green, rgb.blue); + mMin = Math.min(rgb.red, rgb.green, rgb.blue); + c = mMax - mMin; + + if (c === 0) { + h = 0; + } else if (mMax === rgb.red) { + h = ((rgb.green - rgb.blue) / c) % 6; + } else if (mMax === rgb.green) { + h = (rgb.blue - rgb.red) / c + 2; + } else { + h = (rgb.red - rgb.green) / c + 4; + } + h = h / 6; + v = mMax / 255; + s = v === 0 ? 0 : c / mMax; + return { h: h, s: s, v: v }; + } + + function updateColorCircle() { + var theta, r, x, y; + + // V overlay alpha per v. + Overlays.editOverlay(hsvControl.circle.overlay, { alpha: 1.0 - hsvControl.hsv.v }); + + // Cursor position per h & s. + theta = 2 * Math.PI * hsvControl.hsv.h; + r = hsvControl.hsv.s * hsvControl.circle.radius; + x = r * Math.cos(theta); + y = r * Math.sin(theta); + Overlays.editOverlay(hsvControl.circle.cursorOverlay, { + localPosition: { x: y, y: hsvControl.circle.localPosition.y, z: -x } + }); + } + + function updateColorSlider() { + // Base color per h & s. + Overlays.editOverlay(hsvControl.slider.colorOverlay, { + color: hsvToRGB({ h: hsvControl.hsv.h, s: hsvControl.hsv.s, v: 1.0 }) + }); + + // Slider position per v. + Overlays.editOverlay(hsvControl.slider.pointerOverlay, { + localPosition: { + x: hsvControl.slider.localPosition.x, + y: (0.5 - hsvControl.hsv.v) * hsvControl.slider.length, + z: hsvControl.slider.localPosition.z + } + }); + } + + function setColorPicker(rgb) { + hsvControl.hsv = rgbToHSV(rgb); + updateColorCircle(); + updateColorSlider(); + } + + function setCurrentColor(rgb) { + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { + color: rgb + }); + if (optionsSettings.currentColor) { + Settings.setValue(optionsSettings.currentColor.key, rgb); + } } function evaluateParameter(parameter) { @@ -1225,17 +1334,32 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { switch (command) { + case "setPickColor": + setColorPicker(parameter); + break; + + case "setColorPerCircle": + hsvControl.hsv.h = parameter.h; + hsvControl.hsv.s = parameter.s; + updateColorSlider(); + setCurrentColor(hsvToRGB(hsvControl.hsv)); + uiCommandCallback("setColor", value); + break; + + case "setColorPerSlider": + hsvControl.hsv.v = parameter; + updateColorCircle(); + setCurrentColor(hsvToRGB(hsvControl.hsv)); + uiCommandCallback("setColor", value); + break; + case "setColorPerSwatch": index = optionsOverlaysIDs.indexOf(parameter); hasColor = Overlays.getProperty(optionsOverlays[index], "solid"); if (hasColor) { value = Overlays.getProperty(optionsOverlays[index], "color"); - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { - color: value - }); - if (optionsSettings.currentColor) { - Settings.setValue(optionsSettings.currentColor.key, value); - } + setCurrentColor(value); + setColorPicker(value); uiCommandCallback("setColor", value); } else { // Swatch has no color; set swatch color to current fill color. @@ -1251,12 +1375,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "setColorFromPick": - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], { - color: parameter - }); - if (optionsSettings.currentColor) { - Settings.setValue(optionsSettings.currentColor.key, parameter); - } + setCurrentColor(parameter); + setColorPicker(parameter); break; case "setGravityOn": @@ -1416,7 +1536,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { basePoint, fraction, delta, - radius; + radius, + x, + y, + s, + h; // Intersection details. if (intersection.overlayID) { @@ -1468,7 +1592,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ["dimensions", "localPosition"]); if (isHighlightingColorCircle) { // Cylinder used has different coordinate system to other elements. - // TODO: Should be able to remove this special case when UI look is reword. + // TODO: Should be able to remove this special case when UI look is reworked. Overlays.editOverlay(highlightOverlay, { parentID: intersectionOverlays[intersectedItem], dimensions: { @@ -1628,8 +1752,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: Vec3.sum(optionsSliderData[intersectedItem].offset, { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) }); - if (intersectionItems[intersectedItem].callback) { - uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + if (intersectionItems[intersectedItem].command) { + doCommand(intersectionItems[intersectedItem].command.method, fraction); } } @@ -1639,15 +1763,23 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delta = Vec3.multiplyQbyV(Quat.inverse(sliderProperties.orientation), Vec3.subtract(intersection.intersection, sliderProperties.position)); radius = Vec3.length(delta); - if (radius > optionsColorData[intersectedItem].maxRadius) { - delta = Vec3.multiply(optionsColorData[intersectedItem].maxRadius / radius, delta); + if (radius > hsvControl.circle.radius) { + delta = Vec3.multiply(hsvControl.circle.radius / radius, delta); } Overlays.editOverlay(optionsColorData[intersectedItem].value, { localPosition: Vec3.sum(optionsColorData[intersectedItem].offset, { x: delta.x, y: 0, z: delta.z }) }); - if (intersectionItems[intersectedItem].callback) { - uiCommandCallback(intersectionItems[intersectedItem].callback.method, fraction); + if (intersectionItems[intersectedItem].command) { + // Cartesian planar coordinates. + x = -delta.z; + y = delta.x; + s = Math.sqrt(x * x + y * y) / hsvControl.circle.radius; + h = Math.atan2(y, x) / (2 * Math.PI); + if (h < 0) { + h = h + 1; + } + doCommand(intersectionItems[intersectedItem].command.method, { h: h, s: s }); } } From c3f96b672738002a1edc0ad8f77d45b71b55c4d1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 18 Aug 2017 13:27:38 +1200 Subject: [PATCH 203/504] Fix damping property name --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index eca85994b9..e278ff12a9 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1435,7 +1435,7 @@ case "setDamping": if (parameter !== undefined) { // Power range 0.0, 0.5, 1.0 maps to 0, 0.39, 1.0. - physicsToolPhysics.linearDamping = 0.69136364 * Math.pow(2.446416831, parameter) - 0.691364; + physicsToolPhysics.damping = 0.69136364 * Math.pow(2.446416831, parameter) - 0.691364; // Power range 0.0, 0.5, 1.0 maps to 0, 0.3935, 1.0. physicsToolPhysics.angularDamping = 0.72695892 * Math.pow(2.375594, parameter) - 0.726959; // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; From 9434ef44c3c40f99bea77b389d213143c129db00 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Aug 2017 14:15:34 +1200 Subject: [PATCH 204/504] Delay physics kick in order to avoid some erratic behavior --- scripts/vr-edit/modules/selection.js | 51 +++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 75e7b7e105..b28a95817d 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -25,9 +25,7 @@ Selection = function (side) { scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"], - DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.02, z: 0 }; + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"]; if (!this instanceof Selection) { @@ -188,6 +186,30 @@ Selection = function (side) { }; } + function doKick(entityID) { + var properties, + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; + + if (entityID === rootEntityID) { + // Don't kick if have started editing entity again. + return; + } + + properties = Entities.getEntityProperties(entityID, ["velocity", "gravity", "parentID"]); + if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(entityID, { velocity: DYNAMIC_VELOCITY_KICK }); + } + } + + function kickPhysics(entityID) { + // Gives entities a small kick to start off physics, if necessary. + var KICK_DELAY = 500; // ms + + // Give physics a chance to catch up. Avoids some erratic behavior. + Script.setTimeout(function () { doKick(entityID); }, KICK_DELAY); + } + function startEditing() { var count, i; @@ -202,28 +224,20 @@ Selection = function (side) { } function finishEditing() { - var firstDynamicEntityID = null, - properties, - count, + var count, i; // Restore entity set's physics. for (i = 0, count = selection.length; i < count; i += 1) { - if (firstDynamicEntityID === null && selection[i].dynamic) { - firstDynamicEntityID = selection[i].id; - } Entities.editEntity(selection[i].id, { dynamic: selection[i].dynamic, collisionless: selection[i].collisionless }); } - // If dynamic with gravity, and velocity is zero, give the entity set a little kick to set off physics. - if (firstDynamicEntityID) { - properties = Entities.getEntityProperties(firstDynamicEntityID, ["velocity", "gravity"]); - if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { - Entities.editEntity(firstDynamicEntityID, { velocity: DYNAMIC_VELOCITY_KICK }); - } + // Kick off physics if necessary. + if (selection.length > 0 && selection[0].dynamic) { + kickPhysics(selection[0].id); } } @@ -422,12 +436,9 @@ Selection = function (side) { properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); Entities.editEntity(rootEntityID, properties); + // Kick off physics if necessary. if (physicsProperties.dynamic) { - // Give dynamic entities with zero velocity a little kick to set off physics. - properties = Entities.getEntityProperties(rootEntityID, ["velocity"]); - if (Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { - Entities.editEntity(rootEntityID, { velocity: DYNAMIC_VELOCITY_KICK }); - } + kickPhysics(rootEntityID); } } From 5b2d5b01743bdeafafa8d359bdd8d384738ab2af Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Aug 2017 20:48:45 +1200 Subject: [PATCH 205/504] Minor merge fix --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 8457e27bc3..aef5c73fa3 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -354,7 +354,7 @@ void HmdDisplayPlugin::updateFrameData() { auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; _overlayRenderer.mvps[eye] = _eyeProjections[eye] * modelView; }); - } +} void HmdDisplayPlugin::OverlayRenderer::build() { vertices = std::make_shared(); From e34e8ff2c09bcea07f028b3ede4eda55dd417136 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Aug 2017 13:24:07 +1200 Subject: [PATCH 206/504] Fix color circle and slider changes not being applied when coloring --- scripts/vr-edit/modules/toolMenu.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 8bd39f0e35..dc58ce1bc9 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -1342,14 +1342,16 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { hsvControl.hsv.h = parameter.h; hsvControl.hsv.s = parameter.s; updateColorSlider(); - setCurrentColor(hsvToRGB(hsvControl.hsv)); + value = hsvToRGB(hsvControl.hsv); + setCurrentColor(value); uiCommandCallback("setColor", value); break; case "setColorPerSlider": hsvControl.hsv.v = parameter; updateColorCircle(); - setCurrentColor(hsvToRGB(hsvControl.hsv)); + value = hsvToRGB(hsvControl.hsv); + setCurrentColor(value); uiCommandCallback("setColor", value); break; From ca3eadb82f72018b949bb27a339edd8ca9266f4a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Aug 2017 16:04:34 +1200 Subject: [PATCH 207/504] Don't display menu buttons when tool options panel open, and vice versa --- scripts/vr-edit/modules/toolMenu.js | 66 ++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index dc58ce1bc9..a16d504370 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -912,13 +912,52 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { return [menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); } - function closeOptions() { + function openMenu() { + var parentID, + properties, + i, + length; + + parentID = menuPanelOverlay; // Menu panel parents to background panel. + for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); + properties = Object.merge(properties, MENU_ITEMS[i].properties); + properties.parentID = parentID; + menuOverlays.push(Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties)); + if (MENU_ITEMS[i].label) { + properties = Object.clone(UI_ELEMENTS.label.properties); + properties.text = MENU_ITEMS[i].label; + properties.parentID = menuOverlays[menuOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); + } + parentID = menuOverlays[0]; // Menu buttons parent to menu panel. + } + } + + function closeMenu() { var i, length; Overlays.editOverlay(highlightOverlay, { parentID: menuOriginOverlay }); + + for (i = 0, length = menuOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(menuOverlays[i]); + } + + menuOverlays = []; + } + + function closeOptions() { + var i, + length; + + // Remove options items. + Overlays.editOverlay(highlightOverlay, { + parentID: menuOriginOverlay + }); + for (i = 0, length = optionsOverlays.length; i < length; i += 1) { Overlays.deleteOverlay(optionsOverlays[i]); } @@ -932,6 +971,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsItems = null; isPicklistOpen = false; + + // Display menu items. + openMenu(true); } function openOptions(toolOptions) { @@ -947,15 +989,15 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { i, length; - // Close current panel, if any. - closeOptions(); + // Remove menu items. + closeMenu(); // TODO: Remove once all tools have an options panel. if (OPTONS_PANELS[toolOptions] === undefined) { return; } - // Open specified panel. + // Open specified options panel. optionsItems = OPTONS_PANELS[toolOptions]; parentID = menuPanelOverlay; // Menu panel parents to background panel. for (i = 0, length = optionsItems.length; i < length; i += 1) { @@ -1815,7 +1857,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Creates and shows menu entities. var handJointIndex, properties, - parentID, id, i, length; @@ -1845,20 +1886,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuPanelOverlay = Overlays.addOverlay("cube", properties); // Menu items. - parentID = menuPanelOverlay; // Menu panel parents to background panel. - for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { - properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); - properties = Object.merge(properties, MENU_ITEMS[i].properties); - properties.parentID = parentID; - menuOverlays.push(Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties)); - if (MENU_ITEMS[i].label) { - properties = Object.clone(UI_ELEMENTS.label.properties); - properties.text = MENU_ITEMS[i].label; - properties.parentID = menuOverlays[menuOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); - } - parentID = menuOverlays[0]; // Menu buttons parent to menu panel. - } + openMenu(); // Prepare highlight overlay. properties = Object.clone(HIGHLIGHT_PROPERTIES); From 0064c516108638d934580ac10df3af0f0db4dc6e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Aug 2017 16:24:55 +1200 Subject: [PATCH 208/504] Add "Finish" buttons for scale, clone, and delete tools --- scripts/vr-edit/modules/toolMenu.js | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index a16d504370..d720b7adeb 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -318,6 +318,50 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, OPTONS_PANELS = { + scaleOptions: [ + { + id: "scaleOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + }, + { + id: "scaleFinishButton", + type: "button", + properties: { + dimensions: { x: 0.07, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: 0, z: -0.005 }, + color: { red: 200, green: 200, blue: 200 } + }, + label: "FINISH", + command: { + method: "closeOptions" + } + } + ], + cloneOptions: [ + { + id: "cloneOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + }, + { + id: "cloneFinishButton", + type: "button", + properties: { + dimensions: { x: 0.07, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: 0, z: -0.005 }, + color: { red: 200, green: 200, blue: 200 } + }, + label: "FINISH", + command: { + method: "closeOptions" + } + } + ], groupOptions: [ { id: "groupOptionsPanel", @@ -750,6 +794,28 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: { x: 0.045, y: 0.057, z: -0.0075 } } } + ], + deleteOptions: [ + { + id: "deleteOptionsPanel", + type: "panel", + properties: { + localPosition: { x: 0.055, y: 0.0, z: -0.005 } + } + }, + { + id: "deleteFinishButton", + type: "button", + properties: { + dimensions: { x: 0.07, y: 0.03, z: 0.01 }, + localPosition: { x: 0, y: 0, z: -0.005 }, + color: { red: 200, green: 200, blue: 200 } + }, + label: "FINISH", + command: { + method: "closeOptions" + } + } ] }, @@ -769,6 +835,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: { red: 0, green: 240, blue: 240 } }, label: " SCALE", + toolOptions: "scaleOptions", callback: { method: "scaleTool" } @@ -781,6 +848,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: { red: 240, green: 240, blue: 0 } }, label: " CLONE", + toolOptions: "cloneOptions", callback: { method: "cloneTool" } @@ -833,6 +901,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: { red: 240, green: 60, blue: 60 } }, label: " DELETE", + toolOptions: "deleteOptions", callback: { method: "deleteTool" } @@ -1538,6 +1607,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { uiCommandCallback("setDensity", parameter); break; + case "closeOptions": + closeOptions(); + break; + default: App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } From fcf4831a3b61221ea9cf1e6f5e7f846f6a5fbcf2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Aug 2017 17:26:03 +1200 Subject: [PATCH 209/504] Center Tools menu and options UI --- scripts/vr-edit/modules/toolMenu.js | 36 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d720b7adeb..d212aee2f4 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -323,7 +323,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "scaleOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, { @@ -345,7 +345,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "cloneOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, { @@ -367,7 +367,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "groupOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, { @@ -404,7 +404,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, { @@ -540,7 +540,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "physicsOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, @@ -800,7 +800,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.055, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: -0.005 } } }, { @@ -824,7 +824,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "toolsMenuPanel", type: "panel", properties: { - localPosition: { x: -0.055, y: 0.0, z: -0.005 } + localPosition: { x: -0.0, y: 0.0, z: -0.005 } } }, { @@ -942,6 +942,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isButtonPressed, isPicklistPressed, isPicklistItemPressed, + isTriggerClicked, + wasTriggerClicked, isGripClicked, isGroupButtonEnabled, @@ -1016,6 +1018,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } menuOverlays = []; + pressedItem = null; } function closeOptions() { @@ -1041,6 +1044,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isPicklistOpen = false; + pressedItem = null; + // Display menu items. openMenu(true); } @@ -1681,7 +1686,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { - if (intersectedItem !== NONE && (intersectionItems[intersectedItem].command !== undefined + if (intersectedItem !== NONE && intersectionItems[intersectedItem] && + (intersectionItems[intersectedItem].command !== undefined || intersectionItems[intersectedItem].callback !== undefined)) { // Lower old slider or color circle. if (isHighlightingSlider || isHighlightingColorCircle) { @@ -1763,8 +1769,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Press/unpress button. + if (controlHand.triggerClicked() !== isTriggerClicked) { + isTriggerClicked = !isTriggerClicked; + } if ((pressedItem && intersectedItem !== pressedItem.index) || intersectionOverlays !== pressedSource - || controlHand.triggerClicked() !== isButtonPressed) { + || isTriggerClicked !== isButtonPressed) { if (pressedItem) { // Unpress previous button. Overlays.editOverlay(intersectionOverlays[pressedItem.index], { @@ -1772,9 +1781,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); pressedItem = null; } - isButtonPressed = isHighlightingButton && controlHand.triggerClicked(); - if (isButtonPressed && (intersectionEnabled === null || intersectionEnabled[intersectedItem])) { + if (isHighlightingButton && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) + && isTriggerClicked && !wasTriggerClicked) { // Press new button. + isButtonPressed = true; localPosition = intersectionItems[intersectedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) @@ -1924,6 +1934,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsEnabled[ungroupButtonIndex] = enableUngroupButton; } } + + wasTriggerClicked = isTriggerClicked; } function display() { @@ -1982,6 +1994,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isButtonPressed = false; isPicklistPressed = false; isPicklistItemPressed = false; + isTriggerClicked = false; + wasTriggerClicked = false; isGripClicked = false; isGroupButtonEnabled = false; isUngroupButtonEnabled = false; From cc0b95c70a3814515201bae566ca9a974f15aa09 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Aug 2017 17:32:40 +1200 Subject: [PATCH 210/504] Lint --- scripts/vr-edit/modules/toolMenu.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index d212aee2f4..c0b418cd1e 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -960,7 +960,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isDisplaying = false, // References. - controlHand; + controlHand, + + // Forward declarations. + doCommand; if (!this instanceof ToolMenu) { @@ -1437,7 +1440,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { return Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf(overlayID)], overlayProperty); } - function doCommand(command, parameter) { + doCommand = function (command, parameter) { var index, hasColor, value, @@ -1619,7 +1622,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { default: App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } - } + }; function doGripClicked(command, parameter) { var overlayID; From c953df00409a36c172279e3d7dbf6d417aed317f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 14:35:56 +1200 Subject: [PATCH 211/504] Fix "Finish" buttons not clearing tool properly --- scripts/vr-edit/modules/toolMenu.js | 10 +++++++--- scripts/vr-edit/vr-edit.js | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c0b418cd1e..c9195155af 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -336,7 +336,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, label: "FINISH", command: { - method: "closeOptions" + method: "clearTool" } } ], @@ -358,7 +358,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, label: "FINISH", command: { - method: "closeOptions" + method: "clearTool" } } ], @@ -813,7 +813,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, label: "FINISH", command: { - method: "closeOptions" + method: "clearTool" } } ] @@ -1619,6 +1619,10 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { closeOptions(); break; + case "clearTool": + uiCommandCallback("clearTool"); + break; + default: App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e278ff12a9..3bac2fcd90 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -846,6 +846,7 @@ toolSelected = TOOL_NONE; grouping.clear(); ui.clearTool(); + ui.updateUIEntities(); } } @@ -1378,6 +1379,10 @@ ui.setToolIcon(ui.DELETE_TOOL); ui.updateUIEntities(); break; + case "clearTool": + ui.clearTool(); + ui.updateUIEntities(); + break; case "groupButton": grouping.group(); From 79056c385fa1d8c5569d7e561751aa5ea58217c2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 14:57:17 +1200 Subject: [PATCH 212/504] Fix options buttons not pressing --- scripts/vr-edit/modules/toolMenu.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index c9195155af..6062b0b277 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -939,7 +939,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isPicklistOpen, pressedItem = null, pressedSource, - isButtonPressed, isPicklistPressed, isPicklistItemPressed, isTriggerClicked, @@ -1776,11 +1775,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Press/unpress button. - if (controlHand.triggerClicked() !== isTriggerClicked) { - isTriggerClicked = !isTriggerClicked; - } + isTriggerClicked = controlHand.triggerClicked(); if ((pressedItem && intersectedItem !== pressedItem.index) || intersectionOverlays !== pressedSource - || isTriggerClicked !== isButtonPressed) { + || isTriggerClicked !== (pressedItem !== null)) { if (pressedItem) { // Unpress previous button. Overlays.editOverlay(intersectionOverlays[pressedItem.index], { @@ -1791,7 +1788,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (isHighlightingButton && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) && isTriggerClicked && !wasTriggerClicked) { // Press new button. - isButtonPressed = true; localPosition = intersectionItems[intersectedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) @@ -1998,7 +1994,6 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isPicklistOpen = false; pressedItem = null; pressedSource = null; - isButtonPressed = false; isPicklistPressed = false; isPicklistItemPressed = false; isTriggerClicked = false; From bb7e4fa3025628066456df70073725147ce5a9c3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 16:02:24 +1200 Subject: [PATCH 213/504] Revise coordinate system --- scripts/vr-edit/modules/createPalette.js | 27 ++-- scripts/vr-edit/modules/toolMenu.js | 164 ++++++++++++----------- 2 files changed, 101 insertions(+), 90 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index d1c52d41f9..34dc1162b7 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -24,15 +24,18 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { controlJointName, + /* Coordinate system: UI lies in x-y plane with the front surface being in the +ve z direction. */ CANVAS_SIZE = { x: 0.21, y: 0.13 }, - PALETTE_ROOT_POSITION = { x: -CANVAS_SIZE.x / 2, y: 0.15, z: 0.11 }, - PALETTE_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + HAND_JOINT_OFFSET = 0.15, // Distance from hand (wrist) joint to center of panel. + PANELS_SEPARATION = 0.01, // Gap between Tools menu and Create panel. + PALETTE_ORIGIN_POSITION = { x: 0, y: HAND_JOINT_OFFSET - CANVAS_SIZE.y / 2, z: PANELS_SEPARATION + CANVAS_SIZE.x / 2 }, + PALETTE_ORIGIN_ROTATION = Quat.ZERO, lateralOffset, PALETTE_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, - localPosition: PALETTE_ROOT_POSITION, - localRotation: PALETTE_ROOT_ROTATION, + localPosition: PALETTE_ORIGIN_POSITION, + localRotation: PALETTE_ORIGIN_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, parentID: Uuid.SELF, @@ -42,7 +45,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_PANEL_PROPERTIES = { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, - localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0 }, + localPosition: { x: 0, y: 0, z: 0 }, localRotation: Quat.ZERO, color: { red: 192, green: 192, blue: 192 }, alpha: 0.3, @@ -69,7 +72,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, + localPosition: { x: -0.04, y: 0.04, z: 0.0 }, localRotation: Quat.ZERO, color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -90,7 +93,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { shape: "Cylinder", dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.06, y: 0.02, z: 0.0 }, + localPosition: { x: 0.0, y: 0.04, z: 0.0 }, localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -112,8 +115,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { shape: "Cone", dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.10, y: 0.02, z: 0.0 }, - localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + localPosition: { x: 0.04, y: 0.04, z: 0.0 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }), color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, solid: true, @@ -133,7 +136,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "sphere", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.14, y: 0.02, z: 0.0 }, + localPosition: { x: -0.04, y: 0.0, z: 0.0 }, localRotation: Quat.ZERO, color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -183,7 +186,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { var itemIndex, isTriggerClicked, properties, - PRESS_DELTA = { x: 0, y: 0, z: 0.01 }, + PRESS_DELTA = { x: 0, y: 0, z: -0.01 }, CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }); @@ -257,7 +260,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Calculate position to put palette. properties = Object.clone(PALETTE_ORIGIN_PROPERTIES); properties.parentJointIndex = handJointIndex; - properties.localPosition = Vec3.sum(PALETTE_ROOT_POSITION, { x: lateralOffset, y: 0, z: 0 }); + properties.localPosition = Vec3.sum(PALETTE_ORIGIN_POSITION, { x: lateralOffset, y: 0, z: 0 }); paletteOriginOverlay = Overlays.addOverlay("sphere", properties); // Create palette. diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6062b0b277..6095db7bb3 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -34,28 +34,34 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { LEFT_HAND = 0, - CANVAS_SIZE = { x: 0.22, y: 0.13 }, - PANEL_ORIGIN_POSITION = { x: -0.005 - CANVAS_SIZE.x / 2, y: 0.15, z: -CANVAS_SIZE.x / 2 }, - PANEL_ROOT_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 180 }), + /* Coordinate system: UI lies in x-y plane with the front surface being in the +ve z direction. */ + CANVAS_SIZE = { x: 0.21, y: 0.13 }, + HAND_JOINT_OFFSET = 0.15, // Distance from hand (wrist) joint to center of panel. + PANELS_SEPARATION = 0.01, // Gap between Tools menu and Create panel. + PANEL_ORIGIN_POSITION = { x: -PANELS_SEPARATION - CANVAS_SIZE.x / 2, y: HAND_JOINT_OFFSET - CANVAS_SIZE.y / 2, z: 0 }, + PANEL_ORIGIN_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 0 }), panelLateralOffset, MENU_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, localPosition: PANEL_ORIGIN_POSITION, - localRotation: PANEL_ROOT_ROTATION, + localRotation: PANEL_ORIGIN_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, parentID: Uuid.SELF, ignoreRayIntersection: true, - visible: false + //visible: false + visible: true, + displayInFront: true }, MENU_PANEL_PROPERTIES = { dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, - localPosition: { x: CANVAS_SIZE.x / 2, y: CANVAS_SIZE.y / 2, z: 0.005 }, + localPosition: { x: 0, y: 0, z: 0.0 }, localRotation: Quat.ZERO, color: { red: 164, green: 164, blue: 164 }, - alpha: 1.0, + //alpha: 1.0, + alpha: 0.5, solid: true, ignoreRayIntersection: false, visible: true @@ -73,7 +79,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.10, y: 0.12, z: 0.01 }, localRotation: Quat.ZERO, color: { red: 192, green: 192, blue: 192 }, - alpha: 1.0, + //alpha: 1.0, + alpha: 0.5, solid: true, ignoreRayIntersection: false, visible: true @@ -119,8 +126,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { overlay: "text3d", properties: { dimensions: { x: 0.03, y: 0.0075 }, - localPosition: { x: 0.0, y: 0.0, z: -0.005 }, - localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + localPosition: { x: 0, y: 0, z: 0.005 }, + localRotation: Quat.ZERO, topMargin: 0, leftMargin: 0, color: { red: 240, green: 240, blue: 240 }, @@ -137,8 +144,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { overlay: "circle3d", properties: { size: 0.01, - localPosition: { x: 0.0, y: 0.0, z: -0.01 }, - localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }), + localPosition: { x: 0.0, y: 0.0, z: 0.01 }, + localRotation: Quat.ZERO, color: { red: 128, green: 128, blue: 128 }, alpha: 1.0, solid: true, @@ -151,7 +158,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.1, y: 0.1 }, localPosition: { x: 0, y: 0, z: 0 }, - localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }), + localRotation: Quat.ZERO, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, ignoreRayIntersection: true, @@ -293,7 +300,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, BUTTON_UI_ELEMENTS = ["button", "toggleButton", "swatch"], - BUTTON_PRESS_DELTA = { x: 0, y: 0, z: 0.004 }, + BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.004 }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], COLOR_CIRCLE_UI_ELEMENTS = ["colorCircle"], @@ -323,7 +330,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "scaleOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, { @@ -331,7 +338,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: -0.005 }, + localPosition: { x: 0, y: 0, z: 0.005 }, color: { red: 200, green: 200, blue: 200 } }, label: "FINISH", @@ -345,7 +352,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "cloneOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, { @@ -353,7 +360,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: -0.005 }, + localPosition: { x: 0, y: 0, z: 0.005 }, color: { red: 200, green: 200, blue: 200 } }, label: "FINISH", @@ -367,7 +374,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "groupOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, { @@ -375,7 +382,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: -0.025, z: -0.005 }, + localPosition: { x: 0, y: 0.025, z: 0.005 }, color: { red: 200, green: 200, blue: 200 } }, label: " GROUP", @@ -389,7 +396,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0.025, z: -0.005 }, + localPosition: { x: 0, y: -0.025, z: 0.005 }, color: { red: 200, green: 200, blue: 200 } }, label: "UNGROUP", @@ -404,14 +411,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, { id: "colorCircle", type: "colorCircle", properties: { - localPosition: { x: -0.0125, y: -0.025, z: -0.005 } + localPosition: { x: -0.0125, y: 0.025, z: 0.005 } }, imageURL: "../assets/color-circle.png", imageOverlayURL: "../assets/color-circle-black.png", @@ -423,7 +430,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorSlider", type: "imageSlider", properties: { - localPosition: { x: 0.035, y: -0.025, z: -0.005 } + localPosition: { x: 0.035, y: 0.025, z: 0.005 } }, useBaseColor: true, imageURL: "../assets/slider-white.png", @@ -437,7 +444,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.02, z: -0.005 } + localPosition: { x: -0.035, y: -0.02, z: 0.005 } }, setting: { key: "VREdit.colorTool.swatch1Color", @@ -456,7 +463,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.02, z: -0.005 } + localPosition: { x: -0.01, y: -0.02, z: 0.005 } }, setting: { key: "VREdit.colorTool.swatch2Color", @@ -475,7 +482,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: 0.045, z: -0.005 } + localPosition: { x: -0.035, y: -0.045, z: 0.005 } }, setting: { key: "VREdit.colorTool.swatch3Color", @@ -494,7 +501,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "swatch", properties: { dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: 0.045, z: -0.005 } + localPosition: { x: -0.01, y: -0.045, z: 0.005 } }, setting: { key: "VREdit.colorTool.swatch4Color", @@ -512,7 +519,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "currentColor", type: "circle", properties: { - localPosition: { x: 0.025, y: 0.02, z: -0.007 } + localPosition: { x: 0.025, y: -0.02, z: 0.007 } }, setting: { key: "VREdit.colorTool.currentColor", @@ -526,7 +533,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.04, y: 0.02, z: 0.01 }, - localPosition: { x: 0.025, y: 0.045, z: -0.005 }, + localPosition: { x: 0.025, y: -0.045, z: 0.005 }, color: { red: 255, green: 255, blue: 255 } }, label: " PICK", @@ -540,7 +547,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "physicsOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, @@ -550,14 +557,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "PROPERTIES", lineHeight: 0.0045, - localPosition: { x: -0.031, y: -0.0475, z: -0.0075} + localPosition: { x: -0.031, y: 0.0475, z: 0.0075} } }, { id: "gravityToggle", type: "toggleButton", properties: { - localPosition: { x: -0.0325, y: -0.03, z: -0.005 }, + localPosition: { x: -0.0325, y: 0.03, z: 0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } }, label: "GRAVITY", @@ -574,7 +581,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "grabToggle", type: "toggleButton", properties: { - localPosition: { x: -0.0325, y: -0.005, z: -0.005 }, + localPosition: { x: -0.0325, y: 0.005, z: 0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } }, label: " GRAB", @@ -591,7 +598,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "collideToggle", type: "toggleButton", properties: { - localPosition: { x: -0.0325, y: 0.02, z: -0.005 }, + localPosition: { x: -0.0325, y: -0.02, z: 0.005 }, dimensions: { x: 0.03, y: 0.02, z: 0.01 } }, label: "COLLIDE", @@ -611,14 +618,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "PRESETS", lineHeight: 0.0045, - localPosition: { x: 0.002, y: -0.0475, z: -0.0075 } + localPosition: { x: 0.002, y: 0.0475, z: 0.0075 } } }, { id: "presets", type: "picklist", properties: { - localPosition: { x: 0.016, y: -0.03, z: -0.005 }, + localPosition: { x: 0.016, y: 0.03, z: 0.005 }, dimensions: { x: 0.06, y: 0.02, z: 0.01 } }, label: "DEFAULT", @@ -698,7 +705,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "gravitySlider", type: "barSlider", properties: { - localPosition: { x: -0.007, y: 0.016, z: -0.005 }, + localPosition: { x: -0.007, y: -0.016, z: 0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, setting: { @@ -716,14 +723,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "GRAVITY", lineHeight: 0.0045, - localPosition: { x: -0.003, y: 0.052, z: -0.0075 } + localPosition: { x: -0.003, y: -0.052, z: 0.0075 } } }, { id: "bounceSlider", type: "barSlider", properties: { - localPosition: { x: 0.009, y: 0.016, z: -0.005 }, + localPosition: { x: 0.009, y: -0.016, z: 0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, setting: { @@ -741,14 +748,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "BOUNCE", lineHeight: 0.0045, - localPosition: { x: 0.015, y: 0.057, z: -0.0075 } + localPosition: { x: 0.015, y: -0.057, z: 0.0075 } } }, { id: "dampingSlider", type: "barSlider", properties: { - localPosition: { x: 0.024, y: 0.016, z: -0.005 }, + localPosition: { x: 0.024, y: -0.016, z: 0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, setting: { @@ -766,14 +773,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "DAMPING", lineHeight: 0.0045, - localPosition: { x: 0.030, y: 0.052, z: -0.0075 } + localPosition: { x: 0.030, y: -0.052, z: 0.0075 } } }, { id: "densitySlider", type: "barSlider", properties: { - localPosition: { x: 0.039, y: 0.016, z: -0.005 }, + localPosition: { x: 0.039, y: -0.016, z: 0.005 }, dimensions: { x: 0.014, y: 0.06, z: 0.01 } }, setting: { @@ -791,7 +798,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { text: "DENSITY", lineHeight: 0.0045, - localPosition: { x: 0.045, y: 0.057, z: -0.0075 } + localPosition: { x: 0.045, y: -0.057, z: 0.0075 } } } ], @@ -800,7 +807,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteOptionsPanel", type: "panel", properties: { - localPosition: { x: 0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0.0, y: 0.0, z: 0.005 } } }, { @@ -808,7 +815,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "button", properties: { dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: -0.005 }, + localPosition: { x: 0, y: 0, z: 0.005 }, color: { red: 200, green: 200, blue: 200 } }, label: "FINISH", @@ -824,14 +831,14 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "toolsMenuPanel", type: "panel", properties: { - localPosition: { x: -0.0, y: 0.0, z: -0.005 } + localPosition: { x: 0, y: 0, z: 0.005 } } }, { id: "scaleButton", type: "button", properties: { - localPosition: { x: -0.022, y: -0.04, z: -0.005 }, + localPosition: { x: -0.022, y: 0.04, z: 0.005 }, color: { red: 0, green: 240, blue: 240 } }, label: " SCALE", @@ -844,7 +851,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "cloneButton", type: "button", properties: { - localPosition: { x: 0.022, y: -0.04, z: -0.005 }, + localPosition: { x: 0.022, y: 0.04, z: 0.005 }, color: { red: 240, green: 240, blue: 0 } }, label: " CLONE", @@ -857,7 +864,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "groupButton", type: "button", properties: { - localPosition: { x: -0.022, y: 0.0, z: -0.005 }, + localPosition: { x: -0.022, y: 0.0, z: 0.005 }, color: { red: 220, green: 60, blue: 220 } }, label: " GROUP", @@ -870,7 +877,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorButton", type: "button", properties: { - localPosition: { x: 0.022, y: 0.0, z: -0.005 }, + localPosition: { x: 0.022, y: 0.0, z: 0.005 }, color: { red: 220, green: 220, blue: 220 } }, label: " COLOR", @@ -884,7 +891,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "physicsButton", type: "button", properties: { - localPosition: { x: -0.022, y: 0.04, z: -0.005 }, + localPosition: { x: -0.022, y: -0.04, z: 0.005 }, color: { red: 60, green: 60, blue: 240 } }, label: "PHYSICS", @@ -897,7 +904,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteButton", type: "button", properties: { - localPosition: { x: 0.022, y: 0.04, z: -0.005 }, + localPosition: { x: 0.022, y: -0.04, z: 0.005 }, color: { red: 240, green: 60, blue: 60 } }, label: " DELETE", @@ -913,7 +920,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { yDelta: 0.004, zDimension: 0.001, properties: { - localPosition: { x: 0, y: 0, z: -0.003 }, + localPosition: { x: 0, y: 0, z: 0.003 }, localRotation: Quat.ZERO, color: { red: 255, green: 255, blue: 0 }, alpha: 0.8, @@ -1134,7 +1141,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (optionsItems[i].type === "barSlider") { optionsSliderData[i] = {}; auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderValue.properties); - auxiliaryProperties.localPosition = { x: 0, y: (0.5 - value / 2) * properties.dimensions.y, z: 0 }; + auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + value / 2) * properties.dimensions.y, z: 0 }; auxiliaryProperties.dimensions = { x: properties.dimensions.x, y: Math.max(value * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), @@ -1144,7 +1151,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSliderData[i].value = Overlays.addOverlay(UI_ELEMENTS.barSliderValue.overlay, auxiliaryProperties); auxiliaryProperties = Object.clone(UI_ELEMENTS.barSliderRemainder.properties); - auxiliaryProperties.localPosition = { x: 0, y: (-0.5 + (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; + auxiliaryProperties.localPosition = { x: 0, y: (0.5 - (1.0 - value) / 2) * properties.dimensions.y, z: 0 }; auxiliaryProperties.dimensions = { x: properties.dimensions.x, y: Math.max((1.0 - value) * properties.dimensions.y, MIN_BAR_SLIDER_DIMENSION), @@ -1169,7 +1176,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (optionsItems[i].useBaseColor) { childProperties.color = properties.color; } - childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.localPosition = { x: 0, y: 0, z: properties.dimensions.z / 2 + imageOffset }; hsvControl.slider.localPosition = childProperties.localPosition; childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; hsvControl.slider.colorOverlay = Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); @@ -1184,7 +1191,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delete childProperties.dimensions; childProperties.scale = properties.dimensions.y; imageOffset += IMAGE_OFFSET; - childProperties.localPosition = { x: 0, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + childProperties.localPosition = { x: 0, y: 0, z: properties.dimensions.z / 2 + imageOffset }; childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); } @@ -1192,7 +1199,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Value pointers. optionsSliderData[i] = {}; optionsSliderData[i].offset = - { x: -properties.dimensions.x / 2, y: 0, z: -properties.dimensions.z / 2 - imageOffset }; + { x: -properties.dimensions.x / 2, y: 0, z: properties.dimensions.z / 2 + imageOffset }; auxiliaryProperties = Object.clone(UI_ELEMENTS.sliderPointer.properties); auxiliaryProperties.localPosition = optionsSliderData[i].offset; hsvControl.slider.localPosition = auxiliaryProperties.localPosition; @@ -1217,8 +1224,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties.scale = 0.95 * properties.dimensions.x; // TODO: Magic number. imageOffset += IMAGE_OFFSET; childProperties.emissive = true; - childProperties.localPosition = { x: 0, y: -properties.dimensions.y / 2 - imageOffset, z: 0 }; - childProperties.localRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); + childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; + childProperties.localRotation = Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); } @@ -1231,7 +1238,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delete childProperties.dimensions; childProperties.scale = 0.95 * properties.dimensions.x; // TODO: Magic number. imageOffset += IMAGE_OFFSET; - childProperties.localPosition = { x: 0, y: -properties.dimensions.y / 2 - imageOffset, z: 0 }; + childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; childProperties.localRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; childProperties.alpha = 0.0; @@ -1242,7 +1249,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Invisible sphere at target point with cones as decoration. optionsColorData[i] = {}; optionsColorData[i].offset = - { x: 0, y: -properties.dimensions.y / 2 - imageOffset, z: 0 }; + { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; auxiliaryProperties = Object.clone(UI_ELEMENTS.sphere.properties); auxiliaryProperties.localPosition = optionsColorData[i].offset; auxiliaryProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; @@ -1311,7 +1318,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { otherFraction = 1.0 - fraction; Overlays.editOverlay(optionsSliderData[item].value, { - localPosition: { x: 0, y: (0.5 - fraction / 2) * overlayDimensions.y, z: 0 }, + localPosition: { x: 0, y: (-0.5 + fraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, y: Math.max(fraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), @@ -1319,7 +1326,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }); Overlays.editOverlay(optionsSliderData[item].remainder, { - localPosition: { x: 0, y: (-0.5 + otherFraction / 2) * overlayDimensions.y, z: 0 }, + localPosition: { x: 0, y: (0.5 - otherFraction / 2) * overlayDimensions.y, z: 0 }, dimensions: { x: overlayDimensions.x, y: Math.max(otherFraction * overlayDimensions.y, MIN_BAR_SLIDER_DIMENSION), @@ -1392,7 +1399,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { x = r * Math.cos(theta); y = r * Math.sin(theta); Overlays.editOverlay(hsvControl.circle.cursorOverlay, { - localPosition: { x: y, y: hsvControl.circle.localPosition.y, z: -x } + // Coordinates based on rotate cylinder entity. TODO: Use FBX model instead of cylinder entity. + localPosition: { x: -y, y: hsvControl.circle.localPosition.y, z: -x } }); } @@ -1406,7 +1414,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(hsvControl.slider.pointerOverlay, { localPosition: { x: hsvControl.slider.localPosition.x, - y: (0.5 - hsvControl.hsv.v) * hsvControl.slider.length, + y: (hsvControl.hsv.v - 0.5) * hsvControl.slider.length, z: hsvControl.slider.localPosition.z } }); @@ -1545,7 +1553,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Raise picklist. Overlays.editOverlay(parentID, { - localPosition: Vec3.subtract(optionsItems[index].properties.localPosition, ITEM_RAISE_DELTA) + localPosition: Vec3.sum(optionsItems[index].properties.localPosition, ITEM_RAISE_DELTA) }); // Show options. @@ -1554,7 +1562,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { index = optionsOverlaysIDs.indexOf(items[i]); Overlays.editOverlay(optionsOverlays[index], { parentID: parentID, - localPosition: { x: 0, y: (i + 1) * -UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, + localPosition: { x: 0, y: (i + 1) * UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, visible: true }); Overlays.editOverlay(optionsOverlaysLabels[index], { @@ -1713,7 +1721,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (isHighlightingSlider || isHighlightingColorCircle) { localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { - localPosition: Vec3.subtract(localPosition, ITEM_RAISE_DELTA) + localPosition: Vec3.sum(localPosition, ITEM_RAISE_DELTA) }); } // Highlight new item. (The existence of a command or callback infers that the item should be highlighted.) @@ -1856,8 +1864,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { overlayDimensions = UI_ELEMENTS.barSlider.properties.dimensions; } basePoint = Vec3.sum(sliderProperties.position, - Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); - fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), + Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: -overlayDimensions.y / 2, z: 0 })); + fraction = Vec3.dot(Vec3.subtract(intersection.intersection, basePoint), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); setBarSliderValue(intersectedItem, fraction); @@ -1874,13 +1882,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { overlayDimensions = UI_ELEMENTS.imageSlider.properties.dimensions; } basePoint = Vec3.sum(sliderProperties.position, - Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: overlayDimensions.y / 2, z: 0 })); - fraction = Vec3.dot(Vec3.subtract(basePoint, intersection.intersection), + Vec3.multiplyQbyV(sliderProperties.orientation, { x: 0, y: -overlayDimensions.y / 2, z: 0 })); + fraction = Vec3.dot(Vec3.subtract(intersection.intersection, basePoint), Vec3.multiplyQbyV(sliderProperties.orientation, Vec3.UNIT_Y)) / overlayDimensions.y; fraction = adjustSliderFraction(fraction); Overlays.editOverlay(optionsSliderData[intersectedItem].value, { localPosition: Vec3.sum(optionsSliderData[intersectedItem].offset, - { x: 0, y: (0.5 - fraction) * overlayDimensions.y, z: 0 }) + { x: 0, y: (fraction - 0.5) * overlayDimensions.y, z: 0 }) }); if (intersectionItems[intersectedItem].command) { doCommand(intersectionItems[intersectedItem].command.method, fraction); @@ -1902,8 +1910,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); if (intersectionItems[intersectedItem].command) { // Cartesian planar coordinates. - x = -delta.z; - y = delta.x; + x = -delta.z; // Coordinates based on rotate cylinder entity. TODO: Use FBX model instead of cylinder entity. + y = -delta.x; // "" s = Math.sqrt(x * x + y * y) / hsvControl.circle.radius; h = Math.atan2(y, x) / (2 * Math.PI); if (h < 0) { From 5a4ebbd54df9042fdf0810af2582b65564234bce Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 21:59:31 +1200 Subject: [PATCH 214/504] Style Create palette header and panel --- scripts/vr-edit/assets/create/create.svg | 12 +++ scripts/vr-edit/modules/createPalette.js | 97 +++++++++++++++++++----- scripts/vr-edit/modules/uit.js | 37 +++++++++ scripts/vr-edit/vr-edit.js | 1 + 4 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 scripts/vr-edit/assets/create/create.svg create mode 100644 scripts/vr-edit/modules/uit.js diff --git a/scripts/vr-edit/assets/create/create.svg b/scripts/vr-edit/assets/create/create.svg new file mode 100644 index 0000000000..fa2a096ede --- /dev/null +++ b/scripts/vr-edit/assets/create/create.svg @@ -0,0 +1,12 @@ + + + + CREATE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 34dc1162b7..1eb3d4c607 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -16,6 +16,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { "use strict"; var paletteOriginOverlay, + paletteHeaderOverlay, + paletteHeaderBarOverlay, + paletteTitleOverlay, palettePanelOverlay, highlightOverlay, paletteItemOverlays = [], @@ -24,11 +27,11 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { controlJointName, - /* Coordinate system: UI lies in x-y plane with the front surface being in the +ve z direction. */ - CANVAS_SIZE = { x: 0.21, y: 0.13 }, - HAND_JOINT_OFFSET = 0.15, // Distance from hand (wrist) joint to center of panel. - PANELS_SEPARATION = 0.01, // Gap between Tools menu and Create panel. - PALETTE_ORIGIN_POSITION = { x: 0, y: HAND_JOINT_OFFSET - CANVAS_SIZE.y / 2, z: PANELS_SEPARATION + CANVAS_SIZE.x / 2 }, + PALETTE_ORIGIN_POSITION = { + x: 0, + y: UIT.dimensions.handOffset, + z: UIT.dimensions.canvasSeparation + UIT.dimensions.canvas.x / 2 + }, PALETTE_ORIGIN_ROTATION = Quat.ZERO, lateralOffset, @@ -43,12 +46,55 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }, - PALETTE_PANEL_PROPERTIES = { - dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.001 }, - localPosition: { x: 0, y: 0, z: 0 }, + PALETTE_HEADER_PROPERTIES = { + dimensions: UIT.dimensions.header, + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y / 2, + z: UIT.dimensions.header.z / 2 + }, localRotation: Quat.ZERO, - color: { red: 192, green: 192, blue: 192 }, - alpha: 0.3, + color: UIT.colors.baseGray, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + PALETTE_HEADER_BAR_PROPERTIES = { + dimensions: UIT.dimensions.headerBar, + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y - UIT.dimensions.headerBar.y / 2, + z: UIT.dimensions.headerBar.z / 2 + }, + localRotation: Quat.ZERO, + color: UIT.colors.blueHighlight, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + PALETTE_TITLE_PROPERTIES = { + url: "../assets/create/create.svg", + scale: 0.0363, + localPosition: { x: 0, y: 0, z: PALETTE_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOffset }, + localRotation: Quat.ZERO, + color: UIT.colors.white, + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, + + PALETTE_PANEL_PROPERTIES = { + dimensions: UIT.dimensions.panel, + localPosition: { x: 0, y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, z: UIT.dimensions.panel.z / 2 }, + localRotation: Quat.ZERO, + color: UIT.colors.baseGray, + alpha: 1.0, solid: true, ignoreRayIntersection: false, visible: true @@ -72,7 +118,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: -0.04, y: 0.04, z: 0.0 }, + localPosition: { x: -0.04, y: 0.04, z: 0.03 }, localRotation: Quat.ZERO, color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -93,7 +139,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { shape: "Cylinder", dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.0, y: 0.04, z: 0.0 }, + localPosition: { x: 0.0, y: 0.04, z: 0.03 }, localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -115,7 +161,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { shape: "Cone", dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.04, y: 0.04, z: 0.0 }, + localPosition: { x: 0.04, y: 0.04, z: 0.03 }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }), color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -136,7 +182,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "sphere", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: -0.04, y: 0.0, z: 0.0 }, + localPosition: { x: -0.04, y: 0.0, z: 0.03 }, localRotation: Quat.ZERO, color: { red: 240, green: 0, blue: 0 }, alpha: 1.0, @@ -173,13 +219,13 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; - lateralOffset = side === LEFT_HAND ? -0.01 : 0.01; + lateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; } setHand(side); function getEntityIDs() { - return [palettePanelOverlay].concat(paletteItemOverlays); + return [palettePanelOverlay, paletteHeaderOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); } function update(intersectionOverlayID) { @@ -227,7 +273,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); properties.position = Vec3.sum(controlHand.palmPosition(), Vec3.multiplyQbyV(controlHand.orientation(), - Vec3.sum({ x: 0, y: properties.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); + Vec3.sum({ x: 0, y: properties.dimensions.z + 0.01, z: 0 }, CREATE_OFFSET))); properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); Entities.addEntity(properties); @@ -264,9 +310,23 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { paletteOriginOverlay = Overlays.addOverlay("sphere", properties); // Create palette. + properties = Object.clone(PALETTE_HEADER_PROPERTIES); + properties.parentID = paletteOriginOverlay; + paletteHeaderOverlay = Overlays.addOverlay("cube", properties); + + properties = Object.clone(PALETTE_HEADER_BAR_PROPERTIES); + properties.parentID = paletteOriginOverlay; + paletteHeaderBarOverlay = Overlays.addOverlay("cube", properties); + + properties = Object.clone(PALETTE_TITLE_PROPERTIES); + properties.parentID = paletteHeaderOverlay; + properties.url = Script.resolvePath(properties.url); + paletteTitleOverlay = Overlays.addOverlay("image3d", properties); + properties = Object.clone(PALETTE_PANEL_PROPERTIES); properties.parentID = paletteOriginOverlay; palettePanelOverlay = Overlays.addOverlay("cube", properties); + for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { properties = Object.clone(PALETTE_ITEMS[i].overlay.properties); properties.parentID = paletteOriginOverlay; @@ -295,6 +355,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.deleteOverlay(paletteItemOverlays[i]); } Overlays.deleteOverlay(palettePanelOverlay); + Overlays.deleteOverlay(paletteTitleOverlay); + Overlays.deleteOverlay(paletteHeaderBarOverlay); + Overlays.deleteOverlay(paletteHeaderOverlay); Overlays.deleteOverlay(paletteOriginOverlay); isDisplaying = false; diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js new file mode 100644 index 0000000000..4c2362044e --- /dev/null +++ b/scripts/vr-edit/modules/uit.js @@ -0,0 +1,37 @@ +// +// uit.js +// +// Created by David Rowe on 22 Aug 2017. +// Copyright 2017 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 +// + +/* global UIT */ + +UIT = (function () { + // User Interface Toolkit. Global object. + + return { + colors: { + baseGray: { red: 0x40, green: 0x40, blue: 0x40 }, + blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef }, + white: { red: 0xff, green: 0xff, blue: 0xff } + }, + + // Coordinate system: UI lies in x-y plane with the front surface being +z. + dimensions: { + canvas: { x: 0.24, y: 0.24 }, // Overall UI size. + canvasSeparation: 0.01, // Gap between Tools menu and Create panel. + handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. + handLateralOffset: 0.01, // Offset of UI in direction of palm normal. + + header: { x: 0.24, y: 0.044, z: 0.012 }, + headerBar: { x: 0.24, y: 0.004, z: 0.012 }, + panel: { x: 0.24, y: 0.18, z: 0.008 }, + + imageOffset: 0.001 // Raise image above surface. + } + }; +}()); diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 3bac2fcd90..7fc2537703 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -81,6 +81,7 @@ Script.include("./modules/selection.js"); Script.include("./modules/toolIcon.js"); Script.include("./modules/toolMenu.js"); + Script.include("./modules/uit.js"); function log(side, message) { From f542c54e6c256137151ab892c05a3e64eefd6c4d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 22:40:38 +1200 Subject: [PATCH 215/504] Lay out Create palette entity items --- scripts/vr-edit/modules/createPalette.js | 70 ++++++++++++++++-------- scripts/vr-edit/modules/uit.js | 5 +- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 1eb3d4c607..c28d8421e0 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -22,6 +22,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { palettePanelOverlay, highlightOverlay, paletteItemOverlays = [], + paletteItemPositions = [], LEFT_HAND = 0, @@ -112,15 +113,19 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }, + PALETTE_ENTITY_DIMENSIONS = { x: 0.024, y: 0.024, z: 0.024 }, + PALETTE_ENTITY_COLOR = UIT.colors.faintGray, + ENTITY_CREATION_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, + ENTITY_CREATION_COLOR = { red: 192, green: 192, blue: 192 }, + PALETTE_ITEMS = [ { overlay: { type: "cube", properties: { - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: -0.04, y: 0.04, z: 0.03 }, + dimensions: PALETTE_ENTITY_DIMENSIONS, localRotation: Quat.ZERO, - color: { red: 240, green: 0, blue: 0 }, + color: PALETTE_ENTITY_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -129,8 +134,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { }, entity: { type: "Box", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR } }, { @@ -138,10 +143,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "shape", properties: { shape: "Cylinder", - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.0, y: 0.04, z: 0.03 }, + dimensions: PALETTE_ENTITY_DIMENSIONS, localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), - color: { red: 240, green: 0, blue: 0 }, + color: PALETTE_ENTITY_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -151,8 +155,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { entity: { type: "Shape", shape: "Cylinder", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR } }, { @@ -160,10 +164,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "shape", properties: { shape: "Cone", - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: 0.04, y: 0.04, z: 0.03 }, + dimensions: PALETTE_ENTITY_DIMENSIONS, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }), - color: { red: 240, green: 0, blue: 0 }, + color: PALETTE_ENTITY_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -173,18 +176,17 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { entity: { type: "Shape", shape: "Cone", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR } }, { overlay: { type: "sphere", properties: { - dimensions: { x: 0.03, y: 0.03, z: 0.03 }, - localPosition: { x: -0.04, y: 0.0, z: 0.03 }, + dimensions: PALETTE_ENTITY_DIMENSIONS, localRotation: Quat.ZERO, - color: { red: 240, green: 0, blue: 0 }, + color: PALETTE_ENTITY_COLOR, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -193,8 +195,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { }, entity: { type: "Sphere", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 192, green: 192, blue: 192 } + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR } } ], @@ -257,7 +259,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Unpress currently pressed item. if (pressedItem !== NONE && pressedItem !== itemIndex) { Overlays.editOverlay(paletteItemOverlays[pressedItem], { - localPosition: PALETTE_ITEMS[pressedItem].overlay.properties.localPosition + localPosition: paletteItemPositions[pressedItem] }); pressedItem = NONE; } @@ -266,7 +268,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { isTriggerClicked = controlHand.triggerClicked(); if (highlightedItem !== NONE && pressedItem === NONE && isTriggerClicked && !wasTriggerClicked) { Overlays.editOverlay(paletteItemOverlays[itemIndex], { - localPosition: Vec3.sum(PALETTE_ITEMS[itemIndex].overlay.properties.localPosition, PRESS_DELTA) + localPosition: Vec3.sum(paletteItemPositions[itemIndex], PRESS_DELTA) }); pressedItem = itemIndex; @@ -283,6 +285,26 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { wasTriggerClicked = isTriggerClicked; } + function itemPosition(index) { + // Position relative to palette panel. + var ITEMS_PER_ROW = 3, + ROW_ZERO_Y_OFFSET = 0.0580, + ROW_SPACING = 0.0560, + COLUMN_ZERO_OFFSET = -0.08415, + COLUMN_SPACING = 0.0561, + row, + column; + + row = Math.floor(index / ITEMS_PER_ROW); + column = index % ITEMS_PER_ROW; + + return { + x: COLUMN_ZERO_OFFSET + column * COLUMN_SPACING, + y: ROW_ZERO_Y_OFFSET - row * ROW_SPACING, + z: UIT.dimensions.panel.z + PALETTE_ENTITY_DIMENSIONS.z / 2 + }; + } + function display() { // Creates and shows menu entities. var handJointIndex, @@ -329,8 +351,10 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { properties = Object.clone(PALETTE_ITEMS[i].overlay.properties); - properties.parentID = paletteOriginOverlay; + properties.parentID = palettePanelOverlay; + properties.localPosition = itemPosition(i); paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEMS[i].overlay.type, properties); + paletteItemPositions[i] = properties.localPosition; } // Prepare cube highlight overlay. diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index 4c2362044e..2a5301e0e3 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -15,9 +15,10 @@ UIT = (function () { return { colors: { + white: { red: 0xff, green: 0xff, blue: 0xff }, + faintGray: { red: 0xe3, green: 0xe3, blue: 0xe3 }, baseGray: { red: 0x40, green: 0x40, blue: 0x40 }, - blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef }, - white: { red: 0xff, green: 0xff, blue: 0xff } + blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef } }, // Coordinate system: UI lies in x-y plane with the front surface being +z. From 44778e791f9281521db799511510179c65465612 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Aug 2017 22:55:43 +1200 Subject: [PATCH 216/504] Fix entity creation position --- scripts/vr-edit/modules/createPalette.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index c28d8421e0..366fc51c44 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -267,15 +267,17 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Press item and create new entity. isTriggerClicked = controlHand.triggerClicked(); if (highlightedItem !== NONE && pressedItem === NONE && isTriggerClicked && !wasTriggerClicked) { + // Press item. Overlays.editOverlay(paletteItemOverlays[itemIndex], { localPosition: Vec3.sum(paletteItemPositions[itemIndex], PRESS_DELTA) }); pressedItem = itemIndex; + // Create entity. properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); properties.position = Vec3.sum(controlHand.palmPosition(), Vec3.multiplyQbyV(controlHand.orientation(), - Vec3.sum({ x: 0, y: properties.dimensions.z + 0.01, z: 0 }, CREATE_OFFSET))); + Vec3.sum({ x: 0, y: properties.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); Entities.addEntity(properties); From d6a23abb7c304b3bd279fc689fd03939a6c9903a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 09:30:19 +1200 Subject: [PATCH 217/504] Tools menu header and panel --- .../create/{create.svg => create-heading.svg} | 0 .../vr-edit/assets/tools/tools-heading.svg | 12 +++ scripts/vr-edit/modules/createPalette.js | 14 +-- scripts/vr-edit/modules/toolMenu.js | 90 +++++++++++++++---- 4 files changed, 93 insertions(+), 23 deletions(-) rename scripts/vr-edit/assets/create/{create.svg => create-heading.svg} (100%) create mode 100644 scripts/vr-edit/assets/tools/tools-heading.svg diff --git a/scripts/vr-edit/assets/create/create.svg b/scripts/vr-edit/assets/create/create-heading.svg similarity index 100% rename from scripts/vr-edit/assets/create/create.svg rename to scripts/vr-edit/assets/create/create-heading.svg diff --git a/scripts/vr-edit/assets/tools/tools-heading.svg b/scripts/vr-edit/assets/tools/tools-heading.svg new file mode 100644 index 0000000000..e180ae7251 --- /dev/null +++ b/scripts/vr-edit/assets/tools/tools-heading.svg @@ -0,0 +1,12 @@ + + + + TOOLS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 366fc51c44..fe8b04cc16 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -34,7 +34,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { z: UIT.dimensions.canvasSeparation + UIT.dimensions.canvas.x / 2 }, PALETTE_ORIGIN_ROTATION = Quat.ZERO, - lateralOffset, + paletteLateralOffset, PALETTE_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, @@ -78,7 +78,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { }, PALETTE_TITLE_PROPERTIES = { - url: "../assets/create/create.svg", + url: "../assets/create/create-heading.svg", scale: 0.0363, localPosition: { x: 0, y: 0, z: PALETTE_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOffset }, localRotation: Quat.ZERO, @@ -221,7 +221,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; - lateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; + paletteLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; } setHand(side); @@ -330,27 +330,27 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Calculate position to put palette. properties = Object.clone(PALETTE_ORIGIN_PROPERTIES); properties.parentJointIndex = handJointIndex; - properties.localPosition = Vec3.sum(PALETTE_ORIGIN_POSITION, { x: lateralOffset, y: 0, z: 0 }); + properties.localPosition = Vec3.sum(PALETTE_ORIGIN_POSITION, { x: paletteLateralOffset, y: 0, z: 0 }); paletteOriginOverlay = Overlays.addOverlay("sphere", properties); - // Create palette. + // Header. properties = Object.clone(PALETTE_HEADER_PROPERTIES); properties.parentID = paletteOriginOverlay; paletteHeaderOverlay = Overlays.addOverlay("cube", properties); - properties = Object.clone(PALETTE_HEADER_BAR_PROPERTIES); properties.parentID = paletteOriginOverlay; paletteHeaderBarOverlay = Overlays.addOverlay("cube", properties); - properties = Object.clone(PALETTE_TITLE_PROPERTIES); properties.parentID = paletteHeaderOverlay; properties.url = Script.resolvePath(properties.url); paletteTitleOverlay = Overlays.addOverlay("image3d", properties); + // Palette background. properties = Object.clone(PALETTE_PANEL_PROPERTIES); properties.parentID = paletteOriginOverlay; palettePanelOverlay = Overlays.addOverlay("cube", properties); + // Palette items. for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { properties = Object.clone(PALETTE_ITEMS[i].overlay.properties); properties.parentID = palettePanelOverlay; diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolMenu.js index 6095db7bb3..8d30609f43 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolMenu.js @@ -18,6 +18,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { var attachmentJointName, menuOriginOverlay, + menuHeaderOverlay, + menuHeaderBarOverlay, + menuTitleOverlay, menuPanelOverlay, menuOverlays = [], @@ -33,12 +36,11 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightOverlay, LEFT_HAND = 0, - - /* Coordinate system: UI lies in x-y plane with the front surface being in the +ve z direction. */ - CANVAS_SIZE = { x: 0.21, y: 0.13 }, - HAND_JOINT_OFFSET = 0.15, // Distance from hand (wrist) joint to center of panel. - PANELS_SEPARATION = 0.01, // Gap between Tools menu and Create panel. - PANEL_ORIGIN_POSITION = { x: -PANELS_SEPARATION - CANVAS_SIZE.x / 2, y: HAND_JOINT_OFFSET - CANVAS_SIZE.y / 2, z: 0 }, + PANEL_ORIGIN_POSITION = { + x: -UIT.dimensions.canvasSeparation - UIT.dimensions.canvas.x / 2, + y: UIT.dimensions.handOffset, + z: 0 + }, PANEL_ORIGIN_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 0 }), panelLateralOffset, @@ -50,18 +52,59 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { alpha: 1.0, parentID: Uuid.SELF, ignoreRayIntersection: true, - //visible: false - visible: true, + visible: false, displayInFront: true }, - MENU_PANEL_PROPERTIES = { - dimensions: { x: CANVAS_SIZE.x, y: CANVAS_SIZE.y, z: 0.01 }, - localPosition: { x: 0, y: 0, z: 0.0 }, + MENU_HEADER_PROPERTIES = { + dimensions: UIT.dimensions.header, + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y / 2, + z: UIT.dimensions.header.z / 2 + }, localRotation: Quat.ZERO, - color: { red: 164, green: 164, blue: 164 }, - //alpha: 1.0, - alpha: 0.5, + color: UIT.colors.baseGray, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + MENU_HEADER_BAR_PROPERTIES = { + dimensions: UIT.dimensions.headerBar, + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y - UIT.dimensions.headerBar.y / 2, + z: UIT.dimensions.headerBar.z / 2 + }, + localRotation: Quat.ZERO, + color: UIT.colors.blueHighlight, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + MENU_TITLE_PROPERTIES = { + url: "../assets/tools/tools-heading.svg", + scale: 0.0363, + localPosition: { x: 0, y: 0, z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOffset }, + localRotation: Quat.ZERO, + color: UIT.colors.white, + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, + + MENU_PANEL_PROPERTIES = { + dimensions: UIT.dimensions.panel, + localPosition: { x: 0, y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, z: UIT.dimensions.panel.z / 2 }, + localRotation: Quat.ZERO, + color: UIT.colors.baseGray, + alpha: 1.0, solid: true, ignoreRayIntersection: false, visible: true @@ -983,13 +1026,13 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { side = hand; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); attachmentJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; - panelLateralOffset = side === LEFT_HAND ? -0.01 : 0.01; + panelLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; } setHand(side); function getEntityIDs() { - return [menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); + return [menuPanelOverlay, menuHeaderOverlay, menuHeaderBarOverlay].concat(menuOverlays).concat(optionsOverlays); } function openMenu() { @@ -1976,6 +2019,18 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.localPosition = Vec3.sum(properties.localPosition, { x: panelLateralOffset, y: 0, z: 0 }); menuOriginOverlay = Overlays.addOverlay("sphere", properties); + // Header. + properties = Object.clone(MENU_HEADER_PROPERTIES); + properties.parentID = menuOriginOverlay; + menuHeaderOverlay = Overlays.addOverlay("cube", properties); + properties = Object.clone(MENU_HEADER_BAR_PROPERTIES); + properties.parentID = menuOriginOverlay; + menuHeaderBarOverlay = Overlays.addOverlay("cube", properties); + properties = Object.clone(MENU_TITLE_PROPERTIES); + properties.parentID = menuHeaderOverlay; + properties.url = Script.resolvePath(properties.url); + menuTitleOverlay = Overlays.addOverlay("image3d", properties); + // Panel background. properties = Object.clone(MENU_PANEL_PROPERTIES); properties.parentID = menuOriginOverlay; @@ -2045,6 +2100,9 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } menuOverlays = []; + Overlays.deleteOverlay(menuHeaderOverlay); + Overlays.deleteOverlay(menuHeaderBarOverlay); + Overlays.deleteOverlay(menuTitleOverlay); Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); From 7051ff8f1f5a713e9dc1f9e2efa4ce5a093ce114 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 09:41:46 +1200 Subject: [PATCH 218/504] Rename "tool menu" to "tools menu" --- .../modules/{toolMenu.js => toolsMenu.js} | 18 ++++++------ scripts/vr-edit/vr-edit.js | 28 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) rename scripts/vr-edit/modules/{toolMenu.js => toolsMenu.js} (99%) diff --git a/scripts/vr-edit/modules/toolMenu.js b/scripts/vr-edit/modules/toolsMenu.js similarity index 99% rename from scripts/vr-edit/modules/toolMenu.js rename to scripts/vr-edit/modules/toolsMenu.js index 8d30609f43..5d47cda292 100644 --- a/scripts/vr-edit/modules/toolMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1,5 +1,5 @@ // -// toolMenu.js +// toolsMenu.js // // Created by David Rowe on 22 Jul 2017. // Copyright 2017 High Fidelity, Inc. @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global App, ToolMenu */ +/* global App, ToolsMenu */ -ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { +ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. "use strict"; @@ -1015,8 +1015,8 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand; - if (!this instanceof ToolMenu) { - return new ToolMenu(); + if (!this instanceof ToolsMenu) { + return new ToolsMenu(); } controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); @@ -1674,7 +1674,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; default: - App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); + App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); } }; @@ -1692,7 +1692,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; default: - App.log(side, "ERROR: ToolMenu: Unexpected command! " + command); + App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); } } @@ -2009,7 +2009,7 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (handJointIndex === NONE) { // Don't display if joint isn't available (yet) to attach to. // User can clear this condition by toggling the app off and back on once avatar finishes loading. - App.log(side, "ERROR: ToolMenu: Hand joint index isn't available!"); + App.log(side, "ERROR: ToolsMenu: Hand joint index isn't available!"); return; } @@ -2125,4 +2125,4 @@ ToolMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }; }; -ToolMenu.prototype = {}; +ToolsMenu.prototype = {}; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 7fc2537703..4c4af48635 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -58,7 +58,7 @@ Laser, Selection, ToolIcon, - ToolMenu, + ToolsMenu, // Miscellaneous UPDATE_LOOP_TIMEOUT = 16, @@ -80,7 +80,7 @@ Script.include("./modules/laser.js"); Script.include("./modules/selection.js"); Script.include("./modules/toolIcon.js"); - Script.include("./modules/toolMenu.js"); + Script.include("./modules/toolsMenu.js"); Script.include("./modules/uit.js"); @@ -196,7 +196,7 @@ // Tool menu and Create palette. var // Primary objects. - toolMenu, + toolsMenu, toolIcon, createPalette, @@ -210,7 +210,7 @@ } toolIcon = new ToolIcon(otherHand(side)); - toolMenu = new ToolMenu(side, leftInputs, rightInputs, uiCommandCallback); + toolsMenu = new ToolsMenu(side, leftInputs, rightInputs, uiCommandCallback); createPalette = new CreatePalette(side, leftInputs, rightInputs, uiCommandCallback); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; @@ -218,7 +218,7 @@ function setHand(side) { toolIcon.setHand(otherHand(side)); - toolMenu.setHand(side); + toolsMenu.setHand(side); createPalette.setHand(side); getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; } @@ -233,17 +233,17 @@ function clearTool() { toolIcon.clear(); - toolMenu.clearTool(); + toolsMenu.clearTool(); } function setUIEntities() { - var uiEntityIDs = [].concat(toolMenu.entityIDs(), createPalette.entityIDs()); + var uiEntityIDs = [].concat(toolsMenu.entityIDs(), createPalette.entityIDs()); leftInputs.setUIEntities(side === RIGHT_HAND ? uiEntityIDs : []); rightInputs.setUIEntities(side === LEFT_HAND ? uiEntityIDs : []); } function display() { - toolMenu.display(); + toolsMenu.display(); createPalette.display(); setUIEntities(); isDisplaying = true; @@ -254,21 +254,21 @@ if (isDisplaying) { intersection = getIntersection(); - toolMenu.update(intersection, grouping.groupsCount(), grouping.entitiesCount()); + toolsMenu.update(intersection, grouping.groupsCount(), grouping.entitiesCount()); createPalette.update(intersection.overlayID); toolIcon.update(); } } function doPickColor(color) { - toolMenu.doCommand("setColorFromPick", color); + toolsMenu.doCommand("setColorFromPick", color); } function clear() { leftInputs.setUIEntities([]); rightInputs.setUIEntities([]); toolIcon.clear(); - toolMenu.clear(); + toolsMenu.clear(); createPalette.clear(); isDisplaying = false; @@ -279,9 +279,9 @@ createPalette.destroy(); createPalette = null; } - if (toolMenu) { - toolMenu.destroy(); - toolMenu = null; + if (toolsMenu) { + toolsMenu.destroy(); + toolsMenu = null; } if (toolIcon) { toolIcon.destroy(); From 0ef03eb52ae32cf26a97d59d240b65c44078e01f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 10:42:24 +1200 Subject: [PATCH 219/504] Remove extraneous panels --- scripts/vr-edit/modules/toolsMenu.js | 74 +--------------------------- 1 file changed, 2 insertions(+), 72 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 5d47cda292..92aab30b6c 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -116,19 +116,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { UI_HIGHLIGHT_COLOR = { red: 100, green: 240, blue: 100 }, UI_ELEMENTS = { - "panel": { - overlay: "cube", - properties: { - dimensions: { x: 0.10, y: 0.12, z: 0.01 }, - localRotation: Quat.ZERO, - color: { red: 192, green: 192, blue: 192 }, - //alpha: 1.0, - alpha: 0.5, - solid: true, - ignoreRayIntersection: false, - visible: true - } - }, "button": { overlay: "cube", properties: { @@ -369,13 +356,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { OPTONS_PANELS = { scaleOptions: [ - { - id: "scaleOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, { id: "scaleFinishButton", type: "button", @@ -391,13 +371,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } ], cloneOptions: [ - { - id: "cloneOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, { id: "cloneFinishButton", type: "button", @@ -413,13 +386,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } ], groupOptions: [ - { - id: "groupOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, { id: "groupButton", type: "button", @@ -450,13 +416,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } ], colorOptions: [ - { - id: "colorOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, { id: "colorCircle", type: "colorCircle", @@ -586,14 +545,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } ], physicsOptions: [ - { - id: "physicsOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, - { id: "propertiesLabel", type: "label", @@ -846,13 +797,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } ], deleteOptions: [ - { - id: "deleteOptionsPanel", - type: "panel", - properties: { - localPosition: { x: 0.0, y: 0.0, z: 0.005 } - } - }, { id: "deleteFinishButton", type: "button", @@ -870,13 +814,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_ITEMS = [ - { - id: "toolsMenuPanel", - type: "panel", - properties: { - localPosition: { x: 0, y: 0, z: 0.005 } - } - }, { id: "scaleButton", type: "button", @@ -1041,7 +978,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { i, length; - parentID = menuPanelOverlay; // Menu panel parents to background panel. + parentID = menuPanelOverlay; for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); properties = Object.merge(properties, MENU_ITEMS[i].properties); @@ -1053,7 +990,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.parentID = menuOverlays[menuOverlays.length - 1]; Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } - parentID = menuOverlays[0]; // Menu buttons parent to menu panel. } } @@ -1118,14 +1054,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Remove menu items. closeMenu(); - // TODO: Remove once all tools have an options panel. - if (OPTONS_PANELS[toolOptions] === undefined) { - return; - } - // Open specified options panel. optionsItems = OPTONS_PANELS[toolOptions]; - parentID = menuPanelOverlay; // Menu panel parents to background panel. + parentID = menuPanelOverlay; for (i = 0, length = optionsItems.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); if (optionsItems[i].properties) { @@ -1323,7 +1254,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.addOverlay(UI_ELEMENTS.circlePointer.overlay, auxiliaryProperties); } - parentID = optionsOverlays[0]; // Menu buttons parent to menu panel. optionsEnabled.push(true); } From 1aba95ecb8319700f3295263e0f35ea440b101de Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 18:20:51 +1200 Subject: [PATCH 220/504] Style Tools menu buttons --- scripts/vr-edit/assets/tools/clone-icon.svg | 14 + scripts/vr-edit/assets/tools/clone-label.svg | 12 + scripts/vr-edit/assets/tools/color-icon.svg | 15 + scripts/vr-edit/assets/tools/color-label.svg | 12 + scripts/vr-edit/assets/tools/delete-icon.svg | 14 + scripts/vr-edit/assets/tools/delete-label.svg | 12 + scripts/vr-edit/assets/tools/group-icon.svg | 33 ++ scripts/vr-edit/assets/tools/group-label.svg | 12 + scripts/vr-edit/assets/tools/physics-icon.svg | 15 + .../vr-edit/assets/tools/physics-label.svg | 12 + scripts/vr-edit/assets/tools/stretch-icon.svg | 14 + .../vr-edit/assets/tools/stretch-label.svg | 12 + scripts/vr-edit/assets/tools/tool-label.svg | 12 + scripts/vr-edit/modules/createPalette.js | 2 +- scripts/vr-edit/modules/toolsMenu.js | 381 ++++++++++++++---- scripts/vr-edit/modules/uit.js | 13 +- 16 files changed, 503 insertions(+), 82 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/clone-icon.svg create mode 100644 scripts/vr-edit/assets/tools/clone-label.svg create mode 100644 scripts/vr-edit/assets/tools/color-icon.svg create mode 100644 scripts/vr-edit/assets/tools/color-label.svg create mode 100644 scripts/vr-edit/assets/tools/delete-icon.svg create mode 100644 scripts/vr-edit/assets/tools/delete-label.svg create mode 100644 scripts/vr-edit/assets/tools/group-icon.svg create mode 100644 scripts/vr-edit/assets/tools/group-label.svg create mode 100644 scripts/vr-edit/assets/tools/physics-icon.svg create mode 100644 scripts/vr-edit/assets/tools/physics-label.svg create mode 100644 scripts/vr-edit/assets/tools/stretch-icon.svg create mode 100644 scripts/vr-edit/assets/tools/stretch-label.svg create mode 100644 scripts/vr-edit/assets/tools/tool-label.svg diff --git a/scripts/vr-edit/assets/tools/clone-icon.svg b/scripts/vr-edit/assets/tools/clone-icon.svg new file mode 100644 index 0000000000..324c7d57ba --- /dev/null +++ b/scripts/vr-edit/assets/tools/clone-icon.svg @@ -0,0 +1,14 @@ + + + + clone-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/clone-label.svg b/scripts/vr-edit/assets/tools/clone-label.svg new file mode 100644 index 0000000000..1a141714e8 --- /dev/null +++ b/scripts/vr-edit/assets/tools/clone-label.svg @@ -0,0 +1,12 @@ + + + + CLONE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/color-icon.svg b/scripts/vr-edit/assets/tools/color-icon.svg new file mode 100644 index 0000000000..9363b7607f --- /dev/null +++ b/scripts/vr-edit/assets/tools/color-icon.svg @@ -0,0 +1,15 @@ + + + + color-icon + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/color-label.svg b/scripts/vr-edit/assets/tools/color-label.svg new file mode 100644 index 0000000000..008b7b963d --- /dev/null +++ b/scripts/vr-edit/assets/tools/color-label.svg @@ -0,0 +1,12 @@ + + + + COLOR + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/delete-icon.svg b/scripts/vr-edit/assets/tools/delete-icon.svg new file mode 100644 index 0000000000..f77d40f1e6 --- /dev/null +++ b/scripts/vr-edit/assets/tools/delete-icon.svg @@ -0,0 +1,14 @@ + + + + delete-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/delete-label.svg b/scripts/vr-edit/assets/tools/delete-label.svg new file mode 100644 index 0000000000..e63000e209 --- /dev/null +++ b/scripts/vr-edit/assets/tools/delete-label.svg @@ -0,0 +1,12 @@ + + + + DELETE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group-icon.svg b/scripts/vr-edit/assets/tools/group-icon.svg new file mode 100644 index 0000000000..56abd0a30c --- /dev/null +++ b/scripts/vr-edit/assets/tools/group-icon.svg @@ -0,0 +1,33 @@ + + + + group-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group-label.svg b/scripts/vr-edit/assets/tools/group-label.svg new file mode 100644 index 0000000000..001ce5b953 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group-label.svg @@ -0,0 +1,12 @@ + + + + GROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/physics-icon.svg b/scripts/vr-edit/assets/tools/physics-icon.svg new file mode 100644 index 0000000000..ef2635c312 --- /dev/null +++ b/scripts/vr-edit/assets/tools/physics-icon.svg @@ -0,0 +1,15 @@ + + + + physics-icon + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/physics-label.svg b/scripts/vr-edit/assets/tools/physics-label.svg new file mode 100644 index 0000000000..27006f62b8 --- /dev/null +++ b/scripts/vr-edit/assets/tools/physics-label.svg @@ -0,0 +1,12 @@ + + + + PHYSICS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/stretch-icon.svg b/scripts/vr-edit/assets/tools/stretch-icon.svg new file mode 100644 index 0000000000..dc0547b813 --- /dev/null +++ b/scripts/vr-edit/assets/tools/stretch-icon.svg @@ -0,0 +1,14 @@ + + + + stretch-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/stretch-label.svg b/scripts/vr-edit/assets/tools/stretch-label.svg new file mode 100644 index 0000000000..0aecd01a84 --- /dev/null +++ b/scripts/vr-edit/assets/tools/stretch-label.svg @@ -0,0 +1,12 @@ + + + + STRETCH + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/tool-label.svg b/scripts/vr-edit/assets/tools/tool-label.svg new file mode 100644 index 0000000000..bc8b059bdb --- /dev/null +++ b/scripts/vr-edit/assets/tools/tool-label.svg @@ -0,0 +1,12 @@ + + + + TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index fe8b04cc16..91bb7f5db8 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -80,7 +80,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_TITLE_PROPERTIES = { url: "../assets/create/create-heading.svg", scale: 0.0363, - localPosition: { x: 0, y: 0, z: PALETTE_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOffset }, + localPosition: { x: 0, y: 0, z: PALETTE_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, localRotation: Quat.ZERO, color: UIT.colors.white, alpha: 1.0, diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 92aab30b6c..61ba482bf1 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -24,6 +24,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuPanelOverlay, menuOverlays = [], + menuHoverOverlays = [], optionsOverlays = [], optionsOverlaysIDs = [], // Text ids (names) of options overlays. @@ -89,7 +90,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_TITLE_PROPERTIES = { url: "../assets/tools/tools-heading.svg", scale: 0.0363, - localPosition: { x: 0, y: 0, z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOffset }, + localPosition: { x: 0, y: 0, z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, localRotation: Quat.ZERO, color: UIT.colors.white, alpha: 1.0, @@ -127,6 +128,71 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, + "menuButton": { + overlay: "cube", // Invisible cube for hit area. + properties: { + dimensions: UIT.dimensions.itemCollisionZone, + localRotation: Quat.ZERO, + alpha: 0.0, // Invisible. + solid: true, + ignoreRayIntersection: false, + visible: true // So that laser intersects. + }, + hoverButton: { + overlay: "shape", + properties: { + shape: "Cylinder", + dimensions: { + x: UIT.dimensions.menuButton.x, + y: UIT.dimensions.menuButton.z, + z: UIT.dimensions.menuButton.y + }, + localPosition: UIT.dimensions.menuButtonIconOffset, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: -90 }), + color: UIT.colors.greenHighlight, + alpha: 1.0, + emissive: true, // TODO: This has no effect. + solid: true, + ignoreRayIntersection: true, + visible: false + } + }, + icon: { + // Relative to hoverButton. + type: "image", + properties: { + localPosition: { x: 0, y: UIT.dimensions.menuButton.z / 2 + UIT.dimensions.imageOverlayOffset, z: 0 }, + localRotation: Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }), + color: UIT.colors.lightGrayText + } + }, + label: { + // Relative to menuButton. + type: "image", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.menuButtonLabelYOffset, + z: -UIT.dimensions.itemCollisionZone.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white + } + }, + sublabel: { + // Relative to menuButton. + type: "image", + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152, + localPosition: { + x: 0, + y: UIT.dimensions.menuButtonSublabelYOffset, + z: -UIT.dimensions.itemCollisionZone.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.lightGrayText + } + } + }, "toggleButton": { overlay: "cube", properties: { @@ -186,11 +252,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "image": { overlay: "image3d", properties: { - dimensions: { x: 0.1, y: 0.1 }, localPosition: { x: 0, y: 0, z: 0 }, localRotation: Quat.ZERO, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, + emissive: true, ignoreRayIntersection: true, isFacingAvatar: false, visible: true @@ -329,7 +395,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, - BUTTON_UI_ELEMENTS = ["button", "toggleButton", "swatch"], + BUTTON_UI_ELEMENTS = ["button", "menuButton", "toggleButton", "swatch"], BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.004 }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], @@ -813,54 +879,34 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ] }, + MENU_ITEM_XS = [-0.08415, -0.02805, 0.02805, 0.08415], + MENU_ITEM_YS = [0.058, 0.002, -0.054], + MENU_ITEMS = [ - { - id: "scaleButton", - type: "button", - properties: { - localPosition: { x: -0.022, y: 0.04, z: 0.005 }, - color: { red: 0, green: 240, blue: 240 } - }, - label: " SCALE", - toolOptions: "scaleOptions", - callback: { - method: "scaleTool" - } - }, - { - id: "cloneButton", - type: "button", - properties: { - localPosition: { x: 0.022, y: 0.04, z: 0.005 }, - color: { red: 240, green: 240, blue: 0 } - }, - label: " CLONE", - toolOptions: "cloneOptions", - callback: { - method: "cloneTool" - } - }, - { - id: "groupButton", - type: "button", - properties: { - localPosition: { x: -0.022, y: 0.0, z: 0.005 }, - color: { red: 220, green: 60, blue: 220 } - }, - label: " GROUP", - toolOptions: "groupOptions", - callback: { - method: "groupTool" - } - }, { id: "colorButton", - type: "button", + type: "menuButton", properties: { - localPosition: { x: 0.022, y: 0.0, z: 0.005 }, - color: { red: 220, green: 220, blue: 220 } + localPosition: { + x: MENU_ITEM_XS[0], + y: MENU_ITEM_YS[0], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/color-icon.svg", + dimensions: { x: 0.0165, y: 0.0187 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/color-label.svg", + scale: 0.0241 + } }, - label: " COLOR", toolOptions: "colorOptions", callback: { method: "colorTool", @@ -868,13 +914,116 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - id: "physicsButton", - type: "button", + id: "scaleButton", + type: "menuButton", properties: { - localPosition: { x: -0.022, y: -0.04, z: 0.005 }, - color: { red: 60, green: 60, blue: 240 } + localPosition: { + x: MENU_ITEM_XS[1], + y: MENU_ITEM_YS[0], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/stretch-icon.svg", + dimensions: { x: 0.0167, y: 0.0167 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/stretch-label.svg", + scale: 0.0311 + } + }, + toolOptions: "scaleOptions", + callback: { + method: "scaleTool" + } + }, + { + id: "cloneButton", + type: "menuButton", + properties: { + localPosition: { + x: MENU_ITEM_XS[2], + y: MENU_ITEM_YS[0], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/clone-icon.svg", + dimensions: { x: 0.0154, y: 0.0155 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/clone-label.svg", + scale: 0.0231 + } + }, + toolOptions: "cloneOptions", + callback: { + method: "cloneTool" + } + }, + { + id: "groupButton", + type: "menuButton", + properties: { + localPosition: { + x: MENU_ITEM_XS[3], + y: MENU_ITEM_YS[0], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/group-icon.svg", + dimensions: { x: 0.0161, y: 0.0114 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/group-label.svg", + scale: 0.0250 + } + }, + toolOptions: "groupOptions", + callback: { + method: "groupTool" + } + }, + { + id: "physicsButton", + type: "menuButton", + properties: { + localPosition: { + x: MENU_ITEM_XS[0], + y: MENU_ITEM_YS[1], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/physics-icon.svg", + dimensions: { x: 0.0180, y: 0.0198 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/physics-label.svg", + scale: 0.0297 + } }, - label: "PHYSICS", toolOptions: "physicsOptions", callback: { method: "physicsTool" @@ -882,12 +1031,28 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, { id: "deleteButton", - type: "button", + type: "menuButton", properties: { - localPosition: { x: 0.022, y: -0.04, z: 0.005 }, - color: { red: 240, green: 60, blue: 60 } + localPosition: { + x: MENU_ITEM_XS[1], + y: MENU_ITEM_YS[1], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + type: "image", + properties: { + url: "../assets/tools/delete-icon.svg", + dimensions: { x: 0.0161, y: 0.0161 } + } + }, + label: { + type: "image", + properties: { + url: "../assets/tools/delete-label.svg", + scale: 0.0254 + } }, - label: " DELETE", toolOptions: "deleteOptions", callback: { method: "deleteTool" @@ -920,6 +1085,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItems, highlightedSource, isHighlightingButton, + isHighlightingMenuButton, isHighlightingSlider, isHighlightingColorCircle, isHighlightingPicklist, @@ -973,23 +1139,56 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } function openMenu() { - var parentID, - properties, + var properties, + itemID, + buttonID, i, length; - parentID = menuPanelOverlay; for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); properties = Object.merge(properties, MENU_ITEMS[i].properties); - properties.parentID = parentID; - menuOverlays.push(Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties)); + properties.parentID = menuPanelOverlay; + itemID = Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties); + menuOverlays[i] = itemID; + if (MENU_ITEMS[i].label) { properties = Object.clone(UI_ELEMENTS.label.properties); properties.text = MENU_ITEMS[i].label; - properties.parentID = menuOverlays[menuOverlays.length - 1]; + properties.parentID = itemID; Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); } + + if (MENU_ITEMS[i].type === "menuButton") { + // Hover button. + properties = Object.clone(UI_ELEMENTS.menuButton.hoverButton.properties); + properties.parentID = itemID; + buttonID = Overlays.addOverlay(UI_ELEMENTS.menuButton.hoverButton.overlay, properties); + menuHoverOverlays[i] = buttonID; + + // Icon. + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.icon.properties); + properties = Object.merge(properties, MENU_ITEMS[i].icon.properties); + properties.url = Script.resolvePath(properties.url); + properties.parentID = buttonID; + Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].overlay, properties); + + // Label. + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.label.properties); + properties = Object.merge(properties, MENU_ITEMS[i].label.properties); + properties.url = Script.resolvePath(properties.url); + properties.parentID = itemID; + Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].overlay, properties); + + // Sublabel. + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.sublabel.properties); + properties.url = Script.resolvePath(properties.url); + properties.parentID = itemID; + Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].overlay, properties); + } } } @@ -1006,6 +1205,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } menuOverlays = []; + menuHoverOverlays = []; pressedItem = null; } @@ -1146,7 +1346,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delete childProperties.dimensions; childProperties.scale = properties.dimensions.y; imageOffset += IMAGE_OFFSET; - childProperties.emissive = true; if (optionsItems[i].useBaseColor) { childProperties.color = properties.color; } @@ -1164,6 +1363,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties.drawInFront = true; // TODO: Work-around for rendering bug; remove when bug fixed. delete childProperties.dimensions; childProperties.scale = properties.dimensions.y; + childProperties.emissive = false; imageOffset += IMAGE_OFFSET; childProperties.localPosition = { x: 0, y: 0, z: properties.dimensions.z / 2 + imageOffset }; childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; @@ -1197,7 +1397,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delete childProperties.dimensions; childProperties.scale = 0.95 * properties.dimensions.x; // TODO: Magic number. imageOffset += IMAGE_OFFSET; - childProperties.emissive = true; childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; childProperties.localRotation = Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; @@ -1212,6 +1411,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { delete childProperties.dimensions; childProperties.scale = 0.95 * properties.dimensions.x; // TODO: Magic number. imageOffset += IMAGE_OFFSET; + childProperties.emissive = false; childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; childProperties.localRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; @@ -1676,22 +1876,34 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (intersectedItem !== NONE && intersectionItems[intersectedItem] && (intersectionItems[intersectedItem].command !== undefined || intersectionItems[intersectedItem].callback !== undefined)) { - // Lower old slider or color circle. - if (isHighlightingSlider || isHighlightingColorCircle) { - localPosition = highlightedItems[highlightedItem].properties.localPosition; + if (isHighlightingMenuButton) { + // Lower menu button. + Overlays.editOverlay(menuHoverOverlays[highlightedItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false + }); + } else if (isHighlightingSlider || isHighlightingColorCircle) { + // Lower old slider or color circle. Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: localPosition + localPosition: highlightedItems[highlightedItem].properties.localPosition }); } // Update status variables. highlightedItem = intersectedItem; highlightedItems = intersectionItems; isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; + isHighlightingMenuButton = intersectionItems[highlightedItem].type === "menuButton"; isHighlightingSlider = SLIDER_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingColorCircle = COLOR_CIRCLE_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingPicklist = PICKLIST_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; - // Raise new slider or color circle. - if (isHighlightingSlider || isHighlightingColorCircle) { + if (isHighlightingMenuButton) { + // Raise menu button. + Overlays.editOverlay(menuHoverOverlays[highlightedItem], { + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, ITEM_RAISE_DELTA), + visible: true + }); + } else if (isHighlightingSlider || isHighlightingColorCircle) { + // Raise new slider or color circle. localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { localPosition: Vec3.sum(localPosition, ITEM_RAISE_DELTA) @@ -1719,7 +1931,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); - } else { + } else if (!isHighlightingMenuButton) { Overlays.editOverlay(highlightOverlay, { parentID: intersectionOverlays[intersectedItem], dimensions: { @@ -1738,16 +1950,22 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(highlightOverlay, { visible: false }); - // Lower slider or color circle. - if (isHighlightingSlider || isHighlightingColorCircle) { - localPosition = highlightedItems[highlightedItem].properties.localPosition; + if (isHighlightingMenuButton) { + // Lower menu button. + Overlays.editOverlay(menuHoverOverlays[highlightedItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false + }); + // Lower slider or color circle. + } else if (isHighlightingSlider || isHighlightingColorCircle) { Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: localPosition + localPosition: highlightedItems[highlightedItem].properties.localPosition }); } // Update status variables. highlightedItem = NONE; isHighlightingButton = false; + isHighlightingMenuButton = false; isHighlightingSlider = false; isHighlightingColorCircle = false; isHighlightingPicklist = false; @@ -1770,9 +1988,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { && isTriggerClicked && !wasTriggerClicked) { // Press new button. localPosition = intersectionItems[intersectedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[intersectedItem], { - localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) - }); + if (!isHighlightingMenuButton) { + Overlays.editOverlay(intersectionOverlays[intersectedItem], { + localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) + }); + } pressedSource = intersectionOverlays; pressedItem = { index: intersectedItem, @@ -1981,6 +2201,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItem = NONE; highlightedSource = null; isHighlightingButton = false; + isHighlightingMenuButton = false; isHighlightingSlider = false; isHighlightingColorCircle = false; isHighlightingPicklist = false; @@ -2020,15 +2241,15 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.deleteOverlay(highlightOverlay); for (i = 0, length = optionsOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(optionsOverlays[i]); - // Any auxiliary overlays parented to this overlay are automatically deleted. + Overlays.deleteOverlay(optionsOverlays[i]); // Automatically deletes any child overlays. } optionsOverlays = []; for (i = 0, length = menuOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(menuOverlays[i]); + Overlays.deleteOverlay(menuOverlays[i]); // Automatically deletes any child overlays. } menuOverlays = []; + menuHoverOverlays = []; Overlays.deleteOverlay(menuHeaderOverlay); Overlays.deleteOverlay(menuHeaderBarOverlay); diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index 2a5301e0e3..e9ba08ca24 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -17,11 +17,15 @@ UIT = (function () { colors: { white: { red: 0xff, green: 0xff, blue: 0xff }, faintGray: { red: 0xe3, green: 0xe3, blue: 0xe3 }, + lightGrayText: { red: 0xaf, green: 0xaf, blue: 0xaf }, baseGray: { red: 0x40, green: 0x40, blue: 0x40 }, + darkGray: { red: 0x12, green: 0x12, blue: 0x12 }, + greenHighlight: { red: 0x1f, green: 0xc6, blue: 0xa6 }, blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef } }, // Coordinate system: UI lies in x-y plane with the front surface being +z. + // Offsets are relative to parents' centers. dimensions: { canvas: { x: 0.24, y: 0.24 }, // Overall UI size. canvasSeparation: 0.01, // Gap between Tools menu and Create panel. @@ -32,7 +36,14 @@ UIT = (function () { headerBar: { x: 0.24, y: 0.004, z: 0.012 }, panel: { x: 0.24, y: 0.18, z: 0.008 }, - imageOffset: 0.001 // Raise image above surface. + itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0060 }, // Cursor intersection zone for Tools and Create items. + + menuButton: { x: 0.0267, y: 0.0267, z: 0.0040 }, + menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0050 }, // Non-hovered position. + menuButtonLabelYOffset: -0.00915, // Relative to itemCollisionZone. + menuButtonSublabelYOffset: -0.01775, // Relative to itemCollisionZone. + + imageOverlayOffset: 0.001 // Raise image above surface. } }; }()); From be45f600d4594fdf098e8a3ae36df6b735645612 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 21:56:59 +1200 Subject: [PATCH 221/504] Style Create palette items --- scripts/vr-edit/assets/create/cone.fbx | Bin 0 -> 19056 bytes scripts/vr-edit/assets/create/cube.fbx | Bin 0 -> 17696 bytes scripts/vr-edit/assets/create/cylinder.fbx | Bin 0 -> 20240 bytes scripts/vr-edit/assets/create/sphere.fbx | Bin 0 -> 29312 bytes scripts/vr-edit/modules/createPalette.js | 181 ++++++++++----------- scripts/vr-edit/modules/toolsMenu.js | 21 ++- scripts/vr-edit/modules/uit.js | 12 +- 7 files changed, 112 insertions(+), 102 deletions(-) create mode 100644 scripts/vr-edit/assets/create/cone.fbx create mode 100644 scripts/vr-edit/assets/create/cube.fbx create mode 100644 scripts/vr-edit/assets/create/cylinder.fbx create mode 100644 scripts/vr-edit/assets/create/sphere.fbx diff --git a/scripts/vr-edit/assets/create/cone.fbx b/scripts/vr-edit/assets/create/cone.fbx new file mode 100644 index 0000000000000000000000000000000000000000..b883b042c56337c43754ddd3fa7614304cdb7c48 GIT binary patch literal 19056 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 CO_Y=X literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/create/cube.fbx b/scripts/vr-edit/assets/create/cube.fbx new file mode 100644 index 0000000000000000000000000000000000000000..530122b16efab5be9127898ba6f25bcf7d28872e GIT binary patch literal 17696 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 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/create/cylinder.fbx b/scripts/vr-edit/assets/create/cylinder.fbx new file mode 100644 index 0000000000000000000000000000000000000000..250ce66773f3f970ef26c58798ac070956746bad GIT binary patch literal 20240 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 k*{AMK^Mn_F5AMHJE literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/create/sphere.fbx b/scripts/vr-edit/assets/create/sphere.fbx new file mode 100644 index 0000000000000000000000000000000000000000..b7a5a32952823de483a5cf4eaf26263339d95ca1 GIT binary patch literal 29312 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 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 91bb7f5db8..1caa6de854 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -20,9 +20,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { paletteHeaderBarOverlay, paletteTitleOverlay, palettePanelOverlay, - highlightOverlay, paletteItemOverlays = [], paletteItemPositions = [], + paletteItemHoverOverlays = [], LEFT_HAND = 0, @@ -101,35 +101,55 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, - HIGHLIGHT_PROPERTIES = { - dimensions: { x: 0.034, y: 0.034, z: 0.034 }, - localPosition: { x: 0.02, y: 0.02, z: 0.0 }, - localRotation: Quat.ZERO, - color: { red: 240, green: 240, blue: 0 }, - alpha: 0.8, - solid: false, - drawInFront: true, - ignoreRayIntersection: true, - visible: false - }, - - PALETTE_ENTITY_DIMENSIONS = { x: 0.024, y: 0.024, z: 0.024 }, - PALETTE_ENTITY_COLOR = UIT.colors.faintGray, ENTITY_CREATION_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, ENTITY_CREATION_COLOR = { red: 192, green: 192, blue: 192 }, + PALETTE_ITEM = { + overlay: "cube", // Invisible cube for hit area. + properties: { + dimensions: UIT.dimensions.itemCollisionZone, + localRotation: Quat.ZERO, + alpha: 0.0, // Invisible. + solid: true, + ignoreRayIntersection: false, + visible: true // So that laser intersects. + }, + hoverButton: { + // Relative to root overlay. + overlay: "cube", + properties: { + dimensions: UIT.dimensions.paletteItemButtonDimensions, + localPosition: UIT.dimensions.paletteItemButtonOffset, + localRotation: Quat.ZERO, + color: UIT.colors.blueHighlight, + alpha: 1.0, + emissive: true, // TODO: This has no effect. + solid: true, + ignoreRayIntersection: true, + visible: false + } + }, + icon: { + // Relative to hoverButton. + overlay: "model", + properties: { + dimensions: UIT.dimensions.paletteItemIconDimensions, + localPosition: UIT.dimensions.paletteItemIconOffset, + localRotation: Quat.ZERO, + emissive: true, // TODO: This has no effect. + ignoreRayIntersection: true + } + }, + entity: { + dimensions: ENTITY_CREATION_DIMENSIONS + } + }, + PALETTE_ITEMS = [ { - overlay: { - type: "cube", + icon: { properties: { - dimensions: PALETTE_ENTITY_DIMENSIONS, - localRotation: Quat.ZERO, - color: PALETTE_ENTITY_COLOR, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true + url: "../assets/create/cube.fbx" } }, entity: { @@ -139,17 +159,10 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - overlay: { - type: "shape", + icon: { properties: { - shape: "Cylinder", - dimensions: PALETTE_ENTITY_DIMENSIONS, - localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), - color: PALETTE_ENTITY_COLOR, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true + url: "../assets/create/cylinder.fbx", + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } }, entity: { @@ -160,17 +173,10 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - overlay: { - type: "shape", + icon: { properties: { - shape: "Cone", - dimensions: PALETTE_ENTITY_DIMENSIONS, - localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }), - color: PALETTE_ENTITY_COLOR, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true + url: "../assets/create/cone.fbx", + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } }, entity: { @@ -181,16 +187,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - overlay: { - type: "sphere", + icon: { properties: { - dimensions: PALETTE_ENTITY_DIMENSIONS, - localRotation: Quat.ZERO, - color: PALETTE_ENTITY_COLOR, - alpha: 1.0, - solid: true, - ignoreRayIntersection: false, - visible: true + url: "../assets/create/sphere.fbx" } }, entity: { @@ -205,17 +204,16 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { NONE = -1, highlightedItem = NONE, - pressedItem = NONE, wasTriggerClicked = false, // References. controlHand; - if (!this instanceof CreatePalette) { return new CreatePalette(); } + function setHand(hand) { // Assumes UI is not displaying. side = hand; @@ -234,45 +232,32 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { var itemIndex, isTriggerClicked, properties, - PRESS_DELTA = { x: 0, y: 0, z: -0.01 }, CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }); - // Highlight cube. itemIndex = paletteItemOverlays.indexOf(intersectionOverlayID); - if (itemIndex !== NONE) { - if (highlightedItem !== itemIndex) { - Overlays.editOverlay(highlightOverlay, { - parentID: intersectionOverlayID, - localPosition: Vec3.ZERO, - visible: true - }); - highlightedItem = itemIndex; - } - } else { - Overlays.editOverlay(highlightOverlay, { + + // Unhighlight and lower old item. + if (highlightedItem !== NONE && (itemIndex === NONE || itemIndex !== highlightedItem)) { + Overlays.editOverlay(paletteItemHoverOverlays[highlightedItem], { + localPosition: UIT.dimensions.paletteItemButtonOffset, visible: false }); highlightedItem = NONE; } - // Unpress currently pressed item. - if (pressedItem !== NONE && pressedItem !== itemIndex) { - Overlays.editOverlay(paletteItemOverlays[pressedItem], { - localPosition: paletteItemPositions[pressedItem] + // Highlight and raise new item. + if (itemIndex !== NONE && highlightedItem !== itemIndex) { + Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { + localPosition: UIT.dimensions.paletteItemButtonHoveredOffset, + visible: true }); - pressedItem = NONE; + highlightedItem = itemIndex; } // Press item and create new entity. isTriggerClicked = controlHand.triggerClicked(); - if (highlightedItem !== NONE && pressedItem === NONE && isTriggerClicked && !wasTriggerClicked) { - // Press item. - Overlays.editOverlay(paletteItemOverlays[itemIndex], { - localPosition: Vec3.sum(paletteItemPositions[itemIndex], PRESS_DELTA) - }); - pressedItem = itemIndex; - + if (highlightedItem !== NONE && isTriggerClicked && !wasTriggerClicked) { // Create entity. properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); properties.position = Vec3.sum(controlHand.palmPosition(), @@ -281,6 +266,12 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); Entities.addEntity(properties); + // Lower and unhighlight item. + Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { + localPosition: UIT.dimensions.paletteItemButtonOffset, + visible: false + }); + uiCommandCallback("autoGrab"); } @@ -303,7 +294,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { return { x: COLUMN_ZERO_OFFSET + column * COLUMN_SPACING, y: ROW_ZERO_Y_OFFSET - row * ROW_SPACING, - z: UIT.dimensions.panel.z + PALETTE_ENTITY_DIMENSIONS.z / 2 + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.itemCollisionZone.z / 2 }; } @@ -352,17 +343,26 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Palette items. for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { - properties = Object.clone(PALETTE_ITEMS[i].overlay.properties); + // Collision overlay. + properties = Object.clone(PALETTE_ITEM.properties); properties.parentID = palettePanelOverlay; properties.localPosition = itemPosition(i); - paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEMS[i].overlay.type, properties); - paletteItemPositions[i] = properties.localPosition; - } - // Prepare cube highlight overlay. - properties = Object.clone(HIGHLIGHT_PROPERTIES); - properties.parentID = paletteOriginOverlay; - highlightOverlay = Overlays.addOverlay("cube", properties); + paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.overlay, properties); + paletteItemPositions[i] = properties.localPosition; + + // Highlight overlay. + properties = Object.clone(PALETTE_ITEM.hoverButton.properties); + properties.parentID = paletteItemOverlays[i]; + paletteItemHoverOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.hoverButton.overlay, properties); + + // Icon overlay. + properties = Object.clone(PALETTE_ITEM.icon.properties); + properties = Object.merge(properties, PALETTE_ITEMS[i].icon.properties); + properties.parentID = paletteItemHoverOverlays[i]; + properties.url = Script.resolvePath(properties.url); + Overlays.addOverlay(PALETTE_ITEM.icon.overlay, properties); + } isDisplaying = true; } @@ -376,9 +376,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { return; } - Overlays.deleteOverlay(highlightOverlay); for (i = 0, length = paletteItemOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(paletteItemOverlays[i]); + Overlays.deleteOverlay(paletteItemOverlays[i]); // Child overlays are automatically deleted. } Overlays.deleteOverlay(palettePanelOverlay); Overlays.deleteOverlay(paletteTitleOverlay); diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 61ba482bf1..b6f9ae0daf 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -143,9 +143,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { shape: "Cylinder", dimensions: { - x: UIT.dimensions.menuButton.x, - y: UIT.dimensions.menuButton.z, - z: UIT.dimensions.menuButton.y + x: UIT.dimensions.menuButtonDimensions.x, + y: UIT.dimensions.menuButtonDimensions.z, + z: UIT.dimensions.menuButtonDimensions.y }, localPosition: UIT.dimensions.menuButtonIconOffset, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: -90 }), @@ -161,7 +161,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Relative to hoverButton. type: "image", properties: { - localPosition: { x: 0, y: UIT.dimensions.menuButton.z / 2 + UIT.dimensions.imageOverlayOffset, z: 0 }, + localPosition: { + x: 0, + y: UIT.dimensions.menuButtonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset, + z: 0 + }, localRotation: Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }), color: UIT.colors.lightGrayText } @@ -401,6 +405,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], COLOR_CIRCLE_UI_ELEMENTS = ["colorCircle"], PICKLIST_UI_ELEMENTS = ["picklist", "picklistItem"], + MENU_RAISE_DELTA = { x: 0, y: 0, z: 0.006 }, ITEM_RAISE_DELTA = { x: 0, y: 0, z: 0.004 }, MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. @@ -1160,7 +1165,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } if (MENU_ITEMS[i].type === "menuButton") { - // Hover button. + // Collision overlay. properties = Object.clone(UI_ELEMENTS.menuButton.hoverButton.properties); properties.parentID = itemID; buttonID = Overlays.addOverlay(UI_ELEMENTS.menuButton.hoverButton.overlay, properties); @@ -1877,7 +1882,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { (intersectionItems[intersectedItem].command !== undefined || intersectionItems[intersectedItem].callback !== undefined)) { if (isHighlightingMenuButton) { - // Lower menu button. + // Lower old menu button. Overlays.editOverlay(menuHoverOverlays[highlightedItem], { localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, visible: false @@ -1897,9 +1902,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isHighlightingColorCircle = COLOR_CIRCLE_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingPicklist = PICKLIST_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; if (isHighlightingMenuButton) { - // Raise menu button. + // Raise new menu button. Overlays.editOverlay(menuHoverOverlays[highlightedItem], { - localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, ITEM_RAISE_DELTA), + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, MENU_RAISE_DELTA), visible: true }); } else if (isHighlightingSlider || isHighlightingColorCircle) { diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index e9ba08ca24..8bc9cf76e4 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -36,13 +36,19 @@ UIT = (function () { headerBar: { x: 0.24, y: 0.004, z: 0.012 }, panel: { x: 0.24, y: 0.18, z: 0.008 }, - itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0060 }, // Cursor intersection zone for Tools and Create items. + itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0040 }, // Cursor intersection zone for Tools and Create items. - menuButton: { x: 0.0267, y: 0.0267, z: 0.0040 }, - menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0050 }, // Non-hovered position. + menuButtonDimensions: { x: 0.0267, y: 0.0267, z: 0.0040 }, + menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0040 }, // Non-hovered position relative to itemCollisionZone. menuButtonLabelYOffset: -0.00915, // Relative to itemCollisionZone. menuButtonSublabelYOffset: -0.01775, // Relative to itemCollisionZone. + paletteItemButtonDimensions: { x: 0.0481, y: 0.0480, z: 0.0020 }, + paletteItemButtonOffset: { x: 0, y: 0, z: -0.0020 }, // Non-hovered position relative to itemCollisionZone. + paletteItemButtonHoveredOffset: { x: 0, y: 0, z: -0.0010 }, + paletteItemIconDimensions: { x: 0.024, y: 0.024, z: 0.024 }, + paletteItemIconOffset: { x: 0, y: 0, z: 0.015 }, // Non-hovered position relative to palette button. + imageOverlayOffset: 0.001 // Raise image above surface. } }; From 2a2f058898d836e4eb32f63492c48cf7bd70af5e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Aug 2017 21:59:55 +1200 Subject: [PATCH 222/504] Fix Tools menu header underline color --- scripts/vr-edit/modules/toolsMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index b6f9ae0daf..0cc021ac10 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -80,7 +80,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { z: UIT.dimensions.headerBar.z / 2 }, localRotation: Quat.ZERO, - color: UIT.colors.blueHighlight, + color: UIT.colors.greenHighlight, alpha: 1.0, solid: true, ignoreRayIntersection: false, From 5a1b2babde0df3f46c9e9eb9699d09b347b9f93d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Aug 2017 09:21:54 +1200 Subject: [PATCH 223/504] Add further items to Create palette --- scripts/vr-edit/assets/create/icosahedron.fbx | Bin 0 -> 17904 bytes scripts/vr-edit/assets/create/octahedron.fbx | Bin 0 -> 17024 bytes scripts/vr-edit/assets/create/prism.fbx | Bin 0 -> 17392 bytes scripts/vr-edit/assets/create/tetrahedron.fbx | Bin 0 -> 16720 bytes scripts/vr-edit/modules/createPalette.js | 83 +++++++++++++++--- 5 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 scripts/vr-edit/assets/create/icosahedron.fbx create mode 100644 scripts/vr-edit/assets/create/octahedron.fbx create mode 100644 scripts/vr-edit/assets/create/prism.fbx create mode 100644 scripts/vr-edit/assets/create/tetrahedron.fbx diff --git a/scripts/vr-edit/assets/create/icosahedron.fbx b/scripts/vr-edit/assets/create/icosahedron.fbx new file mode 100644 index 0000000000000000000000000000000000000000..52f948816ba00e8a1722ef375621f394d1260460 GIT binary patch literal 17904 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 Date: Thu, 24 Aug 2017 11:10:41 +1200 Subject: [PATCH 224/504] Remove setting tool icon color --- scripts/vr-edit/modules/toolIcon.js | 5 ----- scripts/vr-edit/vr-edit.js | 11 ----------- 2 files changed, 16 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 9b0d81052d..551e141440 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -94,10 +94,6 @@ ToolIcon = function (side) { } } - function setColor(color) { - Overlays.editOverlay(iconOverlay, { color: color }); - } - function clear() { // Deletes current icon. if (iconOverlay) { @@ -121,7 +117,6 @@ ToolIcon = function (side) { setHand: setHand, update: update, display: display, - setColor: setColor, clear: clear, destroy: destroy }; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4c4af48635..bd8ae74a18 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -227,10 +227,6 @@ toolIcon.display(icon); } - function setToolColor(color) { - toolIcon.setColor(color); - } - function clearTool() { toolIcon.clear(); toolsMenu.clearTool(); @@ -292,7 +288,6 @@ return { setHand: setHand, setToolIcon: setToolIcon, - setToolColor: setToolColor, clearTool: clearTool, SCALE_TOOL: toolIcon.SCALE_TOOL, CLONE_TOOL: toolIcon.CLONE_TOOL, @@ -912,11 +907,9 @@ if (color) { colorToolColor = color; ui.doPickColor(colorToolColor); - ui.setToolColor(colorToolColor); } toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); - ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_PHYSICS) { setState(EDITOR_HIGHLIGHTING); selection.applyPhysics(physicsToolPhysics); @@ -995,11 +988,9 @@ if (color) { colorToolColor = color; ui.doPickColor(colorToolColor); - ui.setToolColor(colorToolColor); } toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); - ui.setToolColor(colorToolColor); } else if (toolSelected === TOOL_PHYSICS) { selection.applyPhysics(physicsToolPhysics); } else if (toolSelected === TOOL_DELETE) { @@ -1358,7 +1349,6 @@ grouping.clear(); toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); - ui.setToolColor(parameter); colorToolColor = parameter; ui.updateUIEntities(); break; @@ -1397,7 +1387,6 @@ toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); } - ui.setToolColor(parameter); colorToolColor = parameter; break; From a33dbfe9b312a8c63333e4197106f4e29fd9cb3c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Aug 2017 14:30:05 +1200 Subject: [PATCH 225/504] Change Tools menu header content per current tool --- scripts/vr-edit/assets/tools/back-icon.svg | 14 ++ .../assets/tools/clone-tool-heading.svg | 12 ++ .../assets/tools/color-tool-heading.svg | 12 ++ .../assets/tools/delete-tool-heading.svg | 12 ++ .../assets/tools/group-tool-heading.svg | 12 ++ .../assets/tools/physics-tool-heading.svg | 12 ++ .../assets/tools/stretch-tool-heading.svg | 12 ++ scripts/vr-edit/modules/toolsMenu.js | 193 ++++++++++++++---- 8 files changed, 234 insertions(+), 45 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/back-icon.svg create mode 100644 scripts/vr-edit/assets/tools/clone-tool-heading.svg create mode 100644 scripts/vr-edit/assets/tools/color-tool-heading.svg create mode 100644 scripts/vr-edit/assets/tools/delete-tool-heading.svg create mode 100644 scripts/vr-edit/assets/tools/group-tool-heading.svg create mode 100644 scripts/vr-edit/assets/tools/physics-tool-heading.svg create mode 100644 scripts/vr-edit/assets/tools/stretch-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/back-icon.svg b/scripts/vr-edit/assets/tools/back-icon.svg new file mode 100644 index 0000000000..7de1781804 --- /dev/null +++ b/scripts/vr-edit/assets/tools/back-icon.svg @@ -0,0 +1,14 @@ + + + + back-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/clone-tool-heading.svg b/scripts/vr-edit/assets/tools/clone-tool-heading.svg new file mode 100644 index 0000000000..6ab57cd0e1 --- /dev/null +++ b/scripts/vr-edit/assets/tools/clone-tool-heading.svg @@ -0,0 +1,12 @@ + + + + CLONE TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/color-tool-heading.svg b/scripts/vr-edit/assets/tools/color-tool-heading.svg new file mode 100644 index 0000000000..5b1979e776 --- /dev/null +++ b/scripts/vr-edit/assets/tools/color-tool-heading.svg @@ -0,0 +1,12 @@ + + + + COLOR TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/delete-tool-heading.svg b/scripts/vr-edit/assets/tools/delete-tool-heading.svg new file mode 100644 index 0000000000..e92e3c1d00 --- /dev/null +++ b/scripts/vr-edit/assets/tools/delete-tool-heading.svg @@ -0,0 +1,12 @@ + + + + DELETE TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group-tool-heading.svg b/scripts/vr-edit/assets/tools/group-tool-heading.svg new file mode 100644 index 0000000000..e1942213e2 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group-tool-heading.svg @@ -0,0 +1,12 @@ + + + + GROUP TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/physics-tool-heading.svg b/scripts/vr-edit/assets/tools/physics-tool-heading.svg new file mode 100644 index 0000000000..fb5d696111 --- /dev/null +++ b/scripts/vr-edit/assets/tools/physics-tool-heading.svg @@ -0,0 +1,12 @@ + + + + PHYSICS TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/stretch-tool-heading.svg b/scripts/vr-edit/assets/tools/stretch-tool-heading.svg new file mode 100644 index 0000000000..0d3fde298c --- /dev/null +++ b/scripts/vr-edit/assets/tools/stretch-tool-heading.svg @@ -0,0 +1,12 @@ + + + + STRETCH TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 0cc021ac10..9b66fc65c3 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -20,7 +20,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOriginOverlay, menuHeaderOverlay, menuHeaderBarOverlay, - menuTitleOverlay, + menuHeaderBackOverlay, + menuHeaderTitleOverlay, + menuHeaderIconOverlay, menuPanelOverlay, menuOverlays = [], @@ -87,9 +89,26 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, - MENU_TITLE_PROPERTIES = { + MENU_HEADER_BACK_PROPERTIES = { + url: "../assets/tools/back-icon.svg", + dimensions: { x: 0.0069, y: 0.0107 }, + localPosition: { + x: -MENU_HEADER_PROPERTIES.dimensions.x / 2 + 0.0118 + 0.0069 / 2, + y: 0, + z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, + localRotation: Quat.ZERO, + color: UIT.colors.lightGrayText, + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, + + MENU_HEADER_TITLE_PROPERTIES = { url: "../assets/tools/tools-heading.svg", - scale: 0.0363, + scale: 0.0327, localPosition: { x: 0, y: 0, z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, localRotation: Quat.ZERO, color: UIT.colors.white, @@ -100,6 +119,26 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, + MENU_HEADER_ICON_OFFSET = { + // Default right center position for header tool icons. + x: MENU_HEADER_PROPERTIES.dimensions.x / 2 - 0.0118, + y: 0, + z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, + + MENU_HEADER_ICON_PROPERTIES = { + url: "../assets/tools/color-icon.svg", // Initial value so that the overlay is initialized OK. + dimensions: { x: 0.01, y: 0.01 }, // "" + localPosition: Vec3.ZERO, // "" + localRotation: Quat.ZERO, + color: UIT.colors.lightGrayText, + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: false + }, + MENU_PANEL_PROPERTIES = { dimensions: UIT.dimensions.panel, localPosition: { x: 0, y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, z: UIT.dimensions.panel.z / 2 }, @@ -903,7 +942,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/color-icon.svg", dimensions: { x: 0.0165, y: 0.0187 } - } + }, + headerOffset: { x: -0.00825, y: 0.0020, z: 0 } }, label: { type: "image", @@ -912,6 +952,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0241 } }, + title: { + url: "../assets/tools/color-tool-heading.svg", + scale: 0.0631 + }, toolOptions: "colorOptions", callback: { method: "colorTool", @@ -933,7 +977,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/stretch-icon.svg", dimensions: { x: 0.0167, y: 0.0167 } - } + }, + headerOffset: { x: -0.00835, y: 0, z: 0 } }, label: { type: "image", @@ -942,6 +987,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0311 } }, + title: { + url: "../assets/tools/stretch-tool-heading.svg", + scale: 0.0737 + }, toolOptions: "scaleOptions", callback: { method: "scaleTool" @@ -962,7 +1011,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/clone-icon.svg", dimensions: { x: 0.0154, y: 0.0155 } - } + }, + headerOffset: { x: -0.0077, y: 0, z: 0 } }, label: { type: "image", @@ -971,6 +1021,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0231 } }, + title: { + url: "../assets/tools/clone-tool-heading.svg", + scale: 0.0621 + }, toolOptions: "cloneOptions", callback: { method: "cloneTool" @@ -991,7 +1045,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/group-icon.svg", dimensions: { x: 0.0161, y: 0.0114 } - } + }, + headerOffset: { x: -0.00805, y: 0, z: 0 } }, label: { type: "image", @@ -1000,6 +1055,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0250 } }, + title: { + url: "../assets/tools/group-tool-heading.svg", + scale: 0.0647 + }, toolOptions: "groupOptions", callback: { method: "groupTool" @@ -1020,7 +1079,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/physics-icon.svg", dimensions: { x: 0.0180, y: 0.0198 } - } + }, + headerOffset: { x: -0.009, y: 0, z: 0 } }, label: { type: "image", @@ -1029,6 +1089,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0297 } }, + title: { + url: "../assets/tools/physics-tool-heading.svg", + scale: 0.0712 + }, toolOptions: "physicsOptions", callback: { method: "physicsTool" @@ -1049,7 +1113,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/delete-icon.svg", dimensions: { x: 0.0161, y: 0.0161 } - } + }, + headerOffset: { x: -0.00805, y: 0, z: 0 } }, label: { type: "image", @@ -1058,6 +1123,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0254 } }, + title: { + url: "../assets/tools/delete-tool-heading.svg", + scale: 0.0653 + }, toolOptions: "deleteOptions", callback: { method: "deleteTool" @@ -1150,6 +1219,15 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { i, length; + // Update header. + Overlays.editOverlay(menuHeaderBackOverlay, { visible: false }); + Overlays.editOverlay(menuHeaderTitleOverlay, { + url: Script.resolvePath(MENU_HEADER_TITLE_PROPERTIES.url), + scale: MENU_HEADER_TITLE_PROPERTIES.scale + }); + Overlays.editOverlay(menuHeaderIconOverlay, { visible: false }); + + // Display menu items. for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); properties = Object.merge(properties, MENU_ITEMS[i].properties); @@ -1214,36 +1292,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { pressedItem = null; } - function closeOptions() { - var i, - length; - - // Remove options items. - Overlays.editOverlay(highlightOverlay, { - parentID: menuOriginOverlay - }); - - for (i = 0, length = optionsOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(optionsOverlays[i]); - } - optionsOverlays = []; - - optionsOverlaysIDs = []; - optionsOverlaysLabels = []; - optionsSliderData = []; - optionsColorData = []; - optionsEnabled = []; - optionsItems = null; - - isPicklistOpen = false; - - pressedItem = null; - - // Display menu items. - openMenu(true); - } - - function openOptions(toolOptions) { + function openOptions(menuItem) { var properties, childProperties, auxiliaryProperties, @@ -1259,8 +1308,21 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Remove menu items. closeMenu(); + // Update header. + Overlays.editOverlay(menuHeaderBackOverlay, { visible: true }); + Overlays.editOverlay(menuHeaderTitleOverlay, { + url: Script.resolvePath(menuItem.title.url), + scale: menuItem.title.scale + }); + Overlays.editOverlay(menuHeaderIconOverlay, { + url: Script.resolvePath(menuItem.icon.properties.url), + dimensions: menuItem.icon.properties.dimensions, + localPosition: Vec3.sum(MENU_HEADER_ICON_OFFSET, menuItem.icon.headerOffset), + visible: true + }); + // Open specified options panel. - optionsItems = OPTONS_PANELS[toolOptions]; + optionsItems = OPTONS_PANELS[menuItem.toolOptions]; parentID = menuPanelOverlay; for (i = 0, length = optionsItems.length; i < length; i += 1) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); @@ -1463,12 +1525,41 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Special handling for Group options. - if (toolOptions === "groupOptions") { + if (menuItem.toolOptions === "groupOptions") { optionsEnabled[groupButtonIndex] = false; optionsEnabled[ungroupButtonIndex] = false; } } + function closeOptions() { + var i, + length; + + // Remove options items. + Overlays.editOverlay(highlightOverlay, { + parentID: menuOriginOverlay + }); + + for (i = 0, length = optionsOverlays.length; i < length; i += 1) { + Overlays.deleteOverlay(optionsOverlays[i]); + } + optionsOverlays = []; + + optionsOverlaysIDs = []; + optionsOverlaysLabels = []; + optionsSliderData = []; + optionsColorData = []; + optionsEnabled = []; + optionsItems = null; + + isPicklistOpen = false; + + pressedItem = null; + + // Display menu items. + openMenu(true); + } + function clearTool() { closeOptions(); } @@ -2006,7 +2097,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Button press actions. if (intersectionOverlays === menuOverlays) { - openOptions(intersectionItems[intersectedItem].toolOptions); + openOptions(intersectionItems[intersectedItem]); } if (intersectionItems[intersectedItem].command) { parameter = intersectionItems[intersectedItem].id; @@ -2181,10 +2272,20 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(MENU_HEADER_BAR_PROPERTIES); properties.parentID = menuOriginOverlay; menuHeaderBarOverlay = Overlays.addOverlay("cube", properties); - properties = Object.clone(MENU_TITLE_PROPERTIES); + + // Header content. + properties = Object.clone(MENU_HEADER_BACK_PROPERTIES); properties.parentID = menuHeaderOverlay; properties.url = Script.resolvePath(properties.url); - menuTitleOverlay = Overlays.addOverlay("image3d", properties); + menuHeaderBackOverlay = Overlays.addOverlay("image3d", properties); + properties = Object.clone(MENU_HEADER_TITLE_PROPERTIES); + properties.parentID = menuHeaderOverlay; + properties.url = Script.resolvePath(properties.url); + menuHeaderTitleOverlay = Overlays.addOverlay("image3d", properties); + properties = Object.clone(MENU_HEADER_ICON_PROPERTIES); + properties.parentID = menuHeaderOverlay; + properties.url = Script.resolvePath(properties.url); + menuHeaderIconOverlay = Overlays.addOverlay("image3d", properties); // Panel background. properties = Object.clone(MENU_PANEL_PROPERTIES); @@ -2258,7 +2359,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.deleteOverlay(menuHeaderOverlay); Overlays.deleteOverlay(menuHeaderBarOverlay); - Overlays.deleteOverlay(menuTitleOverlay); + Overlays.deleteOverlay(menuHeaderIconOverlay); + Overlays.deleteOverlay(menuHeaderTitleOverlay); + Overlays.deleteOverlay(menuHeaderBackOverlay); Overlays.deleteOverlay(menuPanelOverlay); Overlays.deleteOverlay(menuOriginOverlay); From 24202b7fa540981bacd6d0671801c204f2664efe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Aug 2017 15:26:26 +1200 Subject: [PATCH 226/504] Simplify deleting overlays --- scripts/vr-edit/modules/createPalette.js | 16 +++------------- scripts/vr-edit/modules/toolsMenu.js | 24 ++---------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 4fb1e7ec6c..003b973198 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -426,22 +426,12 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { function clear() { // Deletes menu entities. - var i, - length; - if (!isDisplaying) { return; } - - for (i = 0, length = paletteItemOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(paletteItemOverlays[i]); // Child overlays are automatically deleted. - } - Overlays.deleteOverlay(palettePanelOverlay); - Overlays.deleteOverlay(paletteTitleOverlay); - Overlays.deleteOverlay(paletteHeaderBarOverlay); - Overlays.deleteOverlay(paletteHeaderOverlay); - Overlays.deleteOverlay(paletteOriginOverlay); - + Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. + paletteItemOverlays = [], + paletteItemHoverOverlays = [], isDisplaying = false; } diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 9b66fc65c3..c84faae972 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -2338,33 +2338,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function clear() { // Deletes menu entities. - var i, - length; - if (!isDisplaying) { return; } - - Overlays.deleteOverlay(highlightOverlay); - for (i = 0, length = optionsOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(optionsOverlays[i]); // Automatically deletes any child overlays. - } - optionsOverlays = []; - - for (i = 0, length = menuOverlays.length; i < length; i += 1) { - Overlays.deleteOverlay(menuOverlays[i]); // Automatically deletes any child overlays. - } + Overlays.deleteOverlay(menuOriginOverlay); // Automatically deletes all other overlays because they're children. menuOverlays = []; menuHoverOverlays = []; - - Overlays.deleteOverlay(menuHeaderOverlay); - Overlays.deleteOverlay(menuHeaderBarOverlay); - Overlays.deleteOverlay(menuHeaderIconOverlay); - Overlays.deleteOverlay(menuHeaderTitleOverlay); - Overlays.deleteOverlay(menuHeaderBackOverlay); - Overlays.deleteOverlay(menuPanelOverlay); - Overlays.deleteOverlay(menuOriginOverlay); - + optionsOverlays = []; isDisplaying = false; } From 9858c05bdbb49122a58cf081bbb8524763bf0265 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Aug 2017 21:22:05 +1200 Subject: [PATCH 227/504] Pressing header in Tools options takes user back to Tools menu --- scripts/vr-edit/assets/tools/back-heading.svg | 12 ++ scripts/vr-edit/modules/createPalette.js | 38 +++-- scripts/vr-edit/modules/toolsMenu.js | 136 ++++++++++++++---- scripts/vr-edit/modules/uit.js | 3 +- 4 files changed, 143 insertions(+), 46 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/back-heading.svg diff --git a/scripts/vr-edit/assets/tools/back-heading.svg b/scripts/vr-edit/assets/tools/back-heading.svg new file mode 100644 index 0000000000..d70f315ea1 --- /dev/null +++ b/scripts/vr-edit/assets/tools/back-heading.svg @@ -0,0 +1,12 @@ + + + + BACK + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 003b973198..1036fdbcf1 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -16,7 +16,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { "use strict"; var paletteOriginOverlay, - paletteHeaderOverlay, + paletteHeaderHeadingOverlay, paletteHeaderBarOverlay, paletteTitleOverlay, palettePanelOverlay, @@ -47,12 +47,12 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }, - PALETTE_HEADER_PROPERTIES = { - dimensions: UIT.dimensions.header, + PALETTE_HEADER_HEADING_PROPERTIES = { + dimensions: UIT.dimensions.headerHeading, localPosition: { x: 0, - y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y / 2, - z: UIT.dimensions.header.z / 2 + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y / 2, + z: UIT.dimensions.headerHeading.z / 2 }, localRotation: Quat.ZERO, color: UIT.colors.baseGray, @@ -66,7 +66,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: UIT.dimensions.headerBar, localPosition: { x: 0, - y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y - UIT.dimensions.headerBar.y / 2, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y - UIT.dimensions.headerBar.y / 2, z: UIT.dimensions.headerBar.z / 2 }, localRotation: Quat.ZERO, @@ -80,7 +80,11 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_TITLE_PROPERTIES = { url: "../assets/create/create-heading.svg", scale: 0.0363, - localPosition: { x: 0, y: 0, z: PALETTE_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, + localPosition: { + x: 0, + y: 0, + z: PALETTE_HEADER_HEADING_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, localRotation: Quat.ZERO, color: UIT.colors.white, alpha: 1.0, @@ -92,7 +96,11 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_PANEL_PROPERTIES = { dimensions: UIT.dimensions.panel, - localPosition: { x: 0, y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, z: UIT.dimensions.panel.z / 2 }, + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, + z: UIT.dimensions.panel.z / 2 + }, localRotation: Quat.ZERO, color: UIT.colors.baseGray, alpha: 1.0, @@ -216,7 +224,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/create/prism.fbx", localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) - } + } }, entity: { type: "Shape", @@ -282,7 +290,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); function getEntityIDs() { - return [palettePanelOverlay, paletteHeaderOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); + return [palettePanelOverlay, paletteHeaderHeadingOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); } function update(intersectionOverlayID) { @@ -382,14 +390,14 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { paletteOriginOverlay = Overlays.addOverlay("sphere", properties); // Header. - properties = Object.clone(PALETTE_HEADER_PROPERTIES); + properties = Object.clone(PALETTE_HEADER_HEADING_PROPERTIES); properties.parentID = paletteOriginOverlay; - paletteHeaderOverlay = Overlays.addOverlay("cube", properties); + paletteHeaderHeadingOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(PALETTE_HEADER_BAR_PROPERTIES); properties.parentID = paletteOriginOverlay; paletteHeaderBarOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(PALETTE_TITLE_PROPERTIES); - properties.parentID = paletteHeaderOverlay; + properties.parentID = paletteHeaderHeadingOverlay; properties.url = Script.resolvePath(properties.url); paletteTitleOverlay = Overlays.addOverlay("image3d", properties); @@ -430,8 +438,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { return; } Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. - paletteItemOverlays = [], - paletteItemHoverOverlays = [], + paletteItemOverlays = []; + paletteItemHoverOverlays = []; isDisplaying = false; } diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index c84faae972..c0fdc8754c 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -19,6 +19,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOriginOverlay, menuHeaderOverlay, + menuHeaderHeadingOverlay, menuHeaderBarOverlay, menuHeaderBackOverlay, menuHeaderTitleOverlay, @@ -59,33 +60,41 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { displayInFront: true }, + MENU_HEADER_HOVER_OFFSET = { x: 0, y: 0, z: 0.0040 }, + MENU_HEADER_PROPERTIES = { - dimensions: UIT.dimensions.header, + dimensions: Vec3.sum(UIT.dimensions.header, MENU_HEADER_HOVER_OFFSET), // Keep the laser on top when hover. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y / 2, - z: UIT.dimensions.header.z / 2 + z: UIT.dimensions.header.z / 2 + MENU_HEADER_HOVER_OFFSET.z / 2 }, localRotation: Quat.ZERO, + alpha: 0.0, // Invisible + solid: true, + ignoreRayIntersection: false, + visible: true // Catch laser intersections. + }, + + MENU_HEADER_HEADING_PROPERTIES = { + dimensions: UIT.dimensions.headerHeading, + localPosition: { x: 0, y: UIT.dimensions.headerBar.y / 2, z: -MENU_HEADER_HOVER_OFFSET.z / 2 }, + localRotation: Quat.ZERO, color: UIT.colors.baseGray, alpha: 1.0, solid: true, - ignoreRayIntersection: false, + ignoreRayIntersection: true, visible: true }, MENU_HEADER_BAR_PROPERTIES = { dimensions: UIT.dimensions.headerBar, - localPosition: { - x: 0, - y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y - UIT.dimensions.headerBar.y / 2, - z: UIT.dimensions.headerBar.z / 2 - }, + localPosition: { x: 0, y: -UIT.dimensions.headerHeading.y / 2 - UIT.dimensions.headerBar.y / 2, z: 0 }, localRotation: Quat.ZERO, color: UIT.colors.greenHighlight, alpha: 1.0, solid: true, - ignoreRayIntersection: false, + ignoreRayIntersection: true, visible: true }, @@ -93,9 +102,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { url: "../assets/tools/back-icon.svg", dimensions: { x: 0.0069, y: 0.0107 }, localPosition: { - x: -MENU_HEADER_PROPERTIES.dimensions.x / 2 + 0.0118 + 0.0069 / 2, + x: -MENU_HEADER_HEADING_PROPERTIES.dimensions.x / 2 + 0.0118 + 0.0069 / 2, y: 0, - z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + z: MENU_HEADER_HEADING_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, localRotation: Quat.ZERO, color: UIT.colors.lightGrayText, @@ -109,7 +118,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_HEADER_TITLE_PROPERTIES = { url: "../assets/tools/tools-heading.svg", scale: 0.0327, - localPosition: { x: 0, y: 0, z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, + localPosition: { + x: 0, + y: 0, + z: MENU_HEADER_HEADING_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, localRotation: Quat.ZERO, color: UIT.colors.white, alpha: 1.0, @@ -119,11 +132,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, + MENU_HEADER_TITLE_BACK_URL = "../assets/tools/back-heading.svg", + MENU_HEADER_TITLE_BACK_SCALE = 0.0256, + MENU_HEADER_ICON_OFFSET = { // Default right center position for header tool icons. - x: MENU_HEADER_PROPERTIES.dimensions.x / 2 - 0.0118, + x: MENU_HEADER_HEADING_PROPERTIES.dimensions.x / 2 - 0.0118, y: 0, - z: MENU_HEADER_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + z: MENU_HEADER_HEADING_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, MENU_HEADER_ICON_PROPERTIES = { @@ -1165,7 +1181,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isHighlightingPicklist, isPicklistOpen, pressedItem = null, - pressedSource, + pressedSource = null, isPicklistPressed, isPicklistItemPressed, isTriggerClicked, @@ -1184,6 +1200,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, isDisplaying = false, + isOptionsOpen = false, + isOptionsHeadingRaised = false, + optionsHeadingURL, + optionsHeadingScale, // References. controlHand, @@ -1209,7 +1229,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); function getEntityIDs() { - return [menuPanelOverlay, menuHeaderOverlay, menuHeaderBarOverlay].concat(menuOverlays).concat(optionsOverlays); + return [menuPanelOverlay, menuHeaderOverlay].concat(menuOverlays).concat(optionsOverlays); } function openMenu() { @@ -1309,10 +1329,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { closeMenu(); // Update header. + optionsHeadingURL = Script.resolvePath(menuItem.title.url); + optionsHeadingScale = menuItem.title.scale; Overlays.editOverlay(menuHeaderBackOverlay, { visible: true }); Overlays.editOverlay(menuHeaderTitleOverlay, { - url: Script.resolvePath(menuItem.title.url), - scale: menuItem.title.scale + url: optionsHeadingURL, + scale: optionsHeadingScale }); Overlays.editOverlay(menuHeaderIconOverlay, { url: Script.resolvePath(menuItem.icon.properties.url), @@ -1529,6 +1551,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsEnabled[groupButtonIndex] = false; optionsEnabled[ungroupButtonIndex] = false; } + + isOptionsOpen = true; } function closeOptions() { @@ -1556,8 +1580,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { pressedItem = null; + isOptionsOpen = false; + // Display menu items. - openMenu(true); + openMenu(); } function clearTool() { @@ -1947,13 +1973,62 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { s, h; - // Intersection details. + isTriggerClicked = controlHand.triggerClicked(); + + // Handle heading. + if (isOptionsOpen && intersection.overlayID === menuHeaderOverlay) { + if (isTriggerClicked && !wasTriggerClicked) { + // Lower and unhighlight heading; go back to Tools menu. + Overlays.editOverlay(menuHeaderHeadingOverlay, { + localPosition: MENU_HEADER_HEADING_PROPERTIES.localPosition, + color: UIT.colors.baseGray, + emissive: false + }); + isOptionsHeadingRaised = false; + doCommand("clearTool"); + } else if (!isOptionsHeadingRaised) { + // Hover heading. + Overlays.editOverlay(menuHeaderHeadingOverlay, { + localPosition: Vec3.sum(MENU_HEADER_HEADING_PROPERTIES.localPosition, MENU_HEADER_HOVER_OFFSET), + color: UIT.colors.greenHighlight, + emissive: true // TODO: This has no effect. + }); + Overlays.editOverlay(menuHeaderTitleOverlay, { + url: Script.resolvePath(MENU_HEADER_TITLE_BACK_URL), + scale: MENU_HEADER_TITLE_BACK_SCALE + }); + Overlays.editOverlay(menuHeaderIconOverlay, { + visible: false + }); + isOptionsHeadingRaised = true; + } + } else { + if (isOptionsHeadingRaised) { + // Unhover heading. + Overlays.editOverlay(menuHeaderHeadingOverlay, { + localPosition: MENU_HEADER_HEADING_PROPERTIES.localPosition, + color: UIT.colors.baseGray, + emissive: false + }); + Overlays.editOverlay(menuHeaderTitleOverlay, { + url: optionsHeadingURL, + scale: optionsHeadingScale + }); + Overlays.editOverlay(menuHeaderIconOverlay, { + visible: true + }); + isOptionsHeadingRaised = false; + } + } + + // Intersection details for menus and options. + intersectionOverlays = null; + intersectionEnabled = null; if (intersection.overlayID) { intersectedItem = menuOverlays.indexOf(intersection.overlayID); if (intersectedItem !== NONE) { intersectionItems = MENU_ITEMS; intersectionOverlays = menuOverlays; - intersectionEnabled = null; } else { intersectedItem = optionsOverlays.indexOf(intersection.overlayID); if (intersectedItem !== NONE) { @@ -1963,9 +2038,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } } - if (!intersectionOverlays) { - return; - } // Highlight clickable item. if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { @@ -2043,6 +2115,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } else if (highlightedItem !== NONE) { // Un-highlight previous button. + print("$$$$$$$ unhighlight clickable item"); Overlays.editOverlay(highlightOverlay, { visible: false }); @@ -2070,7 +2143,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Press/unpress button. - isTriggerClicked = controlHand.triggerClicked(); if ((pressedItem && intersectedItem !== pressedItem.index) || intersectionOverlays !== pressedSource || isTriggerClicked !== (pressedItem !== null)) { if (pressedItem) { @@ -2079,6 +2151,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: pressedItem.localPosition }); pressedItem = null; + pressedSource = null; } if (isHighlightingButton && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) && isTriggerClicked && !wasTriggerClicked) { @@ -2269,21 +2342,24 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(MENU_HEADER_PROPERTIES); properties.parentID = menuOriginOverlay; menuHeaderOverlay = Overlays.addOverlay("cube", properties); + properties = Object.clone(MENU_HEADER_HEADING_PROPERTIES); + properties.parentID = menuHeaderOverlay; + menuHeaderHeadingOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(MENU_HEADER_BAR_PROPERTIES); - properties.parentID = menuOriginOverlay; + properties.parentID = menuHeaderHeadingOverlay; menuHeaderBarOverlay = Overlays.addOverlay("cube", properties); - // Header content. + // Heading content. properties = Object.clone(MENU_HEADER_BACK_PROPERTIES); - properties.parentID = menuHeaderOverlay; + properties.parentID = menuHeaderHeadingOverlay; properties.url = Script.resolvePath(properties.url); menuHeaderBackOverlay = Overlays.addOverlay("image3d", properties); properties = Object.clone(MENU_HEADER_TITLE_PROPERTIES); - properties.parentID = menuHeaderOverlay; + properties.parentID = menuHeaderHeadingOverlay; properties.url = Script.resolvePath(properties.url); menuHeaderTitleOverlay = Overlays.addOverlay("image3d", properties); properties = Object.clone(MENU_HEADER_ICON_PROPERTIES); - properties.parentID = menuHeaderOverlay; + properties.parentID = menuHeaderHeadingOverlay; properties.url = Script.resolvePath(properties.url); menuHeaderIconOverlay = Overlays.addOverlay("image3d", properties); diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index 8bc9cf76e4..70ef74f5c0 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -32,7 +32,8 @@ UIT = (function () { handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. handLateralOffset: 0.01, // Offset of UI in direction of palm normal. - header: { x: 0.24, y: 0.044, z: 0.012 }, + header: { x: 0.24, y: 0.048, z: 0.012 }, + headerHeading: { x: 0.24, y: 0.044, z: 0.012 }, headerBar: { x: 0.24, y: 0.004, z: 0.012 }, panel: { x: 0.24, y: 0.18, z: 0.008 }, From 5fec240ea6fb6b9a608f49ee97c21b0c8e955336 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Aug 2017 09:20:00 +1200 Subject: [PATCH 228/504] Move Tools and Create panels closer together --- scripts/vr-edit/modules/uit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index 70ef74f5c0..57bc001280 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -28,7 +28,7 @@ UIT = (function () { // Offsets are relative to parents' centers. dimensions: { canvas: { x: 0.24, y: 0.24 }, // Overall UI size. - canvasSeparation: 0.01, // Gap between Tools menu and Create panel. + canvasSeparation: 0.004, // Gap between Tools menu and Create panel. handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. handLateralOffset: 0.01, // Offset of UI in direction of palm normal. From 3b299fc34d4278ffb9fdc6c329f9911b94fa48d7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Aug 2017 12:05:24 +1200 Subject: [PATCH 229/504] Fix Tools menu displaying on right hand --- scripts/vr-edit/modules/toolsMenu.js | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index c0fdc8754c..5cfdd5236b 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -17,6 +17,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { var attachmentJointName, + menuOriginLocalPosition, + menuOriginLocalRotation, + menuOriginOverlay, menuHeaderOverlay, menuHeaderHeadingOverlay, @@ -40,18 +43,22 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightOverlay, LEFT_HAND = 0, - PANEL_ORIGIN_POSITION = { + PANEL_ORIGIN_POSITION_LEFT_HAND = { x: -UIT.dimensions.canvasSeparation - UIT.dimensions.canvas.x / 2, y: UIT.dimensions.handOffset, z: 0 }, - PANEL_ORIGIN_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 0 }), + PANEL_ORIGIN_POSITION_RIGHT_HAND = { + x: UIT.dimensions.canvasSeparation + UIT.dimensions.canvas.x / 2, + y: UIT.dimensions.handOffset, + z: 0 + }, + PANEL_ORIGIN_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: -90, z: 0 }), + PANEL_ORIGIN_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 90, z: 0 }), panelLateralOffset, MENU_ORIGIN_PROPERTIES = { dimensions: { x: 0.005, y: 0.005, z: 0.005 }, - localPosition: PANEL_ORIGIN_POSITION, - localRotation: PANEL_ORIGIN_ROTATION, color: { red: 255, blue: 0, green: 0 }, alpha: 1.0, parentID: Uuid.SELF, @@ -1221,9 +1228,19 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function setHand(hand) { // Assumes UI is not displaying. side = hand; - controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); - attachmentJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; - panelLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; + if (side === LEFT_HAND) { + controlHand = rightInputs.hand(); + attachmentJointName = "LeftHand"; + panelLateralOffset = -UIT.dimensions.handLateralOffset; + menuOriginLocalPosition = PANEL_ORIGIN_POSITION_LEFT_HAND; + menuOriginLocalRotation = PANEL_ORIGIN_ROTATION_LEFT_HAND; + } else { + controlHand = leftInputs.hand(); + attachmentJointName = "RightHand"; + panelLateralOffset = UIT.dimensions.handLateralOffset; + menuOriginLocalPosition = PANEL_ORIGIN_POSITION_RIGHT_HAND; + menuOriginLocalRotation = PANEL_ORIGIN_ROTATION_RIGHT_HAND; + } } setHand(side); @@ -2335,7 +2352,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Menu origin. properties = Object.clone(MENU_ORIGIN_PROPERTIES); properties.parentJointIndex = handJointIndex; - properties.localPosition = Vec3.sum(properties.localPosition, { x: panelLateralOffset, y: 0, z: 0 }); + properties.localPosition = Vec3.sum(menuOriginLocalPosition, { x: panelLateralOffset, y: 0, z: 0 }); + properties.localRotation = menuOriginLocalRotation; menuOriginOverlay = Overlays.addOverlay("sphere", properties); // Header. From 850efec97bd45064fd856a9e941caf2aea4eaa71 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Aug 2017 14:07:41 +1200 Subject: [PATCH 230/504] Display tool icon on dominant hand when tool is active --- scripts/vr-edit/assets/tools/tool-icon.fbx | Bin 0 -> 55292 bytes scripts/vr-edit/modules/toolIcon.js | 139 +++++++++++++++------ 2 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/tool-icon.fbx diff --git a/scripts/vr-edit/assets/tools/tool-icon.fbx b/scripts/vr-edit/assets/tools/tool-icon.fbx new file mode 100644 index 0000000000000000000000000000000000000000..f407ad7feb4824077f715ede315c691f1a52eedc GIT binary patch literal 55292 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 UsT&vT`YTc2+l3pQ1Lf!c9{_bN)c^nh literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 551e141440..861d1fa624 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -23,37 +23,63 @@ ToolIcon = function (side) { PHYSICS_TOOL = 5, DELETE_TOOL = 6, - ICON_COLORS = [ - { red: 0, green: 240, blue: 240 }, - { red: 240, green: 240, blue: 0 }, - { red: 220, green: 60, blue: 220 }, - { red: 220, green: 220, blue: 220 }, - { red: 0, green: 0, blue: 0 }, - { red: 60, green: 60, blue: 240 }, - { red: 240, green: 60, blue: 60 } - ], - LEFT_HAND = 0, - ICON_DIMENSIONS = { x: 0.1, y: 0.01, z: 0.1 }, - ICON_POSITION = { x: 0, y: 0.01, z: 0 }, - ICON_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. + MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. + MODEL_POSITION_LEFT_HAND = { x: -0.03, y: 0.035, z: 0 }, // x raises in thumb direction; y moves in fingers direction. + MODEL_POSITION_RIGHT_HAND = { x: 0.03, y: 0.035, z: 0 }, // "" + MODEL_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 0, z: 100 }), + MODEL_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -100 }), - ICON_TYPE = "sphere", - ICON_PROPERTIES = { - dimensions: ICON_DIMENSIONS, - localPosition: ICON_POSITION, - localRotation: ICON_ROTATION, + MODEL_TYPE = "model", + MODEL_PROPERTIES = { + url: "../assets/tools/tool-icon.fbx", + dimensions: Vec3.multiply(MODEL_SCALE, MODEL_DIMENSIONS), solid: true, alpha: 1.0, parentID: Uuid.SELF, - ignoreRayIntersection: false, + ignoreRayIntersection: true, visible: true }, - handJointName, + IMAGE_TYPE = "image3d", + IMAGE_PROPERTIES = { + localRotation: Quat.fromVec3Degrees({ x: -90, y: -90, z: 0 }), + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, - iconOverlay = null; + ICON_PROPERTIES = { + url: "../assets/tools/stretch-icon.svg", + dimensions: { x: 0.0167, y: 0.0167 }, + localPosition: { x: 0.020, y: 0.069, z: 0 }, // Relative to model overlay. + color: UIT.colors.lightGrayText // x is in fingers direction; y is in thumb direction. + }, + LABEL_PROPERTIES = { + url: "../assets/tools/stretch-label.svg", + scale: 0.0311, + localPosition: { x: -0.040, y: 0.067, z: 0 }, + color: UIT.colors.white + }, + SUBLABEL_PROPERTIES = { + url: "../assets/tools/tool-label.svg", + scale: 0.0152, + localPosition: { x: -0.055, y: 0.067, z: 0 }, + color: UIT.colors.lightGrayText + }, + + ICON_SCALE_FACTOR = 3.0, + LABEL_SCALE_FACTOR = 1.8, + + handJointName, + localPosition, + localRotation, + + modelOverlay = null; if (!this instanceof ToolIcon) { return new ToolIcon(); @@ -61,7 +87,15 @@ ToolIcon = function (side) { function setHand(side) { // Assumes UI is not displaying. - handJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + if (side === LEFT_HAND) { + handJointName = "LeftHand"; + localPosition = MODEL_POSITION_LEFT_HAND; + localRotation = MODEL_ROTATION_LEFT_HAND; + } else { + handJointName = "RightHand"; + localPosition = MODEL_POSITION_RIGHT_HAND; + localRotation = MODEL_ROTATION_RIGHT_HAND; + } } setHand(side); @@ -71,10 +105,18 @@ ToolIcon = function (side) { // TODO: Clear icon animation. } + function clear() { + // Deletes current tool model. + if (modelOverlay) { + Overlays.deleteOverlay(modelOverlay); // Child overlays are automatically deleted. + modelOverlay = null; + } + } + function display(icon) { // Displays icon on hand. var handJointIndex, - iconProperties; + properties; handJointIndex = MyAvatar.getJointIndex(handJointName); if (handJointIndex === -1) { @@ -84,22 +126,45 @@ ToolIcon = function (side) { return; } - if (iconOverlay === null) { - iconProperties = Object.clone(ICON_PROPERTIES); - iconProperties.parentJointIndex = handJointIndex; - iconProperties.color = ICON_COLORS[icon]; - iconOverlay = Overlays.addOverlay(ICON_TYPE, iconProperties); - } else { - Overlays.editOverlay(iconOverlay, { color: ICON_COLORS[icon] }); + if (modelOverlay !== null) { + // Should never happen because tool needs to be cleared in order for user to return to Tools menu. + clear(); } - } - function clear() { - // Deletes current icon. - if (iconOverlay) { - Overlays.deleteOverlay(iconOverlay); - iconOverlay = null; - } + // Model. + properties = Object.clone(MODEL_PROPERTIES); + properties.url = Script.resolvePath(properties.url); + properties.parentJointIndex = handJointIndex; + properties.localPosition = localPosition; + properties.localRotation = localRotation; + modelOverlay = Overlays.addOverlay(MODEL_TYPE, properties); + + // Icon. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, ICON_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = Script.resolvePath(properties.url); + properties.dimensions = { + x: ICON_SCALE_FACTOR * properties.dimensions.x, + y: ICON_SCALE_FACTOR * properties.dimensions.y + }; + Overlays.addOverlay(IMAGE_TYPE, properties); + + // Label. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, LABEL_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = Script.resolvePath(properties.url); + properties.scale = LABEL_SCALE_FACTOR * properties.scale; + Overlays.addOverlay(IMAGE_TYPE, properties); + + // Sublabel. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, SUBLABEL_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = Script.resolvePath(properties.url); + properties.scale = LABEL_SCALE_FACTOR * properties.scale; + Overlays.addOverlay(IMAGE_TYPE, properties); } function destroy() { From eda513caf0e5df84391b6f921bacaac294645cd2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Aug 2017 14:58:05 +1200 Subject: [PATCH 231/504] Display appropriate icon and label on tool icon per current tool --- scripts/vr-edit/modules/toolIcon.js | 40 +++++++--------------------- scripts/vr-edit/modules/toolsMenu.js | 23 +++++++++++++++- scripts/vr-edit/vr-edit.js | 17 ++++++------ 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 861d1fa624..d03351b27c 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -15,15 +15,7 @@ ToolIcon = function (side) { "use strict"; - var SCALE_TOOL = 0, - CLONE_TOOL = 1, - GROUP_TOOL = 2, - COLOR_TOOL = 3, - PICK_COLOR_TOOL = 4, - PHYSICS_TOOL = 5, - DELETE_TOOL = 6, - - LEFT_HAND = 0, + var LEFT_HAND = 0, MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. @@ -54,20 +46,14 @@ ToolIcon = function (side) { }, ICON_PROPERTIES = { - url: "../assets/tools/stretch-icon.svg", - dimensions: { x: 0.0167, y: 0.0167 }, localPosition: { x: 0.020, y: 0.069, z: 0 }, // Relative to model overlay. color: UIT.colors.lightGrayText // x is in fingers direction; y is in thumb direction. }, LABEL_PROPERTIES = { - url: "../assets/tools/stretch-label.svg", - scale: 0.0311, localPosition: { x: -0.040, y: 0.067, z: 0 }, color: UIT.colors.white }, SUBLABEL_PROPERTIES = { - url: "../assets/tools/tool-label.svg", - scale: 0.0152, localPosition: { x: -0.055, y: 0.067, z: 0 }, color: UIT.colors.lightGrayText }, @@ -113,7 +99,7 @@ ToolIcon = function (side) { } } - function display(icon) { + function display(iconInfo) { // Displays icon on hand. var handJointIndex, properties; @@ -143,27 +129,28 @@ ToolIcon = function (side) { properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, ICON_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(properties.url); + properties.url = Script.resolvePath(iconInfo.icon.properties.url); properties.dimensions = { - x: ICON_SCALE_FACTOR * properties.dimensions.x, - y: ICON_SCALE_FACTOR * properties.dimensions.y + x: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.x, + y: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.y }; + properties.localPosition.y += ICON_SCALE_FACTOR * iconInfo.icon.headerOffset.y; Overlays.addOverlay(IMAGE_TYPE, properties); // Label. properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, LABEL_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(properties.url); - properties.scale = LABEL_SCALE_FACTOR * properties.scale; + properties.url = Script.resolvePath(iconInfo.label.properties.url); + properties.scale = LABEL_SCALE_FACTOR * iconInfo.label.properties.scale; Overlays.addOverlay(IMAGE_TYPE, properties); // Sublabel. properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, SUBLABEL_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(properties.url); - properties.scale = LABEL_SCALE_FACTOR * properties.scale; + properties.url = Script.resolvePath(iconInfo.sublabel.properties.url); + properties.scale = LABEL_SCALE_FACTOR * iconInfo.sublabel.properties.scale; Overlays.addOverlay(IMAGE_TYPE, properties); } @@ -172,13 +159,6 @@ ToolIcon = function (side) { } return { - SCALE_TOOL: SCALE_TOOL, - CLONE_TOOL: CLONE_TOOL, - GROUP_TOOL: GROUP_TOOL, - COLOR_TOOL: COLOR_TOOL, - PICK_COLOR_TOOL: PICK_COLOR_TOOL, - PHYSICS_TOOL: PHYSICS_TOOL, - DELETE_TOOL: DELETE_TOOL, setHand: setHand, update: update, display: display, diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 5cfdd5236b..6cff0a9e18 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1156,6 +1156,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], + COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. + SCALE_TOOL = 1, + CLONE_TOOL = 2, + GROUP_TOOL = 3, + PHYSICS_TOOL = 4, + DELETE_TOOL = 5, HIGHLIGHT_PROPERTIES = { xDelta: 0.004, @@ -1249,6 +1255,15 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { return [menuPanelOverlay, menuHeaderOverlay].concat(menuOverlays).concat(optionsOverlays); } + function getIconInfo(tool) { + // Provides details of tool icon, label, and sublabel images for the specified tool. + return { + icon: MENU_ITEMS[tool].icon, + label: MENU_ITEMS[tool].label, + sublabel: UI_ELEMENTS.menuButton.sublabel + }; + } + function openMenu() { var properties, itemID, @@ -2132,7 +2147,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } else if (highlightedItem !== NONE) { // Un-highlight previous button. - print("$$$$$$$ unhighlight clickable item"); Overlays.editOverlay(highlightOverlay, { visible: false }); @@ -2447,6 +2461,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } return { + COLOR_TOOL: COLOR_TOOL, + SCALE_TOOL: SCALE_TOOL, + CLONE_TOOL: CLONE_TOOL, + GROUP_TOOL: GROUP_TOOL, + PHYSICS_TOOL: PHYSICS_TOOL, + DELETE_TOOL: DELETE_TOOL, + iconInfo: getIconInfo, setHand: setHand, entityIDs: getEntityIDs, clearTool: clearTool, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index bd8ae74a18..67976a1d91 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -224,7 +224,7 @@ } function setToolIcon(icon) { - toolIcon.display(icon); + toolIcon.display(toolsMenu.iconInfo(icon)); } function clearTool() { @@ -289,13 +289,12 @@ setHand: setHand, setToolIcon: setToolIcon, clearTool: clearTool, - SCALE_TOOL: toolIcon.SCALE_TOOL, - CLONE_TOOL: toolIcon.CLONE_TOOL, - GROUP_TOOL: toolIcon.GROUP_TOOL, - COLOR_TOOL: toolIcon.COLOR_TOOL, - PICK_COLOR_TOOL: toolIcon.PICK_COLOR_TOOL, - PHYSICS_TOOL: toolIcon.PHYSICS_TOOL, - DELETE_TOOL: toolIcon.DELETE_TOOL, + COLOR_TOOL: toolsMenu.COLOR_TOOL, + SCALE_TOOL: toolsMenu.SCALE_TOOL, + CLONE_TOOL: toolsMenu.CLONE_TOOL, + GROUP_TOOL: toolsMenu.GROUP_TOOL, + PHYSICS_TOOL: toolsMenu.PHYSICS_TOOL, + DELETE_TOOL: toolsMenu.DELETE_TOOL, display: display, updateUIEntities: setUIEntities, doPickColor: doPickColor, @@ -1355,7 +1354,7 @@ case "pickColorTool": grouping.clear(); toolSelected = TOOL_PICK_COLOR; - ui.setToolIcon(ui.PICK_COLOR_TOOL); + ui.setToolIcon(ui.COLOR_TOOL); ui.updateUIEntities(); break; case "physicsTool": From 71c3a58e88e1c96df0dd09dc7b8eb32ed8a7e05c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Aug 2017 20:50:12 +1200 Subject: [PATCH 232/504] Style Stretch options --- scripts/vr-edit/assets/horizontal-rule.svg | 66 ++++++++ .../assets/tools/common/actions-label.svg | 12 ++ .../assets/tools/common/finish-label.svg | 12 ++ .../vr-edit/assets/tools/common/info-icon.svg | 12 ++ .../assets/tools/stretch/info-text.svg | 12 ++ scripts/vr-edit/modules/toolsMenu.js | 160 +++++++++++++++--- scripts/vr-edit/modules/uit.js | 8 + 7 files changed, 261 insertions(+), 21 deletions(-) create mode 100644 scripts/vr-edit/assets/horizontal-rule.svg create mode 100644 scripts/vr-edit/assets/tools/common/actions-label.svg create mode 100644 scripts/vr-edit/assets/tools/common/finish-label.svg create mode 100644 scripts/vr-edit/assets/tools/common/info-icon.svg create mode 100644 scripts/vr-edit/assets/tools/stretch/info-text.svg diff --git a/scripts/vr-edit/assets/horizontal-rule.svg b/scripts/vr-edit/assets/horizontal-rule.svg new file mode 100644 index 0000000000..9202a01479 --- /dev/null +++ b/scripts/vr-edit/assets/horizontal-rule.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/scripts/vr-edit/assets/tools/common/actions-label.svg b/scripts/vr-edit/assets/tools/common/actions-label.svg new file mode 100644 index 0000000000..d5428ae3f1 --- /dev/null +++ b/scripts/vr-edit/assets/tools/common/actions-label.svg @@ -0,0 +1,12 @@ + + + + ACTIONS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/common/finish-label.svg b/scripts/vr-edit/assets/tools/common/finish-label.svg new file mode 100644 index 0000000000..58120a337a --- /dev/null +++ b/scripts/vr-edit/assets/tools/common/finish-label.svg @@ -0,0 +1,12 @@ + + + + FINISH + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/common/info-icon.svg b/scripts/vr-edit/assets/tools/common/info-icon.svg new file mode 100644 index 0000000000..ef2495b728 --- /dev/null +++ b/scripts/vr-edit/assets/tools/common/info-icon.svg @@ -0,0 +1,12 @@ + + + + [ + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/stretch/info-text.svg b/scripts/vr-edit/assets/tools/stretch/info-text.svg new file mode 100644 index 0000000000..4bd23f7b7f --- /dev/null +++ b/scripts/vr-edit/assets/tools/stretch/info-text.svg @@ -0,0 +1,12 @@ + + + + Stretch objects by g + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 6cff0a9e18..34d931bde0 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -179,7 +179,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { UI_HIGHLIGHT_COLOR = { red: 100, green: 240, blue: 100 }, UI_ELEMENTS = { - "button": { + "button": { // TODO: Delete. overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, @@ -259,6 +259,27 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } }, + "newButton": { // TODO: Rename to "button". + overlay: "cube", + properties: { + dimensions: UIT.dimensions.buttonDimensions, + localRotation: Quat.ZERO, + color: UIT.colors.baseGrayShadow, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + newLabel: { // TODO: Rename to "label". + // Relative to newButton. + localPosition: { + x: 0, + y: 0, + z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white + } + }, "toggleButton": { overlay: "cube", properties: { @@ -320,7 +341,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: 0, y: 0, z: 0 }, localRotation: Quat.ZERO, - color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, emissive: true, ignoreRayIntersection: true, @@ -328,6 +348,20 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, + "horizontalRule": { + overlay: "image3d", + properties: { + url: "../assets/horizontal-rule.svg", + dimensions: { x: UIT.dimensions.panel.x - 2 * UIT.dimensions.leftMargin, y: 0.001 }, + localRotation: Quat.ZERO, + color: UIT.colors.baseGrayShadow, + alpha: 1.0, + solid: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + } + }, "sphere": { overlay: "sphere", properties: { @@ -461,7 +495,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, - BUTTON_UI_ELEMENTS = ["button", "menuButton", "toggleButton", "swatch"], + BUTTON_UI_ELEMENTS = ["button", "newButton", "menuButton", "toggleButton", "swatch"], BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.004 }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], @@ -488,21 +522,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, OPTONS_PANELS = { - scaleOptions: [ - { - id: "scaleFinishButton", - type: "button", - properties: { - dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: 0.005 }, - color: { red: 200, green: 200, blue: 200 } - }, - label: "FINISH", - command: { - method: "clearTool" - } - } - ], cloneOptions: [ { id: "cloneFinishButton", @@ -677,6 +696,86 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], + scaleOptions: [ + { + id: "stretchActionsLabel", + type: "image", + properties: { + color: UIT.colors.white, + url: "../assets/tools/common/actions-label.svg", + scale: 0.0276, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.topMargin - 0.0047 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "stretchRule1", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0199, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "stretchFinishButton", + type: "newButton", + properties: { + localPosition: { x: 0, y: 0.02, z: 0.005 } + }, + newLabel: { + url: "../assets/tools/common/finish-label.svg", + scale: 0.0318 + }, + command: { + method: "clearTool" + } + }, + { + id: "stretchRule2", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.1197, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "stretchInfoIcon", + type: "image", + properties: { + url: "../assets/tools/common/info-icon.svg", + dimensions: { x: 0.0321, y: 0.0320 }, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white // Icon SVG is already lightGray color. + } + }, + { + id: "stretchInfo", + type: "image", + properties: { + url: "../assets/tools/stretch/info-text.svg", + scale: 0.1340, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, // Center on info icon. + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white + } + } + ], physicsOptions: [ { id: "propertiesLabel", @@ -961,7 +1060,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, icon: { - type: "image", + type: "image", // TODO: Can delete this and similar occurrences? properties: { url: "../assets/tools/color-icon.svg", dimensions: { x: 0.0165, y: 0.0187 } @@ -969,7 +1068,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { headerOffset: { x: -0.00825, y: 0.0020, z: 0 } }, label: { - type: "image", + type: "image", // TODO: Can delete this and similar occurrences? properties: { url: "../assets/tools/color-label.svg", scale: 0.0241 @@ -1188,6 +1287,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItems, highlightedSource, isHighlightingButton, + isHighlightingNewButton, // TODO: Delete when no longer needed. isHighlightingMenuButton, isHighlightingSlider, isHighlightingColorCircle, @@ -1432,6 +1532,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id = Overlays.addOverlay(UI_ELEMENTS.label.overlay, properties); optionsOverlaysLabels[i] = id; } + if (optionsItems[i].newLabel) { + properties = Object.clone(UI_ELEMENTS.image.properties); + properties = Object.merge(properties, UI_ELEMENTS[optionsItems[i].type].newLabel); + properties = Object.merge(properties, optionsItems[i].newLabel); + properties.url = Script.resolvePath(properties.url); + properties.parentID = optionsOverlays[optionsOverlays.length - 1]; + Overlays.addOverlay(UI_ELEMENTS.image.overlay, properties); + } if (optionsItems[i].type === "barSlider") { optionsSliderData[i] = {}; @@ -2092,6 +2200,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItem = intersectedItem; highlightedItems = intersectionItems; isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; + isHighlightingNewButton = intersectionItems[highlightedItem].type === "newButton"; isHighlightingMenuButton = intersectionItems[highlightedItem].type === "menuButton"; isHighlightingSlider = SLIDER_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingColorCircle = COLOR_CIRCLE_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; @@ -2131,6 +2240,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); + } else if (isHighlightingNewButton) { + Overlays.editOverlay(intersectionOverlays[highlightedItem], { + color: UIT.colors.greenHighlight + }); } else if (!isHighlightingMenuButton) { Overlays.editOverlay(highlightOverlay, { parentID: intersectionOverlays[intersectedItem], @@ -2156,8 +2269,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, visible: false }); - // Lower slider or color circle. + } else if (isHighlightingNewButton) { + Overlays.editOverlay(highlightedSource[highlightedItem], { + color: UIT.colors.baseGrayShadow + }); } else if (isHighlightingSlider || isHighlightingColorCircle) { + // Lower slider or color circle. Overlays.editOverlay(highlightedSource[highlightedItem], { localPosition: highlightedItems[highlightedItem].properties.localPosition }); @@ -2165,6 +2282,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Update status variables. highlightedItem = NONE; isHighlightingButton = false; + isHighlightingNewButton = false; isHighlightingMenuButton = false; isHighlightingSlider = false; isHighlightingColorCircle = false; diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index 57bc001280..ae3c1674f9 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -19,6 +19,7 @@ UIT = (function () { faintGray: { red: 0xe3, green: 0xe3, blue: 0xe3 }, lightGrayText: { red: 0xaf, green: 0xaf, blue: 0xaf }, baseGray: { red: 0x40, green: 0x40, blue: 0x40 }, + baseGrayShadow: { red: 0x25, green: 0x25, blue: 0x25 }, darkGray: { red: 0x12, green: 0x12, blue: 0x12 }, greenHighlight: { red: 0x1f, green: 0xc6, blue: 0xa6 }, blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef } @@ -32,6 +33,9 @@ UIT = (function () { handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. handLateralOffset: 0.01, // Offset of UI in direction of palm normal. + topMargin: 0.010, + leftMargin: 0.0118, + header: { x: 0.24, y: 0.048, z: 0.012 }, headerHeading: { x: 0.24, y: 0.044, z: 0.012 }, headerBar: { x: 0.24, y: 0.004, z: 0.012 }, @@ -39,6 +43,8 @@ UIT = (function () { itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0040 }, // Cursor intersection zone for Tools and Create items. + buttonDimensions: { x: 0.2164, y: 0.0840, z: 0.0040 }, // Default size of large single options button. + menuButtonDimensions: { x: 0.0267, y: 0.0267, z: 0.0040 }, menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0040 }, // Non-hovered position relative to itemCollisionZone. menuButtonLabelYOffset: -0.00915, // Relative to itemCollisionZone. @@ -50,6 +56,8 @@ UIT = (function () { paletteItemIconDimensions: { x: 0.024, y: 0.024, z: 0.024 }, paletteItemIconOffset: { x: 0, y: 0, z: 0.015 }, // Non-hovered position relative to palette button. + horizontalRuleHeight : 0.0004, + imageOverlayOffset: 0.001 // Raise image above surface. } }; From 7e817652a773fc1c93a95eb2fac942b512f6e8e6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 09:51:51 +1200 Subject: [PATCH 233/504] Style Clone options --- scripts/vr-edit/modules/toolsMenu.js | 56 ++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 34d931bde0..9e409cc40e 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -522,21 +522,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, OPTONS_PANELS = { - cloneOptions: [ - { - id: "cloneFinishButton", - type: "button", - properties: { - dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: 0.005 }, - color: { red: 200, green: 200, blue: 200 } - }, - label: "FINISH", - command: { - method: "clearTool" - } - } - ], groupOptions: [ { id: "groupButton", @@ -776,6 +761,47 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], + cloneOptions: [ + { + id: "cloneActionsLabel", + type: "image", + properties: { + color: UIT.colors.white, + url: "../assets/tools/common/actions-label.svg", + scale: 0.0276, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.topMargin - 0.0047 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "cloneRule1", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0199, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "cloneFinishButton", + type: "newButton", + properties: { + localPosition: { x: 0, y: 0.02, z: 0.005 } + }, + newLabel: { + url: "../assets/tools/common/finish-label.svg", + scale: 0.0318 + }, + command: { + method: "clearTool" + } + } + ], physicsOptions: [ { id: "propertiesLabel", From 73a6f1f27847a5ac063c4ffaa6cdaffb3f1ba407 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 10:47:41 +1200 Subject: [PATCH 234/504] Style Delete options --- .../vr-edit/assets/tools/delete/info-text.svg | 83 +++++++++++++++++++ scripts/vr-edit/modules/toolsMenu.js | 77 +++++++++++++++-- 2 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/delete/info-text.svg diff --git a/scripts/vr-edit/assets/tools/delete/info-text.svg b/scripts/vr-edit/assets/tools/delete/info-text.svg new file mode 100644 index 0000000000..4148bd8525 --- /dev/null +++ b/scripts/vr-edit/assets/tools/delete/info-text.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 9e409cc40e..13c46603d0 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1056,17 +1056,82 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ], deleteOptions: [ { - id: "deleteFinishButton", - type: "button", + id: "deleteActionsLabel", + type: "image", properties: { - dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0, z: 0.005 }, - color: { red: 200, green: 200, blue: 200 } + color: UIT.colors.white, + url: "../assets/tools/common/actions-label.svg", + scale: 0.0276, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.topMargin - 0.0047 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "deleeteRule1", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0199, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "deleteFinishButton", + type: "newButton", + properties: { + localPosition: { x: 0, y: 0.02, z: 0.005 } + }, + newLabel: { + url: "../assets/tools/common/finish-label.svg", + scale: 0.0318 }, - label: "FINISH", command: { method: "clearTool" } + }, + { + id: "deleteRule2", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.1197, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "deleteInfoIcon", + type: "image", + properties: { + url: "../assets/tools/common/info-icon.svg", + dimensions: { x: 0.0321, y: 0.0320 }, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white // Icon SVG is already lightGray color. + } + }, + { + id: "deleteInfo", + type: "image", + properties: { + url: "../assets/tools/delete/info-text.svg", + scale: 0.1416, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0240 / 2 + 0.0063 / 2, // Off-center w.r.t. info icon. + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white + } } ] }, From 2899d5183571cfe1e78a5bd64208bc469c140232 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 11:18:25 +1200 Subject: [PATCH 235/504] Fix "Finish" buttons not clearing tool action --- scripts/vr-edit/vr-edit.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 67976a1d91..1ae8bf7517 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1370,6 +1370,8 @@ ui.updateUIEntities(); break; case "clearTool": + grouping.clear(); + toolSelected = TOOL_NONE; ui.clearTool(); ui.updateUIEntities(); break; From c29a900588b320b6408b18fac441522a4e66dfeb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 12:08:37 +1200 Subject: [PATCH 236/504] Fix button hovering and pressing problems --- scripts/vr-edit/modules/toolsMenu.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 13c46603d0..ce81847ffe 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -496,7 +496,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, BUTTON_UI_ELEMENTS = ["button", "newButton", "menuButton", "toggleButton", "swatch"], - BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.004 }, + BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.8 * UIT.dimensions.buttonDimensions.z }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], COLOR_CIRCLE_UI_ELEMENTS = ["colorCircle"], @@ -2281,6 +2281,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, visible: false }); + } else if (isHighlightingNewButton) { + // Unhighlight button. + Overlays.editOverlay(highlightedSource[highlightedItem], { + color: UIT.colors.baseGrayShadow + }); } else if (isHighlightingSlider || isHighlightingColorCircle) { // Lower old slider or color circle. Overlays.editOverlay(highlightedSource[highlightedItem], { @@ -2361,6 +2366,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }); } else if (isHighlightingNewButton) { + // Unhighlight button. Overlays.editOverlay(highlightedSource[highlightedItem], { color: UIT.colors.baseGrayShadow }); @@ -2387,7 +2393,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { || isTriggerClicked !== (pressedItem !== null)) { if (pressedItem) { // Unpress previous button. - Overlays.editOverlay(intersectionOverlays[pressedItem.index], { + Overlays.editOverlay(pressedSource[pressedItem.index], { localPosition: pressedItem.localPosition }); pressedItem = null; From b0ef570ce0bc7d10883be42b1c3fb6534e4180d5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 14:20:37 +1200 Subject: [PATCH 237/504] Style Group options --- .../assets/tools/group/group-label.svg | 12 + .../assets/tools/group/ungroup-label.svg | 12 + scripts/vr-edit/modules/toolsMenu.js | 207 +++++++++++++----- scripts/vr-edit/modules/uit.js | 3 + 4 files changed, 183 insertions(+), 51 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/group/group-label.svg create mode 100644 scripts/vr-edit/assets/tools/group/ungroup-label.svg diff --git a/scripts/vr-edit/assets/tools/group/group-label.svg b/scripts/vr-edit/assets/tools/group/group-label.svg new file mode 100644 index 0000000000..b2f15b4b22 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group/group-label.svg @@ -0,0 +1,12 @@ + + + + GROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group/ungroup-label.svg b/scripts/vr-edit/assets/tools/group/ungroup-label.svg new file mode 100644 index 0000000000..ec246359b5 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group/ungroup-label.svg @@ -0,0 +1,12 @@ + + + + UNGROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index ce81847ffe..83adc7582c 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -31,6 +31,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOverlays = [], menuHoverOverlays = [], + menuEnabled = [], optionsOverlays = [], optionsOverlaysIDs = [], // Text ids (names) of options overlays. @@ -522,36 +523,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, OPTONS_PANELS = { - groupOptions: [ - { - id: "groupButton", - type: "button", - properties: { - dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: 0.025, z: 0.005 }, - color: { red: 200, green: 200, blue: 200 } - }, - label: " GROUP", - enabledColor: { red: 64, green: 240, blue: 64 }, - callback: { - method: "groupButton" - } - }, - { - id: "ungroupButton", - type: "button", - properties: { - dimensions: { x: 0.07, y: 0.03, z: 0.01 }, - localPosition: { x: 0, y: -0.025, z: 0.005 }, - color: { red: 200, green: 200, blue: 200 } - }, - label: "UNGROUP", - enabledColor: { red: 240, green: 64, blue: 64 }, - callback: { - method: "ungroupButton" - } - } - ], colorOptions: [ { id: "colorCircle", @@ -711,7 +682,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "stretchFinishButton", type: "newButton", properties: { - localPosition: { x: 0, y: 0.02, z: 0.005 } + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0280 - UIT.dimensions.buttonDimensions.y / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + } }, newLabel: { url: "../assets/tools/common/finish-label.svg", @@ -791,7 +766,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "cloneFinishButton", type: "newButton", properties: { - localPosition: { x: 0, y: 0.02, z: 0.005 } + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0280 - UIT.dimensions.buttonDimensions.y / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + } }, newLabel: { url: "../assets/tools/common/finish-label.svg", @@ -802,6 +781,90 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], + groupOptions: [ + { + id: "groupActionsLabel", + type: "image", + properties: { + color: UIT.colors.white, + url: "../assets/tools/common/actions-label.svg", + scale: 0.0276, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.topMargin - 0.0047 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "groupRule1", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0199, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "groupButton", + type: "newButton", + properties: { + dimensions: { + x: UIT.dimensions.buttonDimensions.x, + y: 0.0680, + z: UIT.dimensions.buttonDimensions.z + }, + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0280 - 0.0680 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + }, + color: UIT.colors.baseGrayShadow + }, + enabledColor: UIT.colors.greenShadow, + highlightColor: UIT.colors.greenHighlight, + newLabel: { + url: "../assets/tools/group/group-label.svg", + scale: 0.0351, + color: UIT.colors.baseGray + }, + labelEnabledColor: UIT.colors.white, + callback: { + method: "groupButton" + } + }, + { + id: "ungroupButton", + type: "newButton", + properties: { + dimensions: { + x: UIT.dimensions.buttonDimensions.x, + y: 0.0680, + z: UIT.dimensions.buttonDimensions.z + }, + localPosition: { + x: 0, + y: -UIT.dimensions.panel.y / 2 + 0.0120 + 0.0680 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + }, + color: UIT.colors.baseGrayShadow + + }, + enabledColor: UIT.colors.redAccent, + highlightColor: UIT.colors.redHighlight, + newLabel: { + url: "../assets/tools/group/ungroup-label.svg", + scale: 0.0496, + color: UIT.colors.baseGray + }, + labelEnabledColor: UIT.colors.white, + callback: { + method: "ungroupButton" + } + } + ], physicsOptions: [ { id: "propertiesLabel", @@ -1084,7 +1147,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteFinishButton", type: "newButton", properties: { - localPosition: { x: 0, y: 0.02, z: 0.005 } + localPosition: { + x: 0, + y: UIT.dimensions.panel.y / 2 - 0.0280 - UIT.dimensions.buttonDimensions.y / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + } }, newLabel: { url: "../assets/tools/common/finish-label.svg", @@ -1376,7 +1443,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { intersectionEnabled, highlightedItem, highlightedItems, - highlightedSource, + highlightedSourceOverlays, + highlightedSourceItems, isHighlightingButton, isHighlightingNewButton, // TODO: Delete when no longer needed. isHighlightingMenuButton, @@ -1477,6 +1545,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.parentID = menuPanelOverlay; itemID = Overlays.addOverlay(UI_ELEMENTS[MENU_ITEMS[i].type].overlay, properties); menuOverlays[i] = itemID; + menuEnabled[i] = true; if (MENU_ITEMS[i].label) { properties = Object.clone(UI_ELEMENTS.label.properties); @@ -1629,7 +1698,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.merge(properties, optionsItems[i].newLabel); properties.url = Script.resolvePath(properties.url); properties.parentID = optionsOverlays[optionsOverlays.length - 1]; - Overlays.addOverlay(UI_ELEMENTS.image.overlay, properties); + id = Overlays.addOverlay(UI_ELEMENTS.image.overlay, properties); + optionsOverlaysLabels[i] = id; } if (optionsItems[i].type === "barSlider") { @@ -2187,6 +2257,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function update(intersection, groupsCount, entitiesCount) { var intersectedItem = NONE, intersectionItems, + color, parentProperties, localPosition, parameter, @@ -2260,6 +2331,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (intersectedItem !== NONE) { intersectionItems = MENU_ITEMS; intersectionOverlays = menuOverlays; + intersectionEnabled = menuEnabled; } else { intersectedItem = optionsOverlays.indexOf(intersection.overlayID); if (intersectedItem !== NONE) { @@ -2271,7 +2343,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Highlight clickable item. - if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSource) { + if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSourceOverlays) { if (intersectedItem !== NONE && intersectionItems[intersectedItem] && (intersectionItems[intersectedItem].command !== undefined || intersectionItems[intersectedItem].callback !== undefined)) { @@ -2283,13 +2355,20 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); } else if (isHighlightingNewButton) { // Unhighlight button. - Overlays.editOverlay(highlightedSource[highlightedItem], { - color: UIT.colors.baseGrayShadow + if (highlightedSourceItems[highlightedItem].enabledColor !== undefined && optionsEnabled[highlightedItem]) { + color = highlightedSourceItems[highlightedItem].enabledColor; + } else { + color = highlightedSourceItems[highlightedItem].properties.color !== undefined + ? highlightedSourceItems[highlightedItem].properties.color + : UI_ELEMENTS.newButton.properties.color; + } + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + color: color }); } else if (isHighlightingSlider || isHighlightingColorCircle) { // Lower old slider or color circle. - Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: highlightedItems[highlightedItem].properties.localPosition + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + localPosition: highlightedSourceItems[highlightedItem].properties.localPosition }); } // Update status variables. @@ -2337,9 +2416,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }); } else if (isHighlightingNewButton) { - Overlays.editOverlay(intersectionOverlays[highlightedItem], { - color: UIT.colors.greenHighlight - }); + if (intersectionEnabled[highlightedItem]) { + Overlays.editOverlay(intersectionOverlays[highlightedItem], { + color: intersectionItems[highlightedItem].highlightColor !== undefined + ? intersectionItems[highlightedItem].highlightColor + : UIT.colors.greenHighlight + }); + } } else if (!isHighlightingMenuButton) { Overlays.editOverlay(highlightOverlay, { parentID: intersectionOverlays[intersectedItem], @@ -2367,13 +2450,20 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); } else if (isHighlightingNewButton) { // Unhighlight button. - Overlays.editOverlay(highlightedSource[highlightedItem], { - color: UIT.colors.baseGrayShadow + if (highlightedSourceItems[highlightedItem].enabledColor !== undefined && optionsEnabled[highlightedItem]) { + color = highlightedSourceItems[highlightedItem].enabledColor; + } else { + color = highlightedSourceItems[highlightedItem].properties.color !== undefined + ? highlightedSourceItems[highlightedItem].properties.color + : UI_ELEMENTS.newButton.properties.color; + } + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + color: color }); } else if (isHighlightingSlider || isHighlightingColorCircle) { // Lower slider or color circle. - Overlays.editOverlay(highlightedSource[highlightedItem], { - localPosition: highlightedItems[highlightedItem].properties.localPosition + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + localPosition: highlightedSourceItems[highlightedItem].properties.localPosition }); } // Update status variables. @@ -2385,7 +2475,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isHighlightingColorCircle = false; isHighlightingPicklist = false; } - highlightedSource = intersectionOverlays; + highlightedSourceOverlays = intersectionOverlays; + highlightedSourceItems = intersectionItems; } // Press/unpress button. @@ -2536,9 +2627,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isGroupButtonEnabled = enableGroupButton; Overlays.editOverlay(optionsOverlays[groupButtonIndex], { color: isGroupButtonEnabled - ? OPTONS_PANELS.groupOptions[groupButtonIndex].enabledColor + ? (highlightedItem === groupButtonIndex + ? OPTONS_PANELS.groupOptions[groupButtonIndex].highlightColor + : OPTONS_PANELS.groupOptions[groupButtonIndex].enabledColor) : OPTONS_PANELS.groupOptions[groupButtonIndex].properties.color }); + Overlays.editOverlay(optionsOverlaysLabels[groupButtonIndex], { + color: isGroupButtonEnabled + ? OPTONS_PANELS.groupOptions[groupButtonIndex].labelEnabledColor + : OPTONS_PANELS.groupOptions[groupButtonIndex].newLabel.color + }); optionsEnabled[groupButtonIndex] = enableGroupButton; } @@ -2547,9 +2645,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isUngroupButtonEnabled = enableUngroupButton; Overlays.editOverlay(optionsOverlays[ungroupButtonIndex], { color: isUngroupButtonEnabled - ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].enabledColor + ? (highlightedItem === ungroupButtonIndex + ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].highlightColor + : OPTONS_PANELS.groupOptions[ungroupButtonIndex].enabledColor) : OPTONS_PANELS.groupOptions[ungroupButtonIndex].properties.color }); + Overlays.editOverlay(optionsOverlaysLabels[ungroupButtonIndex], { + color: isUngroupButtonEnabled + ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].labelEnabledColor + : OPTONS_PANELS.groupOptions[ungroupButtonIndex].newLabel.color + }); optionsEnabled[ungroupButtonIndex] = enableUngroupButton; } } @@ -2628,7 +2733,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { intersectionOverlays = null; intersectionEnabled = null; highlightedItem = NONE; - highlightedSource = null; + highlightedSourceOverlays = null; isHighlightingButton = false; isHighlightingMenuButton = false; isHighlightingSlider = false; diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index ae3c1674f9..0ef61c717c 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -21,6 +21,9 @@ UIT = (function () { baseGray: { red: 0x40, green: 0x40, blue: 0x40 }, baseGrayShadow: { red: 0x25, green: 0x25, blue: 0x25 }, darkGray: { red: 0x12, green: 0x12, blue: 0x12 }, + redAccent: { red: 0xc6, green: 0x21, blue: 0x47 }, + redHighlight: { red: 0xea, green: 0x4c, blue: 0x5f }, + greenShadow: { red: 0x35, green: 0x9d, blue: 0x85 }, greenHighlight: { red: 0x1f, green: 0xc6, blue: 0xa6 }, blueHighlight: { red: 0x00, green: 0xbf, blue: 0xef } }, From 64821ccd76dbf8f91b9942b2d07796559b5d7c21 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Aug 2017 17:09:04 +1200 Subject: [PATCH 238/504] Update general Color options layout --- scripts/vr-edit/modules/toolsMenu.js | 170 ++++++++++++++++++++------- 1 file changed, 127 insertions(+), 43 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 83adc7582c..91fc176e29 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -297,11 +297,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "swatch": { overlay: "cube", properties: { - dimensions: { x: 0.03, y: 0.03, z: 0.01 }, + dimensions: { x: 0.0294, y: 0.0294, z: UIT.dimensions.buttonDimensions.z }, localRotation: Quat.ZERO, color: NO_SWATCH_COLOR, alpha: 1.0, - solid: false, // False indicates "no swatch color assigned" + solid: false, // False indicates "no swatch color assigned" // TODO ignoreRayIntersection: false, visible: true } @@ -327,7 +327,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "circle": { overlay: "circle3d", properties: { - size: 0.01, + size: 0.0147, localPosition: { x: 0.0, y: 0.0, z: 0.01 }, localRotation: Quat.ZERO, color: { red: 128, green: 128, blue: 128 }, @@ -525,41 +525,41 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { OPTONS_PANELS = { colorOptions: [ { - id: "colorCircle", - type: "colorCircle", + id: "colorActionsLabel", + type: "image", properties: { - localPosition: { x: -0.0125, y: 0.025, z: 0.005 } - }, - imageURL: "../assets/color-circle.png", - imageOverlayURL: "../assets/color-circle-black.png", - command: { - method: "setColorPerCircle" + color: UIT.colors.white, + url: "../assets/tools/common/actions-label.svg", + scale: 0.0276, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, + y: UIT.dimensions.panel.y / 2 - UIT.dimensions.topMargin - 0.0047 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } } }, { - id: "colorSlider", - type: "imageSlider", + id: "colorRule1", + type: "horizontalRule", properties: { - localPosition: { x: 0.035, y: 0.025, z: 0.005 } - }, - useBaseColor: true, - imageURL: "../assets/slider-white.png", - imageOverlayURL: "../assets/slider-v-alpha.png", - command: { - method: "setColorPerSlider" + dimensions: { x: 0.0668, y: 0.001 }, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0668 / 2, + y: UIT.dimensions.panel.y / 2 - 0.0199, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } } }, { id: "colorSwatch1", type: "swatch", properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.02, z: 0.005 } + localPosition: { x: -0.0935, y: 0.0513, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, setting: { key: "VREdit.colorTool.swatch1Color", - property: "color", - defaultValue: { red: 0, green: 255, blue: 0 } + property: "color" + // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -572,13 +572,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorSwatch2", type: "swatch", properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.02, z: 0.005 } + localPosition: { x: -0.0561, y: 0.0513, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, setting: { key: "VREdit.colorTool.swatch2Color", - property: "color", - defaultValue: { red: 0, green: 0, blue: 255 } + property: "color" + // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -591,13 +590,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorSwatch3", type: "swatch", properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.035, y: -0.045, z: 0.005 } + localPosition: { x: -0.0935, y: 0.0153, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, setting: { key: "VREdit.colorTool.swatch3Color", property: "color" - // Default to empty swatch. + // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -610,13 +608,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "colorSwatch4", type: "swatch", properties: { - dimensions: { x: 0.02, y: 0.02, z: 0.01 }, - localPosition: { x: -0.01, y: -0.045, z: 0.005 } + localPosition: { x: -0.0561, y: 0.0153, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, setting: { key: "VREdit.colorTool.swatch4Color", property: "color" - // Default to empty swatch. + // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -626,30 +623,117 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - id: "currentColor", - type: "circle", + id: "colorSwatch5", + type: "swatch", properties: { - localPosition: { x: 0.025, y: -0.02, z: 0.007 } + localPosition: { x: -0.0935, y: -0.0207, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, setting: { - key: "VREdit.colorTool.currentColor", - property: "color", - defaultValue: { red: 128, green: 128, blue: 128 }, - command: "setPickColor" + key: "VREdit.colorTool.swatch5Color", + property: "color" + // defaultValue: { red: ?, green: ?, blue: ? }, // Default to empty swatch. + }, + command: { + method: "setColorPerSwatch" + }, + clear: { + method: "clearSwatch" + } + }, + { + id: "colorSwatch6", + type: "swatch", + properties: { + localPosition: { x: -0.0561, y: -0.0207, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } + }, + setting: { + key: "VREdit.colorTool.swatch6Color", + property: "color" + // defaultValue: { red: ?, green: ?, blue: ? }, // Default to empty swatch. + }, + command: { + method: "setColorPerSwatch" + }, + clear: { + method: "clearSwatch" + } + }, + { + id: "colorRule2", + type: "horizontalRule", + properties: { + dimensions: { x: 0.0668, y: 0.001 }, + localPosition: { + x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0668 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0481, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "colorCircle", + type: "colorCircle", + properties: { + localPosition: { x: 0.04675, y: 0.01655, z: 0.005 } + }, + imageURL: "../assets/color-circle.png", + imageOverlayURL: "../assets/color-circle-black.png", + command: { + method: "setColorPerCircle" + } + }, + { + id: "colorSlider", + type: "imageSlider", + properties: { + localPosition: { x: 0.04675, y: -0.0620, z: 0.005 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }) + }, + useBaseColor: true, + imageURL: "../assets/slider-white.png", + imageOverlayURL: "../assets/slider-v-alpha.png", + command: { + method: "setColorPerSlider" + } + }, + { + id: "colorRule3", + type: "horizontalRule", + properties: { + dimensions: { x: 0.1229, y: 0.001 }, + localPosition: { + x: 0.04675, + y: -0.0781, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } } }, { id: "pickColor", type: "button", properties: { - dimensions: { x: 0.04, y: 0.02, z: 0.01 }, - localPosition: { x: 0.025, y: -0.045, z: 0.005 }, + dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, + localPosition: { x: -0.0935, y: -0.064, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, color: { red: 255, green: 255, blue: 255 } }, label: " PICK", callback: { method: "pickColorTool" } + }, + { + id: "currentColor", + type: "circle", + properties: { + //dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, + localPosition: { x: -0.0561, y: -0.064, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } + }, + setting: { + key: "VREdit.colorTool.currentColor", + property: "color", + defaultValue: { red: 128, green: 128, blue: 128 }, + command: "setPickColor" + } } ], scaleOptions: [ From c8381f32bbd754f37b3023df3df686383a8a09b7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 27 Aug 2017 21:15:27 +1200 Subject: [PATCH 239/504] Style current color indicator --- scripts/vr-edit/modules/toolsMenu.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 91fc176e29..42615b836f 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -324,13 +324,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "circle": { - overlay: "circle3d", + "square": { + overlay: "cube", // Emulate a 2D square with a cube. properties: { - size: 0.0147, - localPosition: { x: 0.0, y: 0.0, z: 0.01 }, localRotation: Quat.ZERO, - color: { red: 128, green: 128, blue: 128 }, + color: UIT.colors.baseGrayShadow, alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -723,10 +721,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, { id: "currentColor", - type: "circle", + type: "square", properties: { - //dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, - localPosition: { x: -0.0561, y: -0.064, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } + dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.imageOverlayOffset }, + localPosition: { + x: -0.0561, + y: -0.064, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + } }, setting: { key: "VREdit.colorTool.currentColor", From 8dd06bc270f006572c63b9747f9daa4d2dfa7213 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Aug 2017 12:06:26 +1200 Subject: [PATCH 240/504] Style color picker button --- .../assets/tools/color/pick-color-label.svg | 14 +++ scripts/vr-edit/modules/toolsMenu.js | 92 +++++++++++++++++-- scripts/vr-edit/vr-edit.js | 13 ++- 3 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/color/pick-color-label.svg diff --git a/scripts/vr-edit/assets/tools/color/pick-color-label.svg b/scripts/vr-edit/assets/tools/color/pick-color-label.svg new file mode 100644 index 0000000000..6fa2997328 --- /dev/null +++ b/scripts/vr-edit/assets/tools/color/pick-color-label.svg @@ -0,0 +1,14 @@ + + + + noun_792623 + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 42615b836f..91f14c5076 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -40,6 +40,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsColorData = [], // Uses same index values as optionsOverlays. optionsEnabled = [], optionsSettings = {}, + optionsToggles = {}, // For toggle buttons without a setting. highlightOverlay, @@ -281,7 +282,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white } }, - "toggleButton": { + "toggleButton": { // TODO: Delete overlay: "cube", properties: { dimensions: { x: 0.03, y: 0.03, z: 0.01 }, @@ -294,6 +295,29 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { onColor: UI_HIGHLIGHT_COLOR, offColor: UI_BASE_COLOR }, + "newToggleButton": { // TODO: Rename to "toggleButton". + overlay: "cube", + properties: { + dimensions: UIT.dimensions.buttonDimensions, + localRotation: Quat.ZERO, + color: UIT.colors.baseGrayShadow, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + onColor: UIT.colors.greenShadow, + offColor: UIT.colors.baseGrayShadow, + newLabel: { // TODO: Rename to "label". + // Relative to newToggleButton. + localPosition: { + x: 0, + y: 0, + z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, + color: UIT.colors.white + } + }, "swatch": { overlay: "cube", properties: { @@ -494,7 +518,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, - BUTTON_UI_ELEMENTS = ["button", "newButton", "menuButton", "toggleButton", "swatch"], + BUTTON_UI_ELEMENTS = ["button", "newButton", "menuButton", "toggleButton", "newToggleButton", "swatch"], BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.8 * UIT.dimensions.buttonDimensions.z }, SLIDER_UI_ELEMENTS = ["barSlider", "imageSlider"], @@ -708,15 +732,17 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, { id: "pickColor", - type: "button", + type: "newToggleButton", properties: { dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0935, y: -0.064, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, - color: { red: 255, green: 255, blue: 255 } }, - label: " PICK", - callback: { - method: "pickColorTool" + newLabel: { + url: "../assets/tools/color/pick-color-label.svg", + scale: 0.0120 + }, + command: { + method: "togglePickColor" } }, { @@ -1511,7 +1537,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { yDelta: 0.004, zDimension: 0.001, properties: { - localPosition: { x: 0, y: 0, z: 0.003 }, + localPosition: { x: 0, y: 0, z: 0.001 }, localRotation: Quat.ZERO, color: { red: 255, green: 255, blue: 0 }, alpha: 0.8, @@ -1533,6 +1559,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedSourceItems, isHighlightingButton, isHighlightingNewButton, // TODO: Delete when no longer needed. + isHighlightingNewToggleButton, // TODO: Rename. isHighlightingMenuButton, isHighlightingSlider, isHighlightingColorCircle, @@ -1767,7 +1794,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { uiCommandCallback(optionsItems[i].setting.callback, value); } } + } else if (optionsItems[i].type === "newToggleButton") { + optionsToggles[optionsItems[i].id] = false; // Default to off. } + optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); optionsOverlaysIDs.push(optionsItems[i].id); if (optionsItems[i].label) { @@ -2184,7 +2214,23 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; + case "togglePickColor": + optionsToggles.pickColor = !optionsToggles.pickColor; + index = optionsOverlaysIDs.indexOf("pickColor"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsToggles.pickColor + ? UI_ELEMENTS[optionsItems[index].type].onColor + : UI_ELEMENTS[optionsItems[index].type].offColor + }); + uiCommandCallback("pickColorTool", optionsToggles.pickColor); + break; + case "setColorFromPick": + optionsToggles.pickColor = false; + index = optionsOverlaysIDs.indexOf("pickColor"); + Overlays.editOverlay(optionsOverlays[index], { + color: UI_ELEMENTS[optionsItems[index].type].offColor + }); setCurrentColor(parameter); setColorPicker(parameter); break; @@ -2440,7 +2486,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: false }); } else if (isHighlightingNewButton) { - // Unhighlight button. + // Unhighlight old button. if (highlightedSourceItems[highlightedItem].enabledColor !== undefined && optionsEnabled[highlightedItem]) { color = highlightedSourceItems[highlightedItem].enabledColor; } else { @@ -2451,6 +2497,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { color: color }); + } else if (isHighlightingNewToggleButton) { + // Unhighlight old button. + color = optionsToggles[highlightedSourceItems[highlightedItem].id] + ? UI_ELEMENTS.newToggleButton.onColor + : UI_ELEMENTS.newToggleButton.offColor; + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + color: color + }); } else if (isHighlightingSlider || isHighlightingColorCircle) { // Lower old slider or color circle. Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { @@ -2462,6 +2516,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItems = intersectionItems; isHighlightingButton = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingNewButton = intersectionItems[highlightedItem].type === "newButton"; + isHighlightingNewToggleButton = intersectionItems[highlightedItem].type === "newToggleButton"; isHighlightingMenuButton = intersectionItems[highlightedItem].type === "menuButton"; isHighlightingSlider = SLIDER_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; isHighlightingColorCircle = COLOR_CIRCLE_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; @@ -2501,7 +2556,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: HIGHLIGHT_PROPERTIES.properties.color, visible: true }); - } else if (isHighlightingNewButton) { + } else if (isHighlightingNewButton || isHighlightingNewToggleButton) { if (intersectionEnabled[highlightedItem]) { Overlays.editOverlay(intersectionOverlays[highlightedItem], { color: intersectionItems[highlightedItem].highlightColor !== undefined @@ -2546,6 +2601,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { color: color }); + } else if (isHighlightingNewToggleButton) { + // Unhighlight old button. + color = optionsToggles[highlightedSourceItems[highlightedItem].id] + ? UI_ELEMENTS.newToggleButton.onColor + : UI_ELEMENTS.newToggleButton.offColor; + Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + color: color + }); } else if (isHighlightingSlider || isHighlightingColorCircle) { // Lower slider or color circle. Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { @@ -2556,6 +2619,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItem = NONE; isHighlightingButton = false; isHighlightingNewButton = false; + isHighlightingNewToggleButton = false; isHighlightingMenuButton = false; isHighlightingSlider = false; isHighlightingColorCircle = false; @@ -2821,10 +2885,18 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { highlightedItem = NONE; highlightedSourceOverlays = null; isHighlightingButton = false; + isHighlightingNewButton = false; + isHighlightingNewToggleButton = false; isHighlightingMenuButton = false; isHighlightingSlider = false; isHighlightingColorCircle = false; isHighlightingPicklist = false; + for (id in optionsToggles) { + if (optionsToggles.hasOwnProperty(id)) { + optionsToggles[id] = false; + } + } + isPicklistOpen = false; pressedItem = null; pressedSource = null; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 1ae8bf7517..a760412658 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1352,10 +1352,15 @@ ui.updateUIEntities(); break; case "pickColorTool": - grouping.clear(); - toolSelected = TOOL_PICK_COLOR; - ui.setToolIcon(ui.COLOR_TOOL); - ui.updateUIEntities(); + if (parameter) { + grouping.clear(); + toolSelected = TOOL_PICK_COLOR; + ui.updateUIEntities(); + } else { + grouping.clear(); + toolSelected = TOOL_COLOR; + ui.updateUIEntities(); + } break; case "physicsTool": grouping.clear(); From 9abcf9525c0ce6a33a1cf0b0416bda71c01612c0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Aug 2017 15:51:11 +1200 Subject: [PATCH 241/504] Use Settings values to determine whether or not swatches are populated --- scripts/vr-edit/modules/toolsMenu.js | 40 ++++++++++------------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 91f14c5076..931796f810 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -175,7 +175,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, - NO_SWATCH_COLOR = { red: 128, green: 128, blue: 128 }, + EMPTY_SWATCH_COLOR = UIT.colors.baseGrayShadow, UI_BASE_COLOR = { red: 64, green: 64, blue: 64 }, UI_HIGHLIGHT_COLOR = { red: 100, green: 240, blue: 100 }, @@ -323,12 +323,15 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.0294, y: 0.0294, z: UIT.dimensions.buttonDimensions.z }, localRotation: Quat.ZERO, - color: NO_SWATCH_COLOR, + color: EMPTY_SWATCH_COLOR, alpha: 1.0, - solid: false, // False indicates "no swatch color assigned" // TODO + solid: true, ignoreRayIntersection: false, visible: true } + // Must have a setting property in order to function property. + // Setting property may optionally include a defaultValue. + // A setting value of "" means that the swatch is unpopulated. }, "label": { overlay: "text3d", @@ -581,7 +584,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch1Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -599,7 +601,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch2Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -617,7 +618,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch3Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -635,7 +635,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch4Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? } - Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -653,7 +652,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch5Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? }, // Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -671,7 +669,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.swatch6Color", property: "color" - // defaultValue: { red: ?, green: ?, blue: ? }, // Default to empty swatch. }, command: { method: "setColorPerSwatch" @@ -1768,10 +1765,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } if (value !== "") { properties[optionsItems[i].setting.property] = value; - if (optionsItems[i].type === "swatch") { - // Special case for when swatch color is defined. - properties.solid = true; - } if (optionsItems[i].type === "toggleButton") { // Store value in optionsSettings rather than using overlay property. optionsSettings[optionsItems[i].id].value = value; @@ -2161,7 +2154,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand = function (command, parameter) { var index, - hasColor, value, items, parentID, @@ -2195,22 +2187,19 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "setColorPerSwatch": index = optionsOverlaysIDs.indexOf(parameter); - hasColor = Overlays.getProperty(optionsOverlays[index], "solid"); - if (hasColor) { - value = Overlays.getProperty(optionsOverlays[index], "color"); + value = Settings.getValue(optionsSettings[parameter].key); + if (value !== "") { + // Set current color to swatch color. setCurrentColor(value); setColorPicker(value); uiCommandCallback("setColor", value); } else { - // Swatch has no color; set swatch color to current fill color. + // Swatch has no color; set swatch color to current color. value = Overlays.getProperty(optionsOverlays[optionsOverlaysIDs.indexOf("currentColor")], "color"); Overlays.editOverlay(optionsOverlays[index], { - color: value, - solid: true + color: value }); - if (optionsSettings[parameter]) { - Settings.setValue(optionsSettings[parameter].key, value); - } + Settings.setValue(optionsSettings[parameter].key, value); } break; @@ -2369,11 +2358,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "clearSwatch": overlayID = optionsOverlaysIDs.indexOf(parameter); Overlays.editOverlay(optionsOverlays[overlayID], { - color: NO_SWATCH_COLOR, - solid: false + color: EMPTY_SWATCH_COLOR }); if (optionsSettings[parameter]) { - Settings.setValue(optionsSettings[parameter].key, null); // Deleted settings value. + Settings.setValue(optionsSettings[parameter].key, null); // Delete settings value. } break; default: From 67eb92da8f260e62d1dbc1704dd609ddb9589078 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 29 Aug 2017 10:51:36 +1200 Subject: [PATCH 242/504] Style color swatches --- scripts/vr-edit/modules/toolsMenu.js | 112 +++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 931796f810..2f8147abee 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -39,10 +39,18 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSliderData = [], // Uses same index values as optionsOverlays. optionsColorData = [], // Uses same index values as optionsOverlays. optionsEnabled = [], - optionsSettings = {}, + optionsSettings = { + //

`qzH5SMg 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ⅈ#d@tb@HpUu1R@;ozl_#<|J&0<|y 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ⅇ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 F{|~4DX_x>2 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/audio/drop.wav b/scripts/vr-edit/assets/audio/drop.wav new file mode 100644 index 0000000000000000000000000000000000000000..32d0e9f65a9ad1e9bafd09a892b251d29262afa3 GIT binary patch literal 48044 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 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/audio/equip.wav b/scripts/vr-edit/assets/audio/equip.wav new file mode 100644 index 0000000000000000000000000000000000000000..4a2bf5b4db91197b8029459f02c09e8caf1fc0dd GIT binary patch literal 48044 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 Date: Fri, 1 Sep 2017 14:36:44 +1200 Subject: [PATCH 263/504] Fix action after color picker failure --- scripts/vr-edit/vr-edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4ea36412d2..a85c56d0a2 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1002,11 +1002,11 @@ if (color) { colorToolColor = color; ui.doPickColor(colorToolColor); + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); } else { Feedback.play(side, Feedback.APPLY_ERROR); } - toolSelected = TOOL_COLOR; - ui.setToolIcon(ui.COLOR_TOOL); } else if (toolSelected === TOOL_PHYSICS) { selection.applyPhysics(physicsToolPhysics); } else if (toolSelected === TOOL_DELETE) { From 296d270098f42a9a6a294b91ef4f55860b540a12 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 2 Sep 2017 19:06:43 +1200 Subject: [PATCH 264/504] Enable entities inside an entity to be manipulated --- scripts/vr-edit/modules/hand.js | 41 ++++++++------ scripts/vr-edit/modules/handles.js | 5 ++ scripts/vr-edit/modules/laser.js | 86 ++++++++++++++---------------- scripts/vr-edit/vr-edit.js | 46 +++++++++++++--- 4 files changed, 109 insertions(+), 69 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 35073173ff..16bd033a74 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -42,6 +42,7 @@ Hand = function (side) { handOrientation, palmPosition, + handleOverlayIDs = [], intersection = {}; if (!this instanceof Hand) { @@ -60,6 +61,10 @@ Hand = function (side) { controllerGrip = Controller.Standard.RightGrip; } + function setHandleOverlays(overlayIDs) { + handleOverlayIDs = overlayIDs; + } + function valid() { return handPose.valid; } @@ -143,25 +148,30 @@ Hand = function (side) { isGripClickedHandled = false; } - // Hand-overlay intersection, if any. + // Hand-overlay intersection, if any handle overlays. overlayID = null; palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); - if (overlayIDs.length > 0) { - // Typically, there will be only one overlay; optimize for that case. - overlayID = overlayIDs[0]; - if (overlayIDs.length > 1) { - // Find closest overlay. - overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); - for (i = 1, length = overlayIDs.length; i < length; i += 1) { - distance = - Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); - if (distance > overlayDistance) { - overlayID = overlayIDs[i]; - overlayDistance = distance; + if (handleOverlayIDs.length > 0) { + overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); + if (overlayIDs.length > 0) { + // Typically, there will be only one overlay; optimize for that case. + overlayID = overlayIDs[0]; + if (overlayIDs.length > 1) { + // Find closest overlay. + overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); + for (i = 1, length = overlayIDs.length; i < length; i += 1) { + distance = + Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); + if (distance > overlayDistance) { + overlayID = overlayIDs[i]; + overlayDistance = distance; + } } } } + if (handleOverlayIDs.indexOf(overlayID) === -1) { + overlayID = null; + } } // Hand-entity intersection, if any editable, if overlay not intersected. @@ -194,7 +204,7 @@ Hand = function (side) { intersects: overlayID !== null || entityID !== null, overlayID: overlayID, entityID: entityID, - handIntersected: true, + handIntersected: overlayID !== null || entityID !== null, editableEntity: entityID !== null }; } @@ -208,6 +218,7 @@ Hand = function (side) { } return { + setHandleOverlays: setHandleOverlays, valid: valid, position: position, orientation: orientation, diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index e32918160a..a0279ef9a1 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -109,6 +109,10 @@ Handles = function (side) { return isAxisHandle(overlayID) || isCornerHandle(overlayID); } + function getOverlays() { + return [].concat(cornerHandleOverlays, faceHandleOverlays); + } + function scalingAxis(overlayID) { var axesIndex; if (isCornerHandle(overlayID)) { @@ -340,6 +344,7 @@ Handles = function (side) { return { display: display, + overlays: getOverlays, isHandle: isHandle, scalingAxis: scalingAxis, scalingDirections: scalingDirections, diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 991c9173b6..362f2e915d 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -151,64 +151,58 @@ Laser = function (side) { pickRay; if (!isLaserEnabled) { + intersection = {}; return; } - if (!hand.intersection().intersects) { - handPosition = hand.position(); - handOrientation = hand.orientation(); - deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); - pickRay = { - origin: Vec3.sum(handPosition, deltaOrigin), - direction: Quat.getUp(handOrientation), - length: PICK_MAX_DISTANCE - }; + handPosition = hand.position(); + handOrientation = hand.orientation(); + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; - if (hand.triggerPressed()) { + if (hand.triggerPressed()) { - // Normal laser operation with trigger. - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + // Normal laser operation with trigger. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); - } + intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); + intersection.overlayID = null; + } + intersection.laserIntersected = intersection.intersects; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); + + } else if (uiEntityIDs.length > 0) { + + // Special UI cursor. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (intersection.intersects) { intersection.laserIntersected = true; laserLength = (specifiedLaserLength !== null) ? specifiedLaserLength : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + if (!isLaserOn) { + // Start laser dot at UI distance. + searchDistance = laserLength; + } isLaserOn = true; - display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); - - } else if (uiEntityIDs.length > 0) { - - // Special UI cursor. - intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, uiEntityIDs, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - if (intersection.intersects) { - intersection.laserIntersected = true; - laserLength = (specifiedLaserLength !== null) - ? specifiedLaserLength - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - if (!isLaserOn) { - // Start laser dot at UI distance. - searchDistance = laserLength; - } - isLaserOn = true; - display(pickRay.origin, pickRay.direction, laserLength, false, false); - } else if (isLaserOn) { - isLaserOn = false; - hide(); - } - - } else { - intersection = { intersects: false }; - if (isLaserOn) { - isLaserOn = false; - hide(); - } + display(pickRay.origin, pickRay.direction, laserLength, false, false); + } else if (isLaserOn) { + isLaserOn = false; + hide(); } + } else { intersection = { intersects: false }; if (isLaserOn) { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a85c56d0a2..d8773a96c4 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -151,17 +151,33 @@ } function update() { - // Hand update. - hand.update(); - intersection = hand.intersection(); + var laserIntersection, + handIntersection; - // Laser update. - // Displays laser if hand has no intersection and trigger is pressed. + hand.update(); if (hand.valid()) { laser.update(hand); - if (!intersection.intersects) { - intersection = laser.intersection(); + // Use intersections in order to achieve entity manipulation while inside an entity: + // - Use laser overlay intersection if there is one (for UI). + // - Otherwise use hand overlay if there is one (for UI). + // - Otherwise use laser entity intersection if there is one (for entity manipulation). + // Except if hand intersection is for same entity. + // - Otherwise use hand entity intersection if there is one (for entity manipulation). + laserIntersection = laser.intersection(); + if (laserIntersection.intersects && laserIntersection.overlayID !== null) { + intersection = laserIntersection; + } else { + handIntersection = hand.intersection(); + if (handIntersection.intersects && handIntersection.overlayID !== null) { + intersection = handIntersection; + } else if (laserIntersection.intersects && laserIntersection.entityID !== handIntersection.entityID) { + intersection = laserIntersection; + } else { + intersection = handIntersection; + } } + } else { + intersection = {}; } } @@ -407,6 +423,10 @@ return handles.isHandle(overlayID); } + function setHandleOverlays(overlayIDs) { + hand.setHandleOverlays(overlayIDs); + } + function isEditing(aRootEntityID) { // aRootEntityID is an optional parameter. return editorState > EDITOR_HIGHLIGHTING @@ -656,6 +676,9 @@ function enterEditorHighlighting() { selection.select(intersectedEntityID); + if (!intersection.laserIntersected && !isUIVisible) { + laser.disable(); + } if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), toolSelected === TOOL_COLOR || toolSelected === TOOL_PICK_COLOR ? selection.intersectedEntityIndex() : null, @@ -676,12 +699,13 @@ } else { highlights.clear(); } - isOtherEditorEditingEntityID = !isOtherEditorEditingEntityID; + isOtherEditorEditingEntityID = otherEditor.isEditing(rootEntityID); } function exitEditorHighlighting() { highlights.clear(); isOtherEditorEditingEntityID = false; + laser.enable(); } function enterEditorGrabbing() { @@ -693,6 +717,7 @@ } if (toolSelected === TOOL_SCALE) { handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); + otherEditor.setHandleOverlays(handles.overlays()); } startEditing(); wasScaleTool = toolSelected === TOOL_SCALE; @@ -702,14 +727,17 @@ selection.select(intersectedEntityID); if (toolSelected === TOOL_SCALE) { handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); + otherEditor.setHandleOverlays(handles.overlays()); } else { handles.clear(); + otherEditor.setHandleOverlays([]); } } function exitEditorGrabbing() { finishEditing(); handles.clear(); + otherEditor.setHandleOverlays([]); laser.clearLength(); laser.enable(); } @@ -1170,6 +1198,7 @@ selection.clear(); highlights.clear(); handles.clear(); + otherEditor.setHandleOverlays([]); } function destroy() { @@ -1192,6 +1221,7 @@ hoverHandle: hoverHandle, enableAutoGrab: enableAutoGrab, isHandle: isHandle, + setHandleOverlays: setHandleOverlays, isEditing: isEditing, isScaling: isScaling, intersectedEntityID: getIntersectedEntityID, From 9f9a0b0c58c60c62357a909af106d35a764abe2c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 2 Sep 2017 19:09:36 +1200 Subject: [PATCH 265/504] Hide UI if hand is inside entity that camera is outside of --- scripts/vr-edit/modules/createPalette.js | 28 +++++- scripts/vr-edit/modules/toolsMenu.js | 112 +++++++++++++++++++---- scripts/vr-edit/vr-edit.js | 44 ++++++++- 3 files changed, 163 insertions(+), 21 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index ad76659d04..362551edac 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -23,6 +23,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { paletteItemOverlays = [], paletteItemPositions = [], paletteItemHoverOverlays = [], + iconOverlays = [], + staticOverlays = [], LEFT_HAND = 0, @@ -297,6 +299,21 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { return [palettePanelOverlay, paletteHeaderHeadingOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); } + function setVisible(visible) { + var i, + length; + + for (i = 0, length = staticOverlays.length; i < length; i += 1) { + Overlays.editOverlay(staticOverlays[i], { visible: visible }); + } + + if (!visible) { + for (i = 0, length = paletteItemHoverOverlays.length; i < length; i += 1) { + Overlays.editOverlay(paletteItemHoverOverlays[i], { visible: false }); + } + } + } + function update(intersectionOverlayID) { var itemIndex, isTriggerClicked, @@ -432,9 +449,13 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.merge(properties, PALETTE_ITEMS[i].icon.properties); properties.parentID = paletteItemHoverOverlays[i]; properties.url = Script.resolvePath(properties.url); - Overlays.addOverlay(PALETTE_ITEM.icon.overlay, properties); + iconOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.icon.overlay, properties); } + // Always-visible overlays. + staticOverlays = [].concat(paletteHeaderHeadingOverlay, paletteHeaderBarOverlay, paletteTitleOverlay, + palettePanelOverlay, paletteItemOverlays, iconOverlays); + isDisplaying = true; } @@ -446,6 +467,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. paletteItemOverlays = []; paletteItemHoverOverlays = []; + iconOverlays = []; + staticOverlays = []; isDisplaying = false; } @@ -455,7 +478,8 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { return { setHand: setHand, - entityIDs: getEntityIDs, + entityIDs: getEntityIDs, // TODO: Rename to overlayIDs. + setVisible: setVisible, update: update, display: display, clear: clear, diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 0d208ed09f..e1ce5bbba1 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -31,6 +31,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOverlays = [], menuHoverOverlays = [], + menuLabelOverlays = [], menuEnabled = [], optionsOverlays = [], @@ -39,6 +40,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsOverlaysSublabels = [], // Overlay IDs of sublabels for optionsOverlays. optionsSliderData = [], // Uses same index values as optionsOverlays. optionsColorData = [], // Uses same index values as optionsOverlays. + optionsExtraOverlays = [], optionsEnabled = [], optionsSettings = { // jT^bqCwf9WH1{%AEJcphGKl~ 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

  • `MT{tAR_v7oqpy)krHkhHb+)5*Lv1)X%iF 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

    |^X{9B7EoMgJ`NpmmgXN61 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 z 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

    w zWOT~*=wigjCp^bPT*d|L#vb>CnBv&t?r84ZvPF>tDUsOqQNH3!Ugvopv~Dg>v27<2lasN)V;ddVLsdRFWupA z@z50wDWHzHbktLNPH(tu+b8-=-#K-0B!+W(;xx>}JS@txtj-2($xiIU-#LbVZ~@nH zGxzg2ukmlbX6TQ>7?0_gkLB)qhuG!QxQ3f}h-c0r@XaM2 zgp;TeMdC^vNhnDqk))IqlFFTHAx0yaq;qFm2aeC0{3wb!?=?3IgJyXVyz3? zu!(cemuCSMVtQs~VkTo$#v~l7_Cw$4M}4ZVokr}Y-qY)PL$B!-w|QLGi|+H#S3=oI z_w*nAM_=hHx6^)Cs>dh}(MiU%%*s42Ww5H#jF(H#RY789`;i?IdUaoA-`gs$@kd20rNOh^^68DQs4#_KNB(-xqM|9ZwGrYhx z_sj_C$QEN3#$zCQpe5>|{D1RsYy|L)uX&djd4yY>HgF+?O>u-{j{sL z(I(nJD?1-~Q7xc3G`GWb(`)AcSF8WPMTBblZIm)rm{(-@8LXoYZ15em~ALvcUgW^LsRs^7%aeMn}WSC=2mh`kf

    @mx$7 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 ad*;HrAP%|t@o{r=-&}j+u9wWwSN{jF`jAKf literal 192044 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ⅇ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 literal 48044 zcmX_{b!-+(xQAzU_j7ltx3oAEcPSKi_k%kWcXv3r+ri!C;2c~^TPV^}sJnjdvb!@k zm)zVunaMlJB$LT6?K|&*15J2%duX;uI_ZjX@%41)77}Knt8W8DGT*@HV_1@5dML2V8^! z(1S=&AJq9XLV*YbaRvU3KjQ!J-9J3T?{O9`#|#z$1E>d@fsP;%B!X_B3up&gfchW; z=zsu_Ab{Q2jH_@NF2m)x8k?~fGgt_eAR06R?Z6-~9LxeqU>R5g)`2ZxGuQ|=gVkU) zSO(^TBrp+-2cy71&>M6GtwB>z7sLW1FaQ~l10mr59}*w|Dxd`_5CV)K0>pq=PzN*s zO+a(d4s-+wpcm)^hJYbp7#InLgE3$P7!8Jl5nw17332=Bz(@oKyh&&RXy6#N$+frsM0xF=4; z9dJ9`0yo2raeZ7LN8{Q!97o`A9Ey!N6l-w^*5MFr__GSfVK@dy;MzF$&+dk}1#W`d z<90X!cgMYOUpyQS!{hKIJOwA=d3Xh0g*V}?csD+RkK=Rr3ciUS;>Y*{eusbH3|xTA za5Z*e4~BpO5}*b~5Dn^rCZHwg1iFEKU@#a1#)Fw42`mAt!3MAk>;Xr>DR2Q?2lv1O z@CLj9UqA}@1%87fPzq{*4Y)x7AV5L^6hS3aLjw$h(XbY*0~^AIuqA8;+raj)J?sR# zz%H;G>;Zeg-moX^4|~D>f93(O59|y3!QQY3>KF?fjk<1(}qvFuuwVHPpP^dS0vP!XIJc;x5#`}p>F(>%b_%01b2$oa%k zYR9%XTW9Nd%X0H})2W)P)laIDE7L3T%ZtnGrNI)aL{%JB)VMIAV0iwly!E+9b8cp* z{jT~Y_|+(DitOi{`ed9ZOqq)>B{s~Uy42t|D2ZAKkZxUlvH2Jv6PM} zmQU9|P5;#L6Yo=1@|Wc2$q$q7CBI7ko@`Cled_sX)2Gx=`jjOpzf*dpzEACzmYp{5 zv+T?3FALIJer3OAe7pbs#E;z>J2LnEJe&38SHW*VPD1YLypQ?n!udt%#qCP3mBm$D zu8gmKSu@7$ww$&lI#}mj_X2M-KM1DLm)I3}G;Bvka6~*eze4z1oGN`Uf2@3{zNx*T zzh=A=dM*5NA!3J9!t&X4BbY%0+E#q2q+LpI_+2Kg183|3g1`;24 zU)(dUcSfHj{j>wF3~V|0`H<$rt`Ap^Ts|suboAIc8wGnUV+KPz|E(%GUp`{wZHE}Q#vZiA#bNjH+dCE1d25=!zUWhMQav@WS*l6UT* zxlQLjo6~yEjoISa6K7qWSvo^Lqv`a7X+5SUOsPLvHmP*Nt?_gIGLCyUX7p(5$eAN* zhV>o#Y%n!w?0|#)zV!+Ait5>^`=G?JT_$!O(XoH~=52MY-7VfVJJ@7+V@<=a_14sB z6!$G=MU*DuerQi)u5Px*tK2B3q`O6Y!7i?l+5`!_ig5=P`fA*BoRzkzmg<_>RW;?S zO8rIq3Uqn5v%CB%$Xxv+{M+X*i_;pVlz+VXe*W7wuT?M2&%Zo<`S|L?8~-koW2`o=%;Ctn^PeGDD>_wbxUtqzJ0nD^!H&D>MG+rCS^ ztIN*Se<$xS>{z$GX4~v-?yWnwHr-mh<>Hp4E&aAM+0t-J!z~G0CT`iWC3%Z*>x8Xu zw#IL}zAb)x^7d&v41cHpeSGJfUHx{q+S6ojvweyC#~;{u@W~(lkrPoJyP72jKAO#Zq1*Yj*suCbtR(VCL`Wu+CG>Yk?MmaDd34z9bAceH<9@B;G= zm4G0j&6Vu1u`%UjPy*u8rcKzB8XfvpF z?^az}Hg6HpT-?moy*OJyEVv43&q=f|5_-E21^RNrF!N+B_K-Q5Le8$bnfP2j`(`*2{>Q zT6AJ?dSJi*jnCt4>Ye9#>gKpdxb8b;&Sj2b`%wEATR+=R>tw6bvd_}Yl4IU!PB7D^ z52hWa@upTLjS1CM)#TUwuF0+`uCdl|P2r|Q(@fJz(^r$o+}C`-Tx@P;*=;dd23nt4 z<80?`O8ZH>#&OZn$obki%w>1&amRbIJnOs-e1*O}{(b=_@HV)T?#vKu3VQ&J!g0V3 z-oQP?Uu0tnQ<v43C)~vpQx=%(j?~F)L!G#|(&R79)!(kA4)rCb~;Bih2|^B}x(XZ{(23 z>WEblf{0z=^6;%;ICNHMc1W9$qeizO!EjuktE;7(tUaUorRJ&IsKzS)R$P*&$O@%i ziAWM6juSNzwi2}AcjC3?w&gUV>XFfe4hjIoHEapuTuK zI^=e*)oCd<=a|0Lq*Z^YdRzIf;$!*evLB@dCDp~aNLJLKuuH-8{4II+bF*{AIUTc? z|Ni$^Fsn`0uAjd%n`G|Du>BbLeSe96fBtgj4yO0Kl+pby%gFf zv?gR*NLa{yV}dc&Fw~HtAEy7ROVmBr*3ll*K+Q~bimH)nyRur*S#d;ek+qlYmljAG zN>+&9iA16i!jpnBem#B??=d&X>CD+erIB)S6!8zN0S&-v{2r;$c=j#>nL+fOAQPAr zc<49ym-+I&iQYRNqi2^JxR<*e&UsF^W0`~F*k_Nn|7ROu^H~pC6RcI1Qyw&N^-A8k$hJMB>bRkSu{dd%Y(I;Lx_HMO4Cs;U(l+aY#F?AqAVu~%bX#J-4q8~ZZ$-`ESW zdtw*F4vwuCi)wwYb*R?RTIyP#Vphh~j!BJ9isnV1iE0@2JhDUNn~1g%|At3|9|-e= zjtPAdq6nE`d}QDl`s(-SGPDA156yD*4b?9tp{%1AAfGGSE4?ClBhD6?gjgUIgz@Y0 zT5vmXx>5be!Ne#y8cf6!(F}GvGl!lRoEKQ`U*_B3-QwBlKI}T}yyAFbe`m|ER#|*z ztvSxryJmd#_NptDKPu?*`sJg`_LruVa7%g=?<~qJj4qs4keVNvzcsHUcTn!LoF+L} zvZJ%F{;vP~zh8ZRm1b?qipl!?bLG!QKdUpZWzNlPl_|{5;U2Nq>do=J@m~w<4=$s}GhNyGNQ~_`1KfcJiCJWCDw^Zx zq;t>m=JVSN_`-DI5z#QQTJl}8UD`qBkR6rxQdBE;D;ucZsYa>I>LnVe-K+(=^*V=c zs{Xsawc)sdG4wTFHQJ3GLN2A-Tq##!kix!+t|!!w>xoJ*7XWi__iFw$Z-Qbke+1 zcTnF^HBuc_%9Se>74m`d$FgwQI%$a{L2^#ah(?R<3njv-f|q%HTK=;nTg(=owUKp{b(8g()og8Sn`OIi3);Hb|FPTby&X3l0_S39p0ls(g{y`8 zhP$EXh9}ZhKvm$0z%-EPNF|}i;n7rtJqgO}wiWWwvMs0{{8C4d! zJF;2i_lVgMWW?U^i171ab;2%$hKBAB!Nxhp?}nC!!+MLZm+p|(tZA%St$w9K%1+AV ziaYXhnO4?KI$v^H{6b_E@`a5A{rIzZTe;^r&!``yiNH_|>w)%o2%5$&V|LT0gAW5K z{yd-CEAU2n+Pa6hlAOC7*X^k`vsG?wWf^Dw+w`cWvf5ZZuxfkd+X||pYx(Z7jMAvm zMI|4KBa7D*%sNx-3+Ub_ug<^s z{4V?5A^Su&n>{t>M^4w=$GOe&?&P(|f0Ey=AhTdP=r9Vp7m9;1@ zD?eW`qEcOzRduL(bWMb*%yi$p($dqav6a~#+P6AJI2*cHSEl=lXN`A|uZ}`K)&xv(pFN)7e<^0X<#v}L-`AGt!@SAX{C`SBIJX0c&UXXT> zrOPJEP4eXmTCq~eDw9-Is$uFk>Ux@;8j~hbdqP{SZLM3Qd#l5`R{DAR6Z%xWNv|-} zGb9>@8m1cN8s-}o7-kzL8ipDY40R1cgH8WNe^x(R-(FAZKk0Vry66bqE$wuzNc&7P zMuXKS)UDO2s>v#ca*a}?+^f(kj>)z112TndiEvijE5j;atI2eh2;u z9*;Mjo5E?r*-yF15#$phj93dxK_a+?C3q#uV|%dom=I6VJ7>0IjU&d9ZeL|@WG}Iuu#K|mZAI40)@9bN)(ETH z^3(Fra@MldvdS{UGSxECGQl#_l4RLr*=xCB`Cuut2(3-6DIB=bopr_kp*S@4c_TKi5A$fC49ijp$GGGzPMl z*aTFAcHm|p8*GFPiA-V>S&z!0ws4woi@7^_?f4b^gMu!?8sRZfg1AO}SkhWrBHbWs zApa+S47;C&~sBO5aH|qE4kani_ zho+h4u-d8WrMjSWD7q^4$bZYiWmBZ*C1v7p@o3Qj;RgZ3ugjmn+rfRoDWud?Gjcky z5k3MxFhWtN8=J)Jrk@6L14N*e#XY*U;XXY)> z3(NbGyCSz)ZdJ~moY^_;b9gy<*$=Z1XD`W~nB6bCb9S5T=GkqtJ7o9I9+$l&`#|=+ z?3`>Wr$x@poXa^yIrVbq=Dy5T`Gbh^0M;X6%8x%D>qfOtS+lQR@2W!nO~ZhTbfz@);qS9_T~=YeBs>T>g(2c z@;n#4bA2uR$e$e88yrDLG3Cr{b|Gqo8T=eq_^*AQZVeVia&AZ1RA)tiU zg?&X<(Qa`a$!p0lX_<7X49m93#fqH@O1VjCSI$sB)Yo9ZX&*XYmcpX<~0rFyg8qo?&gy;onY&(o*sALx(iSLlc78|z8^Pu(fq z99>XyX_ji@G|B44YO(sRs)wpTxk5=Pk1L`TcjV3FFJ$dxZ>1fiFC?uc zcg6L^XGLn!P9Y;m5)|?K^I!8C@c!Wnxl1_})IjPrS)cre;1ergH5dxM;MVvKibBU& zDZ7Kg^jbO?ToFWpwE-%y$FK2U@ip;%^p5u0J-a=1J!$T_Zk79`Yql%ImG0c`?Bpb! zuN^xagB{@xkNu7Pl6{4JqCLUh&~C5`?3CSWqitRru=DMDdu@AyeT035{e=Cs-DEd7 z`a3o_UN{&>Tjv&Mnp5vea{cErxaYYu-SM829^g&#=6U=3-uc@3ANZRG?gm-}p9MS7 zsq|2$lv&7<=n!gzU*XZf4tBx1#Cu{g$&%-(www~qDsCk2HSaGzDL5lY5Ecv9i^9b( z#G@o0$sTC~*-P12d9{4ALZ-Z^Y@~Xr>Z<;x9-#TD8K(WN9iV%y>!`o14>fEym<_{> z_l=5>IU#RCw4swiZ-sh8TZOF%yB}5+78>3=d{Owx@aN&b!=2$ogeoF3A||4CL~MjE zLL1==cZPooe-yqwd`5Vya6$Oju;XFF!?a;vLN|mq4$TYM7!n=w&N#~GGHfs?4M+8n z`g6Jv-D#~zyIJEEy%L8cKio<0!#81VTU`^Wo^d%t+No=)x+u18LXqq$>& z{jtq&O|)*eWSgVSOHH3@LTi>+XH+$*I#lVa7+>+Zylwg2vZ%82rTWsdCACVf7k4gB zE}B_H7oINcR9IPXupqI(m;WF?DZhEXKQATkNZ$OsZh7_cgn6FavfLlJ-*QuOQ*wXg z=H}Mq5_$T(mU(0HHssyNE6S7Q56s`5|21D#FtOlH0jF?u;gdpr(dwegqJhP4i<_6+ zDydU?skCm{&9WBdZ_4{ulvFIK6jfcRN~kWb-c(b|^x3q?Y_z0W7FnZhS+))Kb`F!{ ztaG$W=uUMn_q6kxyf=O0{h@)(z}{eY8q<%M`D`4@MSF2q-~pH62!bO2CC5+{=N4xG z*ULS~YtH}5pDqBxL&933hoTPRRB=B^x@3?vMcP&NR8~iRLJk$P6kik#lz%Jplr2^3 zRNquO^+@$;^>?*O(?PRHb6oRYQ>G!c;o7F!-r51$N!kh8Y1&EJ(b~b<&f3OWotD<* zYo2NLYi4U&X@r`u>J#eGYJ)mkwM*4QL2bb)ekA_@PsQ8JrMOEt4r&UOM-Cy=h>pY)*brU;MsNa) z@h(K7^(fyr9 zyUw}Jy3Pv69Y>NQ!69(u+8@}r+Gp4k?e*1(yYFh3UeHBD-j-ST4CJX()Xn?IBB+4U=cdM=0_XBa|7+L8|wv_UhZ}P|a?Q zTQg4kP8*@ysLRzg)o<7T($_L9Fg!5$46Td{jMt4hMrlaXkVzqHLe7T#7xFEnBE%KK zgm6O1P%<zKJPSAP5sNnd(Re!0ksc*LTg(v9l>E7?k zb~bP>bfnld_Bpm!R=stRCC%K}yvyXM8CmnPx+GFjP$ z(l(`^OXik{N^TdAC?<;^6iqLREXprDRXDYzj#!)8e%sdCqa10D zh0ZY72iIbEs3+aC)Eno^^lk7r2^0i&20PG|^bsb3wX!ErS8T-xK^Is6cM^5TbaDVSiD&XrMS%+(Yu8Bwl(!DweI5707zXFUTpy7{y%$ zr5va{tt?U2Q%zA_R^_M^>dxvU^&$0hb*?&~R%+^KT5Gy#hG+(BMr(#^25Ne0T5D=+ zv>KnfSp73;*4U10?Tj62g`%9ld_Jo9O(+F zN_t(=Rgxi|E%u527O6#-gbjpG1ziQn{K5Q8-WXm!cN({xGmB%VW>bE0AsHan5-i*d z1#kz@fx|coUqy}3E4CZ^iRjNQ>YVDF;+)}J;9TL{ z?mXeVxwJGC2)6kFLOV2d)@6ln>?wW5breabFbVt%lFz>%fH57 z?(ZJB8!!Yn1%1K4=xn+N^NMN7-enu0%P1CK#4+G1s10wy#>5k%4f&qzLH(dca*8=K zxK8d$9>L$imkW*w!i1NEwMDl@jl~bdO(hQ`O{Djv^<{UCmtwoqoq?$6I?{`ym@?OR|<&23bioQPQTwP{Jv(7Z(<1 z6{i-b7AF^{7ym3SEp`<1OQK5JmP{yFU2>}=ql8=9s&roI&C<%!hGp~0o|SRShnL?d zhZUnLo>u58*Ho5N_O5zb6<2+%T2!;8CQ!4=_!2ZtO%Tex_ z?-aUDx>~xE-P1h*&k=7E-$&mxKkGjcXc_z(oJ~{AX{IgvgPns2d;&KCAHY~x1J@G@ z@)X&SdQ5fUeBlh_=5R;y@^~Zo+5CQjG(kJzePNVnw}=)^5`PlclkAXGO1emoO3l)a zvTd?#nL$2Tenws-*D3}n)+rt+@)ZJQV`X3ELgg0aCFOl(s`8t%P+6j^R+cHtl{v~E z%D2k<%72twl(UsRl?|0pS**CN*sd6)h*H$ZZ_Ag+o5?-0Te4ZQFxgM(W@&3_k>r4+ zrKCu_SsW)$6U`9uMHht~h53T{0+HYhzd1jRH-=~DuH!1X=QzzcZ>T|35jl@!i9Ljo zxD8vubTA57@H#BRmr--{m7Tyc%s!?elR{6XN&0NCORyraBhWOE>)+&W>M!yg@Fn^f z?``jVZ=AQG*8jsFAJ`VC40I1(3<~KaI+O0eTw$c_2DX|Vhd!Z>_!f=^M}P=!fPOfes3Jy_ zzsbJT2dXpYUrrP5Wo{_%C{MuO%=hr;2=WEPgl~jxMHfUm@p`dEJW}#nQdhc5>W~hV z-IJ;1GvulA5XDl(dxc&(S$R(xPU)0Cc6V);5a@Bd& zIF(wJqFkn|ul%N1t%y{-l24X1vg5LbviH)V(n`q!2`k+(F!AP7}^$N*HLMK}0Y9FMQ&CfNj3wEr%n!O1eI_UfE(qlL6aDvn zA--K+uV;qmm%FF?KUci#qBGoiz@c*Nw2SR~Y*O1nYl!u{rK#n)xxcy4w9F(iU9IU^ zQ&GLEx>@zFsvT7=s%k1vSB|JuR%TTksu)`lSK%msUw*iJetFOGI^}|LTiNfj_hk>t zu9uxDJ5_e9>}1)wvg>7!%Tmg6%iLw+@`mLD$`_aaQ=V4tEpJ#czT#9xW`&`0WaZh) z>dIzSo2oLZ>Q=9>&Z%x*bEw8&Gs5)26lXqShL-u3V#^3?iZ#*p)Yig&#~$yv>1gD< z<811B{ke5?Gz!2WfFO+YWpO#;hKbOChr^!>~>GEXx z2l+GkUHMu09{F1NM0sC%eYsd}mc5gmku8#SmuX}b(p%Cs()LoW^rK{}q^pFKJQgn! z$BGL?yG5NvHNw-vM4?A;RM1^e#^1+p!vD!z$FXKmnd{l;Iqvz_^TlKLNWHDRqr6+Z|9XqON?)RHh3|pS;%n%i;lJaz z`r8E71U?0{!AZdfL5dzl-==xYIOYkXVdt}%Y(sPiInhY`3`c{lzzRmd7qBj|p9m5& z$aJzDb&Zm7)^jR2L%FZGalFI4Aa6SV3%|ACgn%!cD*P^NC^{r^iH3>qi4~F=k`zgZ zbeS|&8ZJwcy^@LLBjgw4mGZ`ld5VV$i=vTon)0MFQwdc~R1;O3R1Z|&RbG`!-9+6> zJz1Tk-lqOreMWs+eOY})eNKHreMr4ey;412JxtwIT}v%cSE@d!PN^2Dda87)O66_k za%DRuQoK?uQ?yi6%FoIN$dT-(Y?zFZo|h&{%Osm6A(H3fe&Q<8GLcwxQdm#;P|!v2 zi9eM8llK>|kUO4R&Y8$5qyD1u$x&n`F_1`wUExd665Pgh@hKF7_Oc>&6GPKWX^khM?R@0uBpV>sD)= zHPf=h5@-2tUTOI)upPV zRhz3;S1qiXUzJofw`y6{imIJehpMhuJ*)azWvvodH>@5~y|ns5_19{qx_QmSnsYT- zH9FG>(=k(lskV8p`Hfj*`OEUJg=?K;ePY$vmf61BTHBA=v3;iFi=(~sqEqGC;Ig@< zxWBsldLDV2dM|oIeFuGF|HeQ6GHYOZup~H|&Y}k}DNGmk1=|eWMltvlmV>_m4Hm)* zIGFfMG$${U8fpz?p@wpvabmcexz*ghyvsZh|1bUMNlysJeCAs3O;v{ifF)ey8+92vCf}&T# zRl;UMtKghqv_L3$&0oNeg@-6iZ^wsxqedXRy-mBgN-X-2?-oD-hZ)0z)H`J^4O1(0##4Gn| zz2V+iZyRqX?W{Pzs2VY#t9w@c*1eQ+d@(_P;_2o7PS*^7N?6< zlF^crl441?biDM0G*c>-CCFCFuE=s^9C;J@X!&~iCHV(=nVe7<6|EHA6yp^$6sr^) z6}uD%6bBTC6nhmr6&n>R6f+cK72Om~6$S;87s_ADPs*3c`^sy{U9u0dgR)Vwy0R+i zZRrAOJ*i1@Rx(y1mAn@(632+sMJq%xqGaKGp;UNZFi2qK@8L)D-|~j>EZogpCHFd~ z4d)XznyMg|kbvAx=!o;M0el2HfE3&xXQ7FxlAXtTnRN`0*-z`~3&94#M}e+^FaFVf zlW&Dj==;ap#QWYe#^ZGFaMy9ac1?64=Q(E=XSHLGqqU>VzT4i(ZnvGajj~B>Y1Ylw zL@URdYT0KQVTrQ1%rDJH%+t+X%pqpKDbMuGbl$YxwAeJ+G}zSL)Xvn*)WlTZ)X3D# z)Y8<&)W+ zKlV=wBnKJ=_XdN((ezt7j`^FhF+C?!5koKuyawTL70iN7h`)(aq8+)9 zEF;@e+o(LM9%mWn9Y@R^#y!O?;@0I&;oaaB^J?)&^AGV;_(0HDFjcTe@SmVkAQ83{ z4i~Nx9v40qW(rM0N~9Cj6SWuh5{(f3C7LOkD_SU8Bw8q%FPbHqCK@doBI+z^C5jR$ zL`YaJ{33iPJSkrWrD$i2!Wsfo_~ZtmEV|;dGC3Lcq4hyybA7J z?mTWQF3owySe*uEHnW`R#%P!l`Vqa29z{2$3A!NoD7ZH`H`p^+Hz)|! z1kwZl22KVx29g3J1HA&x19bz2fHc4l(Efnm;dl70e!Ji0_xrJ*3Mc})K;uB;z<|Ke zz=FW$z=^=az}J8|zz^05_72Vp?hQT&<^~D65j~vVLf@r}XgSlFS;$;rau@~Mja|z= zWF2fAnu5-tTx7sQ@nQT8tHA)UAAAE!I0PPqnNUlNBu)@HL>M`iJWCdk(bOdB992w3 zamI1Za&kC2?nv%l?iVhX*NL}+cb8Yi3*ispZ{q*QFXx8|dJ2{cE(*R00)iM}Z()+~ zknpMSmoOmIi{eE?MN>puM2AGTMK49)L`5Q-C?MjBg<^wPC5{ps#1Y~!u~w`WbH$X{ zDKd+4L@A=Dq6?zEqGh7LM2VtUkx*1FOckCJt{09HHWBiK`GPxwO@bkU2!WITgujD7 zfN$g%@GkRa@fz_Q+h(nc0)<2$s*jW0o-SOc{Na9!|^X&%uqsu0b~NEU+rjDiH8L z^{?`G_EY{8-+te4UyRS>ed68d9p;VndOaUKCq45#-96DBpZmA_uKSQX$vx8D#2w?t zF1zce>z(Va>%429YpZLyYo2R{Yl3TxYpiRyYn*GOYm#e56d$`BDH@Q!^-?)q1q^FLjzh|-MwC9t@;fe8f_pb9^@#cCZzV^Q5zVp69 zpTghEzuEuT@AcOY%nV!zI{aM(WV7B-pXqb_JG zdV?VDgjeI|IDngiMc^8!22pSYmyy@VUnHMuPEDb9Q_rbV zO3G=%8P8e8xx#tRso`+B^|=Y$Y220E3c*A(Tch;;N=G-)W;6+PL`KB0KiTK( zVRj`uitWHgvmCaX`Of^yTx2#g3z;!YPo@PE&Bz(RIO!7l7o9@CrXSHa=u7k|`UHKL zK0xoK_tAUk{qzC)D1Ds1KwqNo(U0l(^k+JkuAqH1#psyYOk1WOGl5yo>|!o4&zYZ$ zl@YRWYy$fiyM{f^K4-JpARCU_p~+|ix`93+7gFMmcs%|a-^9N#jpINcuoRpGAAt>M zVHY?Z9)wR}1r!jiiE+dh;vSJfK(ZFupIk&9Bj1r_q?Bq%4X2h;r>Xy_Vv68Ia}qg| zI2$P)ki`03wxSPV!N<%wt{)g z>|iD^@r;1Ur~jq5(-Y~Iw4AOEz73uXt_TheHVKM@ra)TYYG8L@c3@zjc|a9F{vv;x z|BnB-f2)6vf1Pz;$_C4`E^xgB_^4;-W_ucf}@!jz~ z@;&jr^}X|D`o8*Oo)8(Bu$Ns5wF5mW=J4b_zzNDZf^ zP*bTz)DmhnwUJs!?WQ(Ud#G*H-_#CjBek7cMXjcmP_wDo)EH_E)tl-@wWjJ*F_egRL;gi}C0mlAq=0k~xx_o-4snQBOH3pB63vMaf*>m4S9l+u zf@|PR*b6p?Du_YR9}nvwSPI60E}%A002j{1@9;%@2rtBwZ~|_E)fnPx^aH&@7tvv~ z63s%xP-hg6!jKRJ*(x@N{lxyqUT4p-``NAR3O0$I&JJgXvOU<&Y>Q7+5PM>_9pv)O=dIMa@Nao zkrvfQ?NEO-87)S;&>8d)eL}^^hr~Dzx52~k9J~>q!4GgcuE7|GgQj2*m<~3B)8H}4 z02aW7F|ZvR4i~_k@Dh9pv!Me@i5Q|2F`SrBY$eVS4~cI?1;G#+GLB3j2a&VMRpdeP zJo$u7BeO{d86@RYDAkB+OZB0KQR;+6b%{Db?W49+E2u@(L~1nEgK9_Br$Q((6(mh$ zCi#iHMV=$KlFP_3WM8ro8BRjdLVPEl5$B1m#9U$s(VU1N08s{0;0?GJE`~#3Td0Eo z=79I$G}sCzg5Dqw2!IuT!uRnmybuq<&9DZ0P&Rss&Z1Rl8cIM7kpMZ_AM6wM6uX|C z&h}xOutt_;DwuD~zsz}N3$utB!*pkwF`t{Abpy?PrstS(#LQxrGY6T=%zsP_PS-`OXxq8gX~C(V{m6Y3eUs)@HzYzXJQxTg9y+8 z3;=V%dhidp4N^fK@Bkr>>^l=ZWjYBjOd2N_-~@hyuby*a#0n6M!U1At@l`q=ZzH3Q|ofNhPTurKFtXlYEjQ zS%M|pgq^4%iiv#U2l177MZ6&H5Z8zk#35oUv6`4qOeRJWy@^glBcc|eBm{&Tn&5Bv z89swI;Yqjyu7=mI*0_M%;AJz9+xpm}IA znt;Zj!DtZbi@Kqnr~^twtx+e`8ns33Q7hCQwL=|H0!l#LQE$`}4Ml^|STqSuLi5l( zv=*&Jd(b}g54wVGp#RV-^cDR^rO1hVNQjj<8aKvmaUVPk&%_Jx7Q7Fi$9M26{0--0 zJ7%#QgoAj{5exzo!6L9890upXWAF}SflA;50;q>|U_00Yj)l|UD!3h@f za1QJRE5S4{6m$d)ffi7}hV$`9{0N`I`|vV61rNdPaeb`A1a_l5^cg+*^GdHp3(y!e z0JTC5kOqm7o2_AgvZ?G-_8NPd-N|lb=d)AUQEXqf6Wfff&4#mTmd66t!&sS8CZEY* zzA_(}*UW#+eda!Mjk(I4XU;OGn17g~%n9ZobAma*oL~+!$Cwk$N#-nbmbt`SV{R}H zn1{>@<}H)Vd}Dqxg-jV^XIu=y@>mrc#>TSE**0unb^tq>oyo3bx3UM=bL=hl8T*ON zVM|ya3y}&%qGqTg8j8lE#b^yWh|Zw9=ncw1MaYXV(%@L!5_iYL@eI5S|Ba90>-Y)& zgmZ8e4qzeBfx4h2=mCa;DPSSk2=;>0;0AaCK7tHT1k4}+C@6=aur_Q4+y3!5$G}N2 z2`+$};Cgrv9)PFdS$GrPfKT9K_!ho~Y48*L0l&jcm<@lye3%0Z{*VugU^Xm-*)SXa zhCg5?{0!6Kd-wsqhEL&Bcmv*rXW&J62p)ku;1;+FE`dpK8XO0Q!M?C7Yz>>kS}+PK zp%?<_23AlAvOyYn2Ofdj;4C->c7gR^DVPPugW;e%=n9&EdLRra0Uxl~i7Rmd{*F`e z3;Y1z#Ha9a{5Rf=SKx(sI-Y=s;Q=@icg8JoBU}$h;83i>BFx7KvB-m5$b_m;2`Wap z=r{U_zM>x}6@5X;C=IcP=gX`jW+zxlfJ@8;W98bg3@KU@AZ^8TVVSEW+!;kP=oPvMiB3y}mf4pf8 z2nF>)bI=*|2E)JvFb6CJo4`(R9GnNYz+>mJ&$c6%l!V+x6(KsHr#@%q=KL+?h zyc}=CyYU%(9zVd(@JF11b8#hh;vnV&IS2u9pfP9*x`KXS1egG3gN0x%*a-IhPgmyw zKUY<4|23o+LJ|lhln{E64$`DYkRnnQ3p^1k_JaQ9J+WXxs)$l-PeExa3W7?n(rf57 zA)$p52np$7*8lU}YmO7&Z$5YC&bjT}v-eti?R{=0I;XIGiLTa-x=mB`h@R0)nyI;3 zq_;JwGrTjZvvOyAXHsX)&L*7=I@@$M@9faozOzeb=guCT$@Oox_v!r$?gKjSt$)+a zC;y&%zs^4OFTQv0Ozym^vukJD&UT$GI-7Sk?QGClr?XaPLT5r}h0f^Cpw6Jq5-rvo z&C*MHR!`|6J)k>ugRa-5x=iQmT%Dtz=_Gw$$LVYOl0KoM^Z^~HeYA&m($?Bc>u8e3 zX|#r_qotnX*ZsVw`!PT0d;Blod;iks_@_R_C;0pRwvY8O{))fg&-hb5 z(jWDQ{UIOfLwu0m>;3&6@8i9^rzd+4Pxh{Uw|DjK-ov|ja{Vj($$pRb_TJv#`}trW z>_h!Qf5;#8QU17(_Gjzd-}15kp?~O8eVTvab9}zf^<}=q*Z3OW?%VyaAM|uj_iLW* zh3#W&Xqq9N%tNg7x+jO?Ce~~}Avqxv2&fcBbO!w|Kja&IvoG~ueXf7&pZHXN*WdD&{W*WU?6t4o z?OnX3xAeMR%PV_D5BDH8?LOU|+MUvUsC!rU*6uCcYr5BTFYjK~y`+0l z_rmV)y61I&+5J`btnQiJGrFgDPw$@6J*9hc_r&fg-5+&N?Ea{GQul|oeoFVG?kU~V zx~JBDXLir(p40tx_cz^hyMO3j)V;X-x9+9gE4x>AZ|vUOy{mg?_u=lt-KV=Vx-+`7 zyRUT@cNcaCdyvO^j92$0Z|F_Dop z^rN2U7yP>4^b#+pAsVffG*N5m9okeoXa~Jp@6iD|R3Fxn`lOE5mvxN(Ti?+S^kbc> z({;9fq2KB}ov%OY5?!Ld>vCPKYjnMC)=j!yx9Tq4rF(R@9?(5{Q1|H}-LHobfvD)-*k!osta_Heyel!E1j*gbgE9(i8@}# z>sTGDujtD_7WrpXUpFj?eYae71k)Q+>LBT=D!z{+_?*<9wXI$LTozP(RWs`l(LWuXK*i(|Ni;f7ZqN zhc4AMy0SR(Hr=edbf@mqeR@z2=@C7mC-kJA(o{XIshXju^n#wzbj{FIJzp(THBHZG znx52DJ*p@5upTW--&6U|y}C)a>Uv$TEAivq!Djw_69_{5l%ERh4$U{8P13kzc5At&L8sLGo4Duik^$nYu8-;C`iwrW&*~U`Nyq9K{g1w_Z|nQ|o_?qw>&H4- zC+jCVO+VF7b%uVfpX*GWS=Kwd-hZK=7e3F@Svo^!)?Po=Pjsq&Qhf0vouuP+g1)66 z=vaMQ-_$qs4Sh*pE&u$qKA}(O2pz7E=mR=L2kE`qx8m_0+C|%IM{TZmYGZ9$cd|xd zdc0Q9N*bnNrM?Ggffsv$-z;>`tltBg=IMUIPx~Q1=KFoW@AVzN)Bp0ne6z3f4Zg-# z`6~azfA%$NJGzSMs%&$-kW`VwE_7UV+IFcg_GSKS9eG*t z>OXv~uk!W2(Kq-u-|V}5oA2{Ip5ll6h@bQme%90coM-w)zwXyO&vU)dOTEMcG(f{O zOk*{wEVi1~(j;x9^|hHc(>B^pJL+B9UAt>;3we4%er2ls>Od>&yC* zzNT;JSp7eJSI6o5HQp!aWSvrc{S%$3GxQ6csbA`B{ifLamxaOK>$m!y&MP+moqk{I z=N9MwM(36;_@#bTN1dZH^>dw}pXhX*s*`k*PS6kZ1ARxw>3{0VU)5LiMSWIB>tp() z4%ZR&3rGj)ecHD&rd_mC{W8>++Eg28eXXfAG`@ZZX_SU&ke1WiUgY_H-Ea6s&-7G3 z>&N`4AM{(C;G?!fxlZ3{hR)8f7QqMi~fQ? z@6T5DbCi$r$9#kj_lNxvALbAG{r-Rt^}&^&z2EQiK|at2_#hu#>j(J&AL;{rNa;BC zJ!+jBhMOT5G#4bUJhuVGqAqqT}A zXf;hNP6XW>Yb$M`?etFVtR1wQ_RwzHTkqC>+E4rI03BFpC!W7w|5dT!L;9GG&{2i{ zkCh6>mp)lo{IrhJr}gpr_HiAhPwBt(Ngb&pb(D_K5%rr{*tY$~)gd~t*qUo`&E2)9 zcGYg$xjc7kZL6)dg*MeD+E5#5U9G3pwU$=Zs#--WX+@3J3K~&Z9$3Fs28S*50>4#= zp5f3xvDfL@?op15AzR}kd;;;8L zzOJ{hc9n1N)xN>k`UYR;8|!tGZz)gX95BZ1zR$P&LEr6%eZL>|Lw>Si@-u$g&v~j} z@^gOGFL{<C91xNHM$T+F;>jnQ^` zmv*QuWH0Te_h@hJtNpc~4${H(J7!q#FdeQB>!W44|0=6}QlHkRDrz2G$orf=r_a~x z3;L2ir!VUB^-E`I{j9#AqxCs`Mn~(@`iwr=voW?sM{#zp@?MB73;mksc&1;e-)WoX zspaR7cuM`|**(71cldhW>T7&$Plx@@fAL@ZM_=so{RjWv=lXvOLBH_XKFdEV#Q)T% z`81#GlYOF3@{fFyzvmN6Wudf)`p5gb{+^Hb_xzp8(Z28R_znoljo2V&3mIX=t3_1Qkxzpg&OIe+nm{+loH<-Xik7VqO>H~Lop%lG%kTPY=eV{R|)t@mi3V%+_;xAxP%wY;~ufOWR* zr}t<-?WOmY8knqowMQ}ZWKGtt+Fd)`E#D( zsh;kqJk3w~89!d}^D#f+N9*-);gB&VpX2>OPpR(@)%R?9)KmPpAN1qJ6i@guKUL?M z=Eps~cw~mB`#Hbh=lzmj_DsL#m;Huc^;>@3WQ_B?(2Kpu-O7@%)&LC^S@m#@uHOV) zLE{S5`2Lz&U6Zs<{W{=!Ww*_>v9{1=g_do#jds-bdY5+6PCdKrrCqhR_Uu_~Z|$#r zw7=e`_v+w^^F(^|5L)=q`Ypi^=wQ9S)_Ldm1`n+2MHu)Hy-$afw%bqdt8bAEn3 zaSu(d>!t_WY6rbj+h}WTSvK8N8)-vrpmnvrR@XY3poyhM#%V>3(ehfMY&lHQ5OvFv z3%#hU$S*K5YADbb{G6v3zR9j0t?{4YDZblNd|TB|ZuiZ;rBu?j^}5nm`yc*?ukhvm zTPdnb{1^YJmOuJWzQBJh4R*dS@*jMGFRa|`5B`JCulP?J*&3f0_kJHQK>al z+ois!swC)Uu7TqFXI=Gr|I;`78sFlZe6w%!t-jND`OcnhL%BU%e)Dv(JwA!Apf6wb ztCc;^_FFwa!Ly}q{URcIZcu3&ykv~VRFs>bm5cpX)g-N^wY66H%KANDd51RE#@bBp zDAl)F#q(_{rf*Sv3j%ps2&T<26PTs*1*Mf39Adm7Hqr(lP6nqFKL`&8Ag7*-V>jtIAlm zD9oeZu^CZwhstKQ*G{F2cBs!?w1alhj>QV^(z}XtchPp*xwv9SZKs{;S4LxLexr0- zZB?GJl{V|Cs!e*{!>^L^+obE1Mc33st*+Izs#ewnjVm73cJI%`Cq=xH&sS{ zvv2bCzQNb~x9_N#lZD7I@W%evM# z`G!(!iM;=+tLFOm_-^0l`+UD2=*0v2`m`VQGnFe7lc)N{;(a1B%nyfg*KZc5E%sYp z>P24auEDef_Yc&t@{y4msxca&F=WA+1P1MGk zq>amG*4KtwU+>U5+Fa{YHU#=Nu9{{0d`DH<*RDPIwOsZGvm2BuSWD|`-Lmw$T0`q; zq9*nt5SanLVT-yZyINT*l`cbJVE;tsROWCuKT8fyugdR$aB2J zv%Sc(y}++~enm*UV|HaIGyO*W@-BFL(XZ9-@V@AmDyBZ?mn-L`el@+8nWct^xX+be z&8+>wEEx~K;TwCr=vjWH(EC~;_%*-j*?!Y={ifgY953`+#W@SS#EZPdZ+nU9xjjJS zO3T$<<^D!!xJGD{MpmXVrmCH&ar8KP3!9DCM6IStRRx`>b+x9})4CN6*Dq^r@Ne5} zQh3{}l>6q|L|bSRZKX|X?f~SIiLo%3uw}KNmC;ThmOVDD`Xt9~QdtrB#loBPd}0Hw zr}c_qn2T6j6N{fwSjiNjKjEjUM`*XFD{egJX@1GmOLe^D7t8zJ@GBL2-tgS=RM3p4EcIKaw$Sz4hVQAN z!uo@X^@kSk57)@jbt5b0QC}LZ6-)oDT%JPwUPI%wrpA{oCRQY0RqIqX3;u|s;GJbH ztzBwxO|9Gegq72pJ=lw5(E1#WdgRxoSL?mt61;O|jn_&Qk@*d3Vlo~KnpdbQ@KCLw zp%tBn6_$tAT@lgvwePUIwA55gU09(G9`&}Mr*o++&+**exKUS~UFe?Um&;RL^&6h) z*Zp$k;^<@)&@1)%O7Z7Q)k@1NwFS?bRSE!ZMJ*ADqYKD`(EhpN{IU{yn_u%@T=kBH zg=(r}*z4`emB=6li&-sZEE27`@1a%S2i=UwXpQcT5x=iJL1Q&RD{6JEq}4T{P(HqD z4`kt}9>$+}f^{`f>y>@h)mnx8wY7dlQ{LCFZ(&~Qsl?a~HAx$4t@>PFYZYSG`M084 zv(Ai#Cu)s~!^GlMG_iVu!osV@)$9a11EodHcBDpWsFp9K#V?!>EkEV)7vfMx13Ti+ z88Iq}b1HI?W0S$mDqFo!h=7S__(jj~j6(MeKjUdtLqSpH1-c&dW4)TnBb8k}?g#ut zRaYMM1BH7sE$S^nCk((E*%xc9lg~#JP_qkXprGLo>RMDmqwu2N!ZpuU-)21)7R1oi(B@NaZsOv7#nw#d7G&8dZsW>rbSdIdm_Jj zwq_(A_cTAztB5>S>#)KzwSZCP9-s8HJ-l)h3o{wyZ(MU z_?6Ocv+9`y{0-K}XYiGUp6mJbLS<4JVg$%V_=E3UxfvePsmwFH&us1}4X!aA)l+tG zCnG+p7afCb@D1KTH}CN6ivNxZs6W2({}&J=#%eXK*sFS=oWX6dJH9l2G$T=%`Eueg zwQF#Uy+QJ*%J+#x=*{7^pgx(W8liz2spT}Ra*4r}MRQ*$@5E{RfM-s+g%YqrR>sIM zK6nLHaq7#_a-fVCsxA?nPp}Ts-z>gtYh*UmwcrBq${Je!wbJzX+3a2AfMw3k zJ2^90p5MbWeo6*SWsLZY|AK2WN$v$6rk2nDZ^42l$^WK7=E;bRtVqZE=*pz9ADSi= z2iTEXa#)#~V%Qn0fb(&sg2`~Ak-*{_y;+g0B?DrHoV`}-EgXy0V{dGaPjGgu%{$iQ ziGguFPr|0~4=;L)${tZw{a_gd33q{K?i|$(v*E|&B+O(it+AU|`UXZ`P;rcChIObn z!mqEFNxsshZ-Q@f0%Xg+1lH z*qwjZTlmB8;lSo0WY;X@*r+TdF)kJ65ocaJ61@{D=L4vCyh-M?| zQS)aB%P;cV^?%i5oWM0qxUga;xSn5?#rd97-U7zqN-)|YZPxe}U6fcsJ+kpZV+Ur& z7kjoBdrnpAz;ZGGScNA)$)JO2I46k4kHbjZ%Rs$d>N`x_DRo6Pa8TJR^`zk%q7n7N zVpI)LTO(^>b{lk$)GGBq#)O&SP}r3D{PFcUwx^59&BNDlcGh_hBctk9>eV(P z7-UGRXp|<@mAE3hf&agU$iU}Nt)w9uQ_s*4mxq*ni7CT1P$RUQhF7#%uE);UGSyDT zV^J+28#}_ySa5DtjL0;bm1cWhsU)H{>NWEm=&DzGc@#+J`IT9Isq&^53+ZIzL45G{ zykDv633cZe>lJh|Ki=B1XKF5~yJWtJDkv2h=H1BLW_orl$q`U@=)gBTrxxM`QG*)K z62DPwg-6aWA0hg{V~I(LQB;fGuIdab+cVwlVTby;>>E z%z`3=1=0=^5bse|EU@^fo*D>>VfSdPmMO=!@g(|2=7e2Aba)B97>1^L8^w(-L8TC> zv0#?uCX8A<9aP6sj64=d?JRYYWKw7wE*HFhS;*sAsXBy{We({?bYuCO4+mMP(+H6|PQ14w}(;Z`89i3oB|67x0?+ z4}RJ3%qNwxC~xZByrSg5G&v^>NUk!dr|HP8S)%L6QyF!Tgw|cwS9oT8NJWAcP5Deb z5AvHY@j0wAbfRw*SN1~zz!SujXaf8(S0YawRDL?5K2Z>8&GZdzMwKuAJ-DYh&>qx@ z@c`!RSjaXOl|o6h#vj7*Z+n5Ixt939CI=6RCXPBLhG&Gqb7C!+1?}LTCqwwo z65NAi+L%4#*tR4CBx?rG@JxTimPSL#AfPZ=&eCDFT$;$Aa=7m&#qO`;M+&LqVye$3!mZMI=a?{>q*7yi9 z4-a9aQFeKudfSrdjW)+VV>C#kYu=$L^yIq@-F>_VyU~RGtQc$)lamjEYo3%sK_&wr zTOQQYWI;7tPX3%8M|I-^LrQ;;eG%F4e_h|#>*BkK((o_r#F9E0 z8ob#JRCE8##>2sk3=t0|%(yMDfu*@d7slaau+_H|Y#N@$UdeZsuMCTrjus=PlXHPv z@CsV_Y}*=dv!&JP_)I+>#?Cd9F{2^ysHiM_EVUf^ojXxIU4jmQtrB&(f5riJCQ_3{ zQEi~|u*mlOTF_gJH7tupLK(rRWILdm*q5pXRgR|1iR9$3vkL3MUFN-+|Df{8lLM_b z%DU@on;%E3BUb&#=V3RfC zCyOj`-&B5ihxrFqUcxA~$_vOw?Ziu{!lb$yAIZ}x(Y(a%L4|OT2mbINT3BakFHqXl zdw)G`9GPdalO1P0SK#@tRI*z&o4&v}%u?dNFgaFdp~olG}rlsFkolbytLvH~7KtOW*ClLcLi>Jl!;)SULVo&Ze zmc(PhbvO@PCssvqF`jwBo8eOIl1wQYD)|xdFuEL!Cw@~Sq5er6PYpCtlqwZVp7F>x zW-)@*wieuG&-PArg)_k$T!po;Npbw}a_KHZTApf{R4IyfYF!owd*d zG`~I>Etpf08u}>6NeyRM978JvP;$-(Q z43USvk^i$KZ%Axvs=Hft5bl}#O~wft$U#ykjO`Nj5&_ZUU>X|?C@aMV;nv2r;bF8< zxVax&iL*_)gI|`kprg@f=r_*Bktl&!nKLF+M&X73v1_ZtVpTLD{0&o6^TVpJbQUTj zRDLp(mpBcxQE^~q01Gl^M65h}fC@?#3Fd6tH8m#Uc4iug+hj?gn;3~gNB^d}4C;vD z=;EmLsCK@C`^+!V2D+(cEhr9POIV^m(?NALPd2r=D6Wgf4#SYC1-^%yKeG++)1v=UiCAu$_^ z4k*1GtTzphU84*8cI7NAFa>7?yG=vpN?Z|iqpacY%suye8%~GO@%!j)5F9MLUC)IM zEKUQ#ctF!_$;pzJk*9z;(1;pmo$b9E3B5sGz2RQZL7L zFexm{8rz#TroAz7bQmfg{1ST`cegyLDL1Um()2$$K(5;2Lh3cCg<#KSTUd#ENR1O~ zwtNG-4G{l2t=W)x9rc}@rSJE#7wpM1Dc#<)8PqfK93_>HMDJ8XsDXelaE3NV52My; z%lt(?c_-s-aTdi6Ua4p?H%?uSZ8=BQa$dNDS`0XDRZ|o;2u@UnYlzJF5nP1UNCb(W zfMs|Kuf#)Ih6eX%ZW{iNf53V82T{6dIo6tQ5CN#cfV36`$lF-Q7U5Z{8lVy@rVdSH zNStLG(E*Ra((Dliz=NpOs!*M{g5M<~bNR51%UxeNB z^bz+9&cO-uc^Q>39vA}Ij2pGIj2+{b>LQ=1Ul9xN8BhvV!96YPk!aj18K4xk6ov?X z;gHyf_!7(T9Tf8|x{~+UiOeBuEvLrMlV3JG!@xXUX7?FySuSQ28Bvyueyqm&vUUj) zu~OnGcy4N$mWE+kK{f21y@TzBW6m4q4m)7)R?~#fTMdjT5wB`BKCEmHE%kwV#2>-* zAT9CWvSoBjEE$cJ%sN$(#*TP>tDGffllOpcGAA-qB4YBTe&v#gp1Ck0Coz=_yg$Pc zH1=zttfeLjQd6O3pX>*gS&yDhr4$B%O~?zV$MPH;Nai!~0yM+|o&w1Y`Pdq-0^#Z* z8LQIL>>D+Zt8hlH+OC`^)L5Jzw(1EvWxES9X!5Q>6`x_TfxX%I_(ODUaLtG?E+8EA zWo`iUMS~~PW}hfK3A_fWU_Dxoy&8U7W*h`_T#m*%;n{Eo*$^=~c@Z@`azt9_U+ZId zx1pN+foR2@areGALY#Y9V@SqH(L=5L!%d)U=<~^FS0-cGz zVe4#7o&-;{CG5c&Gdsa?ct(p1txxn1mS&-sWD=;w*b`5Ly_VL3UEA}3ShdxB6RBIB z6WhdE#JI#cYyg753-h}33Q5#8+AH%BiJ8PsVtD2{S{x5j!D*ZM0LAPH6TE4DKWE9m zXFhUN|2(WM_1t2WWCPI*sk^pJ4c0-~1=a0N5(DCy%*ik8Re0MR{gU!;e$A<$pCq#m zCKBfu(=a61&I{JWQ(}#HNxlVzSd4knA+UKv(F zb*H9{)tlC&Z(tT~Vr_7FJe-b31H;LwlTG6ZC^yg$1O-2N28TEuwuk?h)#Pv^TWE#< zoBqjYH>?KD;5*ewFb|%I7ksvbwRW9Ud5Ftbo0O@$hbP9(Z|vDf8ewvl?Ve zsW5|k)~SBBN)-`)j=P>yoRE4bXl^k#dy^Ljzp0!CziAd&(I~5YH+}0YKM`NZwgw4Y~ymL3O5=_ti!SXOx)HYc) zztxm+05SbqR5VQDCd!-mji$jXS}etH`jH$qjT&m>-*693iN$zTAJ2H#vaAtXaYYv1 z**ldV{B?0X0g0vZ7khDUxj!tEXw9920Y(JdwaQr#-DU(pejh;@*S;DG;$k1{97Tn# zT1*cYM`yKJvt(dl?aV6T2Yd(F?d*KRuH*%@f@aq6HP+}^Y#aZ>c37}k5|88#X%8o3 zQ^qKBv#c|+d?sdxHN&|5yeD|(J#~-FMxcW893fe4<}ETa0bsa*7Zszqav95i$GoR4_lzb02CLeqj}@KAb|dJt^P_xN*W zvbaAmjOWMq`GyU!NHP+bFA+S<42pwmkljWei_o6<3BR`Wv;>{(jisoI5Y@34ZK)@F!_$d7(n%#JXni*vzz~koL~qqXca1WkFjK=!E(c4VmB>~JA>Ec`jupPEVnS*Dr7@YGCiuul-Se3P~c#h>vX~X`Fo3ULi$aZ>#-(Y!q!@_&iHtW35 zTihKB^@AW8>ocC;^h1>>2^NbNId zZkjn5ZWXN-bJ?Gqm@|P_uuU7s=IZcCmT(Du<9mFHzO_0ZDloAEABvad{_q>ts4n0W zctw_s89ovXhyU}97YOIG@jq4o+Fi6MF*p7B=8q6}vAQ(jQ0_|8X+6io9tA^R^6*dogv&XV$ z<5=QAyHXHN|5`ujS*!?457E-9Y)5V2fx&g|6$|w-&PcJqZHs$zlvpE{hy}44F)(u& zAUVpZAtrSW5R_R9qU5ruLk)v9(4ENIkQl_%9^?m=!Enop!DfF6n>6f(J2-RlF<5|o zlj+6N;(>Tlyd!Z2eaT%m9LH1gEo0K+Hke41&O5%4{EV??jDs#v)$D+!SZ|okC)SGf zv2-jNJF_072gSiRUXrNKUO8jz%z1d_I`PTYwyfH08721+l@{dz?zv<90RK;wDO|=V z^;2SFqX^j$@A~C~V30ToIO_&;4u1I?+?j zkKv#69t*}oyvKI=jMcDQVjk>9W`ZS}WumtJbKIMyh{;_KEnW>0o6SIYQ_sO>@SSzg z9wy*>A~12hSvH@1&vw>0f3qXsq7m^w`ik{(ml>IOYB)LA4yO4FC&oq@)n&OBw1knF zdrZw3jKb83h(SAw7Zkz*EHD83HM|9niNfrQs^-W~Y>=&MZIq(azIJkp7 z@Flk5MJ)8S#g4>>_!3bE?}-25NB9ibZupL`#HaBO{61sGJ7XJ7)M7x`GV>*jewa1L z!v<}Q&mbO~1))J@^QVT-hE}e{zF0RF?%Ni&Xh_D!oIO6Uti|bl?je|F)&tE(wn^4Z zv<|Yt33rw!7TB76EZB|8Wdt)~{TNCtZTuha;fRLSj5QVkwNXe!V=80~*PuRFZc#bt zjg453jX*5OO{R}kf?htGjkwF)52)>Hk&FrZvE)7Dmx!J9j5m3A-#*y2>EUHFE0i_W zi>A1+Gp|J1V3V2T@ImYdFB7k$;XpM zo}8En&UVfNQ{>8c3rnIit+{^GMDrK!15b>eOe~0h;bZ)9$9OZIkyrr#MWx1XVE3Sz z&x~c_GwWG`DEL2?i6=1T4ae*e%V9ly3~OTL#PS^37=V2^b2}<1=X>_&N_`EM>w;gd z#yfw}+1v@&!P4>mb~nusXa*#ER`GzUP{;ZL@6J*p>zB$C{uy*#!N^O}MCzj1K&>%c0Z+Ho|uoo;Fjm0|K6Q3K(gIaLORu-ZvIafc% z2e-j`d&(D#qxM;nA!q+wIh>L?W_khJaP}4rn$O^8^pJh<7w$cXj?b_?PkD2<_yPO1 z=#|lnw=iya4~Wh9hBNzP&h~f=wgxrq9lT<#X7|M1W;GC=bFfbzwzj8S-yQO0ik_6ZBevL#3ezw(FWusJvf&ESi77@DJkaK1N$ z6KC^c57yGs;yaiId*GFAQS-EM?r;XJY)xCc9`T^{;Xl`!&Zbu&H=fWU0r$ZDaaY`9 z-Pkg9F-C_r{GFJ`XkvdXoN2kntcT;V8-5%=;9I<#OeQFr(b8GW5d?Z#0epzo<=Zx3}FK(#juHwa<0sDo&noV-o zj0?xom)J4vmoWxMtY!4sgFf(%T{7OW1K(*OiZY+ka2P!nTX2@3AFMX+#&TixAeb%L zD_7}XDSgUS(--a~c4uF<#9PzONU$)%v{ON#&YdxD$d2_A(by6MF#hc+gocFJ1Us`| zB6ire#Zv4SEV54`Iq$(eDC32t6V0(9XK2xx^@i+R0}cs0z#h?W!FP_#^|>bf;y$A= zI5Yi@SGN0a_Z(HicJ|1Eha^5TlC2fbiC^T8k;NkYSb%rY8q71=@w9kEtdp3_k`LX!S}XsueoCo!^nX~ z&JwR^+#YWV-Z>&`{IQn3f=&J!8^HZsD{WkptMENXCGy02Y@;XfT>6T?V)y=W+%@+d zRR7<5jD2FAHa;0${usMHBlazpac67L$NBRuEuc9S6_Cw&8@^-Z*fF*b#{1d}i)X>2 z^p|7kMe~{dCv9nmms)JjF|klA+APL+HQr-Pn-ycJjAN|Cn8W9cJ4^6_)mlqKBG@D{ zV9!MESSlzCT7zFAZCcrb{W%Ap$=c&RiNis5t{kP`KVQ2R`EZmrXTjoJpZz#vr9LM*dIyj&;u`OG;R=d;Y19%8y#vKJ!j6M4` zAK(*CZ5o^X826U{aujWN!?JeZxFDCc<~^|vXJlIS*JwRsOM1(>=qD}QeR{_6v2asf$$DtbXf=yxyko&2x?uzxF~&hT zpP&ls^(!1v!`P&cT5Ji9gShDN;FIHN4LW0UwnwLDOO9$s^Nq996P{4WwbP><&vv+n zk-?`L%3JT-9n Date: Sat, 2 Sep 2017 21:23:13 +1200 Subject: [PATCH 271/504] Move hover haptic pulses to dominant hand --- scripts/vr-edit/modules/createPalette.js | 10 ++++------ scripts/vr-edit/modules/toolsMenu.js | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index d682ad335a..8e46783979 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -272,6 +272,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { NONE = -1, highlightedItem = NONE, wasTriggerClicked = false, + otherSide, // References. controlHand; @@ -284,6 +285,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { function setHand(hand) { // Assumes UI is not displaying. side = hand; + otherSide = (side + 1) % 2; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; paletteLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; @@ -291,10 +293,6 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); - function otherHand(side) { - return (side + 1) % 2; - } - function getOverlayIDs() { return [palettePanelOverlay, paletteHeaderHeadingOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); } @@ -334,7 +332,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Highlight and raise new item. if (itemIndex !== NONE && highlightedItem !== itemIndex) { - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { localPosition: UIT.dimensions.paletteItemButtonHoveredOffset, visible: true @@ -346,7 +344,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { isTriggerClicked = controlHand.triggerClicked(); if (highlightedItem !== NONE && isTriggerClicked && !wasTriggerClicked) { // Create entity. - Feedback.play(otherHand(side), Feedback.CREATE_ENTITY); + Feedback.play(otherSide, Feedback.CREATE_ENTITY); properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); properties.position = Vec3.sum(controlHand.palmPosition(), Vec3.multiplyQbyV(controlHand.orientation(), diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 20bfcefeec..ab5a5f74c1 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1876,6 +1876,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsHeadingURL, optionsHeadingScale, + otherSide, + // References. controlHand, @@ -1905,6 +1907,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOriginLocalPosition = PANEL_ORIGIN_POSITION_RIGHT_HAND; menuOriginLocalRotation = PANEL_ORIGIN_ROTATION_RIGHT_HAND; } + otherSide = (side + 1) % 2; } setHand(side); @@ -2803,7 +2806,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand("clearTool"); } else if (!isOptionsHeadingRaised) { // Hover heading. - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); Overlays.editOverlay(menuHeaderHeadingOverlay, { localPosition: Vec3.sum(MENU_HEADER_HEADING_PROPERTIES.localPosition, MENU_HEADER_HOVER_OFFSET), color: UIT.colors.greenHighlight, @@ -2959,7 +2962,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Hover new item. switch (highlightedElementType) { case "menuButton": - Feedback.play(side, Feedback.HOVER_MENU_ITEM); + Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); Overlays.editOverlay(menuHoverOverlays[highlightedItem], { localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, MENU_HOVER_DELTA), visible: true @@ -2970,7 +2973,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "button": if (intersectionEnabled[highlightedItem]) { - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { color: intersectionItems[highlightedItem].highlightColor !== undefined @@ -2982,7 +2985,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "toggleButton": if (intersectionEnabled[highlightedItem]) { - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { color: optionsToggles[intersectionItems[highlightedItem].id] @@ -2993,7 +2996,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; case "swatch": - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); localPosition = intersectionItems[highlightedItem].properties.localPosition; if (optionsSettings[intersectionItems[highlightedItem].id].value === "") { // Swatch is empty; highlight it with current color. @@ -3018,14 +3021,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "barSlider": case "imageSlider": case "colorCircle": - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); break; case "picklist": - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); if (!isPicklistOpen) { localPosition = intersectionItems[highlightedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[highlightedItem], { @@ -3039,7 +3042,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } break; case "picklistItem": - Feedback.play(side, Feedback.HOVER_BUTTON); + Feedback.play(otherSide, Feedback.HOVER_BUTTON); Overlays.editOverlay(intersectionOverlays[highlightedItem], { color: UIT.colors.greenHighlight }); From 1cbc807016ae1f9645545a2632689fb5b65adace Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 2 Sep 2017 21:23:30 +1200 Subject: [PATCH 272/504] Further haptic and audio tweaks --- scripts/vr-edit/modules/feedback.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index 3825efba57..231619f9c3 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -25,14 +25,14 @@ Feedback = (function () { ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")), FEEDBACK_PARAMETERS = { - DROP_TOOL: { sound: DROP_SOUND, volume: 0.5, haptic: 0.75 }, + DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, - CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.5, haptic: 0.2 }, - HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.09 }, // Tools menu. - HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.06 }, // Tools options and Create palette items. - EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.5, haptic: 0.6 }, + CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, + HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. + HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. + EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, From 2cd6f7fd540cc01c401a883f8884e3ddf97766df Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 4 Sep 2017 12:59:05 +1200 Subject: [PATCH 273/504] Fix cursor not working when change dominant hand --- scripts/vr-edit/vr-edit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c5e08e663e..af8fd94bcd 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -233,7 +233,8 @@ getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection; - function setHand(side) { + function setHand(newSide) { + side = newSide; toolIcon.setHand(otherHand(side)); toolsMenu.setHand(side); createPalette.setHand(side); From 310750fc0f2534196755a81f3034c146f7143919 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 4 Sep 2017 13:30:51 +1200 Subject: [PATCH 274/504] Close the app when the user changes avatars --- scripts/vr-edit/vr-edit.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index af8fd94bcd..fff9e9b576 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1614,6 +1614,17 @@ } } + function onSkeletonChanged() { + if (isAppActive) { + // Close the app because the new avatar may have different joint numbers meaning that the UI would be attached + // incorrectly. Let the user reopen the app because it can take some time for the new avatar to load. + isAppActive = false; + updateHandControllerGrab(); + button.editProperties({ isActive: false }); + stopApp(); + } + } + function setUp() { updateHandControllerGrab(); @@ -1653,8 +1664,9 @@ // Grouping object. grouping = new Grouping(); - // Settings changes. + // Changes. MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.skeletonChanged.connect(onSkeletonChanged); // Start main update loop. if (isAppActive) { @@ -1663,16 +1675,20 @@ } function tearDown() { + if (!tablet) { + return; + } + if (updateTimer) { Script.clearTimeout(updateTimer); } + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.skeletonChanged.disconnect(onSkeletonChanged); + isAppActive = false; updateHandControllerGrab(); - if (!tablet) { - return; - } if (button) { button.clicked.disconnect(onAppButtonClicked); From 61fc32714abdc2e99ecf8f29d74243d4e84199ef Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 12:23:36 +1200 Subject: [PATCH 275/504] Handle domain and permission changes; disable app icon appropriately --- scripts/vr-edit/modules/feedback.js | 20 ++++---- scripts/vr-edit/vr-edit.js | 79 ++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index 231619f9c3..843eebe07b 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -25,16 +25,17 @@ Feedback = (function () { ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")), FEEDBACK_PARAMETERS = { - DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, - DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, - SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. - CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, - CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, - HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. + DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, + DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, + SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. + CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, + CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, + HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 } , // Tools menu. HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. - EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, - APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, - APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } + EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, + APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, + APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, + GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, VOLUME_MULTPLIER = 0.5, // Resulting volume range should be within 0.0 - 1.0. @@ -67,6 +68,7 @@ Feedback = (function () { EQUIP_TOOL: "EQUIP_TOOL", APPLY_PROPERTY: "APPLY_PROPERTY", APPLY_ERROR: "APPLY_ERROR", + GENERAL_ERROR: "GENERAL_ERROR", play: play }; }()); diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index fff9e9b576..31920d668b 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -15,10 +15,13 @@ var APP_NAME = "VR EDIT", // TODO: App name. APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons. APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", + APP_ICON_DISABLED = "icons/tablet-icons/edit-disabled.svg", + ENABLED_CAPTION_COLOR_OVERRIDE = "", + DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. // Application state - isAppActive = false, + isAppActive, dominantHand, // Tool state @@ -65,6 +68,7 @@ updateTimer = null, tablet, button, + DOMAIN_CHANGED_MESSAGE = "Toolbar-DomainChanged", DEBUG = true; // TODO: Set false. @@ -1569,7 +1573,7 @@ function startApp() { ui.display(); - update(); + update(); // Start main update loop. } function stopApp() { @@ -1584,8 +1588,14 @@ toolSelected = TOOL_NONE; } + function onAppButtonClicked() { // Application tablet/toolbar button clicked. + if (!isAppActive && !(Entities.canRez() || Entities.canRezTmp())) { + Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + return; + } + isAppActive = !isAppActive; updateHandControllerGrab(); button.editProperties({ isActive: isAppActive }); @@ -1597,6 +1607,49 @@ } } + function onDomainChanged() { + // Fires when domain starts or domain changes; does not fire when domain stops. + var hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); + if (isAppActive && !hasRezPermissions) { + isAppActive = false; + updateHandControllerGrab(); + stopApp(); + } + button.editProperties({ + icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, + captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, + isActive: isAppActive + }); + } + + function onCanRezChanged() { + // canRez or canRezTmp changed. + var hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); + if (isAppActive && !hasRezPermissions) { + isAppActive = false; + updateHandControllerGrab(); + stopApp(); + } + button.editProperties({ + icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, + captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, + isActive: isAppActive + }); + } + + function onMessageReceived(channel) { + // Hacky but currently the only way of detecting server stopping or restarting. Also occurs if changing domains. + // TODO: Remove this when Window.domainChanged or other signal is emitted when you disconnect from a domain. + if (channel === DOMAIN_CHANGED_MESSAGE) { + // Happens a little while after server goes away. + if (isAppActive && !location.isConnected) { + // Interface deletes all overlays when domain connection is lost; restart app to work around this. + stopApp(); + startApp(); + } + } + } + function onDominantHandChanged(hand) { dominantHand = hand === "left" ? LEFT_HAND : RIGHT_HAND; @@ -1627,19 +1680,24 @@ function setUp() { - updateHandControllerGrab(); + var hasRezPermissions; tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!tablet) { + App.log("ERROR: Tablet not found! App not started."); return; } - // Settings values. + // Application state. + isAppActive = false; + updateHandControllerGrab(); dominantHand = MyAvatar.getDominantHand() === "left" ? LEFT_HAND : RIGHT_HAND; // Tablet/toolbar button. + hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); button = tablet.addButton({ - icon: APP_ICON_INACTIVE, + icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, + captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, activeIcon: APP_ICON_ACTIVE, text: APP_NAME, isActive: isAppActive @@ -1665,6 +1723,11 @@ grouping = new Grouping(); // Changes. + Window.domainChanged.connect(onDomainChanged); + Entities.canRezChanged.connect(onCanRezChanged); + Entities.canRezTmpChanged.connect(onCanRezChanged); + Messages.subscribe(DOMAIN_CHANGED_MESSAGE); + Messages.messageReceived.connect(onMessageReceived); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.skeletonChanged.connect(onSkeletonChanged); @@ -1683,13 +1746,17 @@ Script.clearTimeout(updateTimer); } + Window.domainChanged.disconnect(onDomainChanged); + Entities.canRezChanged.disconnect(onCanRezChanged); + Entities.canRezTmpChanged.disconnect(onCanRezChanged); + Messages.messageReceived.disconnect(onMessageReceived); + Messages.unsubscribe(DOMAIN_CHANGED_MESSAGE); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.skeletonChanged.disconnect(onSkeletonChanged); isAppActive = false; updateHandControllerGrab(); - if (button) { button.clicked.disconnect(onAppButtonClicked); tablet.removeButton(button); From 6838461b07d6a121c001add4221f6756acee5744 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 12:44:57 +1200 Subject: [PATCH 276/504] Display notification message when don't have rez permissions --- scripts/system/notifications.js | 13 +++++++++++++ scripts/vr-edit/vr-edit.js | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index b34630f3f8..7b178f53a2 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -85,6 +85,7 @@ var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; var lodTextID = false; + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications" var NotificationType = { UNKNOWN: 0, @@ -531,6 +532,13 @@ createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this } + function onMessageReceived(channel, message) { + if (channel === NOTIFICATIONS_MESSAGE_CHANNEL) { + message = JSON.parse(message); + createNotification(wordWrap(message.message), message.notificationType); + } + } + function onSnapshotTaken(pathStillSnapshot, notify) { if (notify) { var imageProperties = { @@ -623,6 +631,7 @@ Overlays.deleteOverlay(buttons[notificationIndex]); } Menu.removeMenu(MENU_NAME); + Messages.unsubscribe(NOTIFICATIONS_MESSAGE_CHANNEL); } function menuItemEvent(menuItem) { @@ -665,6 +674,10 @@ Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); + + Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); + Messages.messageReceived.connect(onMessageReceived); + setup(); }()); // END LOCAL_SCOPE diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 31920d668b..4bb70134b8 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1590,9 +1590,18 @@ function onAppButtonClicked() { + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications", + EDIT_ERROR = 4, // Per notifications.js. + INSUFFICIENT_PERMISSIONS_ERROR_MSG = + "You do not have the necessary permissions to edit on this domain."; // Same as edit.js. + // Application tablet/toolbar button clicked. if (!isAppActive && !(Entities.canRez() || Entities.canRezTmp())) { Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + Messages.sendLocalMessage(NOTIFICATIONS_MESSAGE_CHANNEL, JSON.stringify({ + message: INSUFFICIENT_PERMISSIONS_ERROR_MSG, + notificationType: EDIT_ERROR + })); return; } From 7b19b39bcef9867977822e014feb86872be3c841 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 18:36:06 +1200 Subject: [PATCH 277/504] Make physics work with multiple entities per HiFi's current capabilities --- scripts/vr-edit/modules/groups.js | 39 +++++++++++++++++++++++----- scripts/vr-edit/modules/selection.js | 26 +++++++++++++++---- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index 8cfb2b971e..fd78b1ef76 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -81,13 +81,27 @@ Groups = function () { function group() { // Groups all selections into one. - var rootID, + var DYNAMIC_AND_COLLISIONLESS = { dynamic: true, collisionless: true }, + rootID, i, - count; + lengthI, + j, + lengthJ; + + // If the first group has physics (i.e., root entity is dynamic) make all entities in child groups dynamic and + // collisionless. (Don't need to worry about other groups physics properties because only those of the the first entity + // in the linkset are used by High Fidelity.) See Selection.applyPhysics(). + if (selections[0][0].dynamic) { + for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + Entities.editEntity(selections[i][j].id, DYNAMIC_AND_COLLISIONLESS); + } + } + } // Make the first entity in the first group the root and link the first entities of all other groups to it. rootID = rootEntityIDs[0]; - for (i = 1, count = rootEntityIDs.length; i < count; i += 1) { + for (i = 1, lengthI = rootEntityIDs.length; i < lengthI; i += 1) { Entities.editEntity(rootEntityIDs[i], { parentID: rootID }); @@ -95,7 +109,7 @@ Groups = function () { // Update selection. rootEntityIDs.splice(1, rootEntityIDs.length - 1); - for (i = 1, count = selections.length; i < count; i += 1) { + for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { selections[i][0].parentID = rootID; selections[0] = selections[0].concat(selections[i]); } @@ -114,8 +128,11 @@ Groups = function () { hasSoloChildren = false, hasGroupChildren = false, isUngroupAll, + NONDYNAMIC_AND_NONCOLLISIONLESS = { dynamic: false, collisionless: false }, i, - count; + lengthI, + j, + lengthJ; function updateGroupInformation() { var childrenIndexesLength = childrenIndexes.length; @@ -141,7 +158,7 @@ Groups = function () { // Compile information on immediate children. rootID = rootEntityIDs[0]; - for (i = 1, count = selections[0].length; i < count; i += 1) { + for (i = 1, lengthI = selections[0].length; i < lengthI; i += 1) { if (selections[0][i].parentID === rootID) { childrenIDs.push(selections[0][i].id); childrenIndexes.push(i); @@ -163,6 +180,16 @@ Groups = function () { selections.push(selections[0].splice(childrenIndexes[i], childrenIndexes[i + 1] - childrenIndexes[i])); } } + + // If root group has physics, reset child groups to defaults for dynamic and collisionless. See + // Selection.applyPhysics(). + if (selections[0][0].dynamic) { + for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + Entities.editEntity(selections[i][j].id, NONDYNAMIC_AND_NONCOLLISIONLESS); + } + } + } } function clear() { diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 578d603945..f3ce53d3e0 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -224,11 +224,11 @@ Selection = function (side) { } function finishEditing() { - var count, - i; + var i; // Restore entity set's physics. - for (i = 0, count = selection.length; i < count; i += 1) { + // Note: Need to apply children-first in order to avoid children's relative positions sometimes drifting. + for (i = selection.length - 1; i >= 0; i -= 1) { Entities.editEntity(selection[i].id, { dynamic: selection[i].dynamic, collisionless: selection[i].collisionless @@ -424,9 +424,25 @@ Selection = function (side) { } function applyPhysics(physicsProperties) { - // Apply physics to just the root entity. - var properties; + // Regarding trees of entities, when physics is to be enabled the physics engine currently: + // - Only works with physics applied to the root entity; i.e., child entities are ignored for collisions. + // - Requires child entities to be dynamic if the root entity is dynamic, otherwise child entities can drift. + // - Requires child entities to be collisionless, otherwise the entity tree can become self-propelled. + // See also: Groups.group() and ungroup(). + var properties, + i, + length; + // Make children cater to physicsProperties. + properties = { + dynamic: physicsProperties.dynamic, + collisionless: physicsProperties.dynamic || physicsProperties.collisionless + }; + for (i = 1, length = selection.length; i < length; i += 1) { + Entities.editEntity(selection[i].id, properties); + } + + // Set root per physicsProperties. properties = Object.clone(physicsProperties); properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); Entities.editEntity(rootEntityID, properties); From 6c35a008f16a0b359acb5166ededbff84a6d972d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 22:32:22 +1200 Subject: [PATCH 278/504] Fix scaling entities that have physics --- scripts/vr-edit/modules/selection.js | 46 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index f3ce53d3e0..8befc6416b 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -21,7 +21,11 @@ Selection = function (side) { rootEntityID = null, rootPosition, rootOrientation, + scaleFactor, + scaleRotation, scaleCenter, + scalePosition, + scaleOrientation, scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", @@ -261,7 +265,6 @@ Selection = function (side) { function startDirectScaling(center) { // Save initial position and orientation so that can scale relative to these without accumulating float errors. - scaleCenter = center; scaleRootOffset = Vec3.subtract(rootPosition, center); scaleRootOrientation = rootOrientation; } @@ -287,10 +290,29 @@ Selection = function (side) { localPosition: Vec3.multiply(factor, selection[i].localPosition) }); } + + // Save most recent scale parameters. + scaleFactor = factor; + scaleRotation = rotation; + scaleCenter = center; } function finishDirectScaling() { - select(intersectedEntityID); // Refresh. + // Update selection with final entity properties. + var i, + length; + // Final scale, position, and orientaation of root. + rootPosition = Vec3.sum(scaleCenter, Vec3.multiply(scaleFactor, Vec3.multiplyQbyV(scaleRotation, scaleRootOffset))); + rootOrientation = Quat.multiply(scaleRotation, scaleRootOrientation); + selection[0].dimensions = Vec3.multiply(scaleFactor, selection[0].dimensions); + selection[0].position = rootPosition; + selection[0].rotation = rootOrientation; + + // Final scale and position of children. + for (i = 1, length = selection.length; i < length; i += 1) { + selection[i].dimensions = Vec3.multiply(scaleFactor, selection[i].dimensions); + selection[i].localPosition = Vec3.multiply(scaleFactor, selection[i].localPosition); + } } function startHandleScaling() { @@ -320,10 +342,28 @@ Selection = function (side) { localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) }); } + + // Save most recent scale parameters. + scaleFactor = factor; + scalePosition = position; + scaleOrientation = orientation; } function finishHandleScaling() { - select(intersectedEntityID); // Refresh. + // Update selection with final entity properties. + var i, + length; + + // Final scale and position of root. + selection[0].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[0].dimensions); + selection[0].position = scalePosition; + selection[0].rotation = scaleOrientation; + + // Final scale and position of children. + for (i = 1, length = selection.length; i < length; i += 1) { + selection[i].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[i].dimensions); + selection[i].localPosition = Vec3.multiplyVbyV(scaleFactor, selection[i].localPosition); + } } function cloneEntities() { From 808f37a824e47735b48eac90421c677277a249e5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 22:54:51 +1200 Subject: [PATCH 279/504] Prevent entities from jittering or drifting when grab a moving set --- scripts/vr-edit/modules/selection.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 8befc6416b..24717cbe62 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -219,12 +219,18 @@ Selection = function (side) { i; // Disable entity set's physics. - for (i = 0, count = selection.length; i < count; i += 1) { + //for (i = 0, count = selection.length; i < count; i += 1) { + for (i = selection.length - 1; i >= 0; i -= 1) { Entities.editEntity(selection[i].id, { - dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. - collisionless: true // So that entity doesn't bump us about as we resize the entity. + dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. + collisionless: true, // So that entity doesn't bump us about as we resize the entity. + velocity: Vec3.ZERO, // So that entity doesn't drift if we've grabbed a set while it was moving. + angularVelocity: Vec3.ZERO // "" }); } + + // Stop moving. + Entities.editEntity(rootEntityID, { velocity: Vec3.ZERO, angularVelocity: Vec3.ZERO }); } function finishEditing() { From 3cc0db8b40d50e314dc7fd66767ec8c167594b9e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 6 Sep 2017 23:38:38 +1200 Subject: [PATCH 280/504] Fix app icon occasionally being disabled at Interface start --- scripts/vr-edit/vr-edit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4bb70134b8..070901cdc0 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -19,6 +19,7 @@ ENABLED_CAPTION_COLOR_OVERRIDE = "", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. + START_DELAY = 2000, // ms // Application state isAppActive, @@ -1803,6 +1804,6 @@ tablet = null; } - setUp(); + Script.setTimeout(setUp, START_DELAY); // Delay start so that Entities.canRez() work; button is enabled correctly. Script.scriptEnding.connect(tearDown); }()); From 2661fb66470ba3db69b4a58196bcabb318e49c4e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Sep 2017 08:48:03 +1200 Subject: [PATCH 281/504] Tidying --- scripts/vr-edit/modules/selection.js | 3 +-- scripts/vr-edit/vr-edit.js | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 24717cbe62..fe2dd5e0a5 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -215,8 +215,7 @@ Selection = function (side) { } function startEditing() { - var count, - i; + var i; // Disable entity set's physics. //for (i = 0, count = selection.length; i < count; i += 1) { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 070901cdc0..75a8557f58 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1740,11 +1740,6 @@ Messages.messageReceived.connect(onMessageReceived); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.skeletonChanged.connect(onSkeletonChanged); - - // Start main update loop. - if (isAppActive) { - update(); - } } function tearDown() { From 8ca97d91c5a4127d0e556eaa57b8a5cc9b6d24bf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Sep 2017 10:26:12 +1200 Subject: [PATCH 282/504] Make able to pick color from locked entities --- scripts/vr-edit/vr-edit.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 75a8557f58..64b116993c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -948,7 +948,7 @@ break; case EDITOR_SEARCHING: if (hand.valid() - && (!intersection.entityID || !intersection.editableEntity) + && (!intersection.entityID || !(intersection.editableEntity || toolSelected === TOOL_PICK_COLOR)) && !(intersection.overlayID && !wasTriggerClicked && isTriggerClicked && otherEditor.isHandle(intersection.overlayID))) { // No transition. @@ -963,12 +963,12 @@ intersectedEntityID = otherEditor.intersectedEntityID(); rootEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && intersection.editableEntity + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); @@ -1014,7 +1014,7 @@ break; case EDITOR_HIGHLIGHTING: if (hand.valid() - && intersection.entityID && intersection.editableEntity + && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && !(!wasTriggerClicked && isTriggerClicked && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID @@ -1056,7 +1056,8 @@ intersectedEntityID = otherEditor.intersectedEntityID(); rootEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && intersection.editableEntity && !wasTriggerClicked && isTriggerClicked) { + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && !wasTriggerClicked && isTriggerClicked) { intersectedEntityID = intersection.entityID; // May be a different entityID. rootEntityID = Entities.rootOf(intersectedEntityID); if (otherEditor.isEditing(rootEntityID)) { From e94d2034c7f1fea9b7d45aca3f32b115a858a9af Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Sep 2017 10:44:03 +1200 Subject: [PATCH 283/504] Don't grab entity behind entity just created and grip-click-deleted --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 64b116993c..f69d2c3690 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1220,7 +1220,7 @@ wasTriggerClicked = isTriggerClicked; wasGripClicked = isGripClicked; - isAutoGrab = isAutoGrab && isTriggerClicked; + isAutoGrab = isAutoGrab && isTriggerClicked && !isGripClicked; if (DEBUG && editorState !== previousState) { debug(side, EDITOR_STATE_STRINGS[editorState]); From 8fdc2313cc2e219764fa7a8b3b4bf61d27665a17 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Sep 2017 18:36:01 +1200 Subject: [PATCH 284/504] Tidying --- scripts/vr-edit/modules/feedback.js | 2 +- scripts/vr-edit/modules/toolIcon.js | 6 - scripts/vr-edit/modules/toolsMenu.js | 160 +++++++++++++-------------- scripts/vr-edit/vr-edit.js | 4 +- 4 files changed, 81 insertions(+), 91 deletions(-) diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index 843eebe07b..23a82e66aa 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -30,7 +30,7 @@ Feedback = (function () { SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, - HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 } , // Tools menu. + HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index d03351b27c..6ea6965a36 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -86,11 +86,6 @@ ToolIcon = function (side) { setHand(side); - function update() { - // TODO: Display icon animation. - // TODO: Clear icon animation. - } - function clear() { // Deletes current tool model. if (modelOverlay) { @@ -160,7 +155,6 @@ ToolIcon = function (side) { return { setHand: setHand, - update: update, display: display, clear: clear, destroy: destroy diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index ab5a5f74c1..5825b808da 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1843,12 +1843,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsItems, intersectionOverlays, intersectionEnabled, - highlightedItem, // TODO: Rename this and similar to "hovered". - highlightedItems, - highlightedSourceOverlays, - highlightedSourceItems, - highlightedElementType = null, - isHighlightingButtonElement, + hoveredItem, + hoveredSourceOverlays, + hoveredSourceItems, + hoveredElementType = null, + isHoveringButtonElement, isPicklistOpen, pressedItem = null, pressedSource = null, @@ -2583,7 +2582,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { index = optionsOverlaysIDs.indexOf("physicsPresets"); // Lower picklist. - isHighlightingPicklist = highlightedElementType === "picklist"; + isHighlightingPicklist = hoveredElementType === "picklist"; Overlays.editOverlay(optionsOverlays[index], { localPosition: isHighlightingPicklist ? Vec3.sum(optionsItems[index].properties.localPosition, OPTION_HOVER_DELTA) @@ -2865,74 +2864,74 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } - // Highlight clickable item. - if (intersectedItem !== highlightedItem || intersectionOverlays !== highlightedSourceOverlays) { + // Hover clickable item. + if (intersectedItem !== hoveredItem || intersectionOverlays !== hoveredSourceOverlays) { - if (highlightedItem !== NONE) { + if (hoveredItem !== NONE) { // Unhover old item. - switch (highlightedElementType) { + switch (hoveredElementType) { case "menuButton": - Overlays.editOverlay(menuHoverOverlays[highlightedItem], { + Overlays.editOverlay(menuHoverOverlays[hoveredItem], { localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, visible: false }); - Overlays.editOverlay(menuIconOverlays[highlightedItem], { + Overlays.editOverlay(menuIconOverlays[hoveredItem], { color: UI_ELEMENTS.menuButton.icon.properties.color }); break; case "button": - if (highlightedSourceItems[highlightedItem].enabledColor !== undefined && optionsEnabled[highlightedItem]) { - color = highlightedSourceItems[highlightedItem].enabledColor; + if (hoveredSourceItems[hoveredItem].enabledColor !== undefined && optionsEnabled[hoveredItem]) { + color = hoveredSourceItems[hoveredItem].enabledColor; } else { - color = highlightedSourceItems[highlightedItem].properties.color !== undefined - ? highlightedSourceItems[highlightedItem].properties.color + color = hoveredSourceItems[hoveredItem].properties.color !== undefined + ? hoveredSourceItems[hoveredItem].properties.color : UI_ELEMENTS.button.properties.color; } - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { color: color, - localPosition: highlightedSourceItems[highlightedItem].properties.localPosition + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition }); break; case "toggleButton": - color = optionsToggles[highlightedSourceItems[highlightedItem].id] + color = optionsToggles[hoveredSourceItems[hoveredItem].id] ? UI_ELEMENTS.toggleButton.onColor : UI_ELEMENTS.toggleButton.offColor; - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { color: color, - localPosition: highlightedSourceItems[highlightedItem].properties.localPosition + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition }); break; case "swatch": Overlays.editOverlay(swatchHighlightOverlay, { visible: false }); - color = optionsSettings[highlightedSourceItems[highlightedItem].id].value; - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + color = optionsSettings[hoveredSourceItems[hoveredItem].id].value; + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { dimensions: UI_ELEMENTS.swatch.properties.dimensions, color: color === "" ? EMPTY_SWATCH_COLOR : color, - localPosition: highlightedSourceItems[highlightedItem].properties.localPosition + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition }); break; case "barSlider": case "imageSlider": case "colorCircle": // Lower old slider or color circle. - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { - localPosition: highlightedSourceItems[highlightedItem].properties.localPosition + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition }); break; case "picklist": - if (highlightedSourceItems[highlightedItem].type !== "picklistItem" && !isPicklistOpen) { - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { - localPosition: highlightedSourceItems[highlightedItem].properties.localPosition, + if (hoveredSourceItems[hoveredItem].type !== "picklistItem" && !isPicklistOpen) { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition, color: UI_ELEMENTS.picklist.properties.color }); } else { - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { color: UIT.colors.darkGray }); } break; case "picklistItem": - Overlays.editOverlay(highlightedSourceOverlays[highlightedItem], { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { color: UI_ELEMENTS.picklistItem.properties.color }); break; @@ -2940,13 +2939,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Nothing to do. break; default: - App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + highlightedElementType); + App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); } // Update status variables. - highlightedItem = NONE; - isHighlightingButtonElement = false; - highlightedElementType = null; + hoveredItem = NONE; + isHoveringButtonElement = false; + hoveredElementType = null; } if (intersectedItem !== NONE && intersectionItems[intersectedItem] && @@ -2954,41 +2953,40 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { || intersectionItems[intersectedItem].callback !== undefined)) { // Update status variables. - highlightedItem = intersectedItem; - highlightedItems = intersectionItems; - isHighlightingButtonElement = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[highlightedItem].type) !== NONE; - highlightedElementType = intersectionItems[highlightedItem].type; + hoveredItem = intersectedItem; + isHoveringButtonElement = BUTTON_UI_ELEMENTS.indexOf(intersectionItems[hoveredItem].type) !== NONE; + hoveredElementType = intersectionItems[hoveredItem].type; // Hover new item. - switch (highlightedElementType) { + switch (hoveredElementType) { case "menuButton": Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); - Overlays.editOverlay(menuHoverOverlays[highlightedItem], { + Overlays.editOverlay(menuHoverOverlays[hoveredItem], { localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, MENU_HOVER_DELTA), visible: true }); - Overlays.editOverlay(menuIconOverlays[highlightedItem], { + Overlays.editOverlay(menuIconOverlays[hoveredItem], { color: UI_ELEMENTS.menuButton.icon.highlightColor }); break; case "button": - if (intersectionEnabled[highlightedItem]) { + if (intersectionEnabled[hoveredItem]) { Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[highlightedItem], { - color: intersectionItems[highlightedItem].highlightColor !== undefined - ? intersectionItems[highlightedItem].highlightColor + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: intersectionItems[hoveredItem].highlightColor !== undefined + ? intersectionItems[hoveredItem].highlightColor : UIT.colors.greenHighlight, localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); } break; case "toggleButton": - if (intersectionEnabled[highlightedItem]) { + if (intersectionEnabled[hoveredItem]) { Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[highlightedItem], { - color: optionsToggles[intersectionItems[highlightedItem].id] + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: optionsToggles[intersectionItems[hoveredItem].id] ? UI_ELEMENTS.toggleButton.onHoverColor : UI_ELEMENTS.toggleButton.offHoverColor, localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) @@ -2997,22 +2995,22 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "swatch": Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[highlightedItem].properties.localPosition; - if (optionsSettings[intersectionItems[highlightedItem].id].value === "") { + localPosition = intersectionItems[hoveredItem].properties.localPosition; + if (optionsSettings[intersectionItems[hoveredItem].id].value === "") { // Swatch is empty; highlight it with current color. - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + Overlays.editOverlay(intersectionOverlays[hoveredItem], { color: optionsSettings.currentColor.value, localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); } else { // Swatch is full; highlight it with ring. - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + Overlays.editOverlay(intersectionOverlays[hoveredItem], { dimensions: Vec3.subtract(UI_ELEMENTS.swatch.properties.dimensions, { x: SWATCH_HIGHLIGHT_DELTA, y: 0, z: SWATCH_HIGHLIGHT_DELTA }), localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); Overlays.editOverlay(swatchHighlightOverlay, { - parentID: intersectionOverlays[highlightedItem], + parentID: intersectionOverlays[hoveredItem], localPosition: UI_ELEMENTS.swatchHighlight.properties.localPosition, visible: true }); @@ -3022,28 +3020,28 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "imageSlider": case "colorCircle": Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); break; case "picklist": Feedback.play(otherSide, Feedback.HOVER_BUTTON); if (!isPicklistOpen) { - localPosition = intersectionItems[highlightedItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA), color: UIT.colors.greenHighlight }); } else { - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + Overlays.editOverlay(intersectionOverlays[hoveredItem], { color: UIT.colors.greenHighlight }); } break; case "picklistItem": Feedback.play(otherSide, Feedback.HOVER_BUTTON); - Overlays.editOverlay(intersectionOverlays[highlightedItem], { + Overlays.editOverlay(intersectionOverlays[hoveredItem], { color: UIT.colors.greenHighlight }); break; @@ -3051,12 +3049,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Nothing to do. break; default: - App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + highlightedElementType); + App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); } } - highlightedSourceOverlays = intersectionOverlays; - highlightedSourceItems = intersectionItems; + hoveredSourceOverlays = intersectionOverlays; + hoveredSourceItems = intersectionItems; } // Press/unpress button. @@ -3065,18 +3063,18 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (pressedItem) { // Unpress previous button. Overlays.editOverlay(pressedSource[pressedItem.index], { - localPosition: isHighlightingButtonElement && highlightedItem === pressedItem.index + localPosition: isHoveringButtonElement && hoveredItem === pressedItem.index ? Vec3.sum(pressedItem.localPosition, OPTION_HOVER_DELTA) : pressedItem.localPosition }); pressedItem = null; pressedSource = null; } - if (isHighlightingButtonElement && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) + if (isHoveringButtonElement && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) && isTriggerClicked && !wasTriggerClicked) { // Press new button. localPosition = intersectionItems[intersectedItem].properties.localPosition; - if (highlightedElementType !== "menuButton") { + if (hoveredElementType !== "menuButton") { Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) }); @@ -3105,7 +3103,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Picklist update. - if (intersectionItems && ((highlightedElementType === "picklist" + if (intersectionItems && ((hoveredElementType === "picklist" && controlHand.triggerClicked() !== isPicklistPressed) || (intersectionItems[intersectedItem].type !== "picklist" && isPicklistPressed))) { isPicklistPressed = controlHand.triggerClicked(); @@ -3113,7 +3111,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { doCommand(intersectionItems[intersectedItem].command.method, intersectionItems[intersectedItem].id); } } - if (intersectionItems && ((highlightedElementType === "picklistItem" + if (intersectionItems && ((hoveredElementType === "picklistItem" && controlHand.triggerClicked() !== isPicklistItemPressed) || (intersectionItems[intersectedItem].type !== "picklistItem" && isPicklistItemPressed))) { isPicklistItemPressed = controlHand.triggerClicked(); @@ -3122,7 +3120,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } if (intersectionItems && isPicklistOpen && controlHand.triggerClicked() - && highlightedElementType !== "picklist" && highlightedElementType !== "picklistItem") { + && hoveredElementType !== "picklist" && hoveredElementType !== "picklistItem") { doCommand("togglePhysicsPresets"); } @@ -3137,7 +3135,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Bar slider update. - if (intersectionItems && highlightedElementType === "barSlider" && controlHand.triggerClicked()) { + if (intersectionItems && hoveredElementType === "barSlider" && controlHand.triggerClicked()) { sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); overlayDimensions = intersectionItems[intersectedItem].properties.dimensions; if (overlayDimensions === undefined) { @@ -3155,7 +3153,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Image slider update. - if (intersectionItems && highlightedElementType === "imageSlider" && controlHand.triggerClicked()) { + if (intersectionItems && hoveredElementType === "imageSlider" && controlHand.triggerClicked()) { sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); overlayDimensions = intersectionItems[intersectedItem].properties.dimensions; if (overlayDimensions === undefined) { @@ -3176,7 +3174,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } // Color circle update. - if (intersectionItems && highlightedElementType === "colorCircle" && controlHand.triggerClicked()) { + if (intersectionItems && hoveredElementType === "colorCircle" && controlHand.triggerClicked()) { sliderProperties = Overlays.getProperties(intersection.overlayID, ["position", "orientation"]); delta = Vec3.multiplyQbyV(Quat.inverse(sliderProperties.orientation), Vec3.subtract(intersection.intersection, sliderProperties.position)); @@ -3208,7 +3206,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isGroupButtonEnabled = enableGroupButton; Overlays.editOverlay(optionsOverlays[groupButtonIndex], { color: isGroupButtonEnabled - ? (highlightedItem === groupButtonIndex + ? (hoveredItem === groupButtonIndex ? OPTONS_PANELS.groupOptions[groupButtonIndex].highlightColor : OPTONS_PANELS.groupOptions[groupButtonIndex].enabledColor) : OPTONS_PANELS.groupOptions[groupButtonIndex].properties.color @@ -3226,7 +3224,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isUngroupButtonEnabled = enableUngroupButton; Overlays.editOverlay(optionsOverlays[ungroupButtonIndex], { color: isUngroupButtonEnabled - ? (highlightedItem === ungroupButtonIndex + ? (hoveredItem === ungroupButtonIndex ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].highlightColor : OPTONS_PANELS.groupOptions[ungroupButtonIndex].enabledColor) : OPTONS_PANELS.groupOptions[ungroupButtonIndex].properties.color @@ -3312,10 +3310,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsItems = null; intersectionOverlays = null; intersectionEnabled = null; - highlightedItem = NONE; - highlightedSourceOverlays = null; - isHighlightingButtonElement = false; - highlightedElementType = null; + hoveredItem = NONE; + hoveredSourceOverlays = null; + isHoveringButtonElement = false; + hoveredElementType = null; pressedItem = null; pressedSource = null; isPicklistOpen = false; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index f69d2c3690..fbf767b839 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -71,7 +71,7 @@ button, DOMAIN_CHANGED_MESSAGE = "Toolbar-DomainChanged", - DEBUG = true; // TODO: Set false. + DEBUG = false; // Utilities Script.include("./utilities/utilities.js"); @@ -275,7 +275,6 @@ intersection = getIntersection(); toolsMenu.update(intersection, grouping.groupsCount(), grouping.entitiesCount()); createPalette.update(intersection.overlayID); - toolIcon.update(); } } @@ -1037,7 +1036,6 @@ intersectedEntityID = intersection.entityID; doUpdateState = true; } - // TODO: For development testing, update intersectedEntityID so that physics can be applied to it. if ((toolSelected === TOOL_COLOR || toolSelected === TOOL_PHYSICS) && intersection.entityID !== intersectedEntityID) { intersectedEntityID = intersection.entityID; From 70581ebbc1b4fde45b2509e092bc430d13521232 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Sep 2017 10:43:43 +1200 Subject: [PATCH 285/504] Fix handle scaling's scaling and positioning of entities --- scripts/vr-edit/modules/selection.js | 7 ++-- scripts/vr-edit/vr-edit.js | 53 +++++----------------------- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index fe2dd5e0a5..122465cdce 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -320,8 +320,9 @@ Selection = function (side) { } } - function startHandleScaling() { - // Nothing to do. + function startHandleScaling(position) { + // Save initial offset from hand position to root position so that can scale without accumulating float errors. + scaleRootOffset = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(rootPosition, position)); } function handleScale(factor, position, orientation) { @@ -330,7 +331,7 @@ Selection = function (side) { length; // Scale and position root. - rootPosition = position; + rootPosition = Vec3.sum(Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(factor, scaleRootOffset)), position); rootOrientation = orientation; Entities.editEntity(selection[0].id, { dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index fbf767b839..84c6e269e3 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -383,15 +383,10 @@ isHandleScaling = false, // "" initialTargetsSeparation, initialtargetsDirection, - initialTargetToBoundingBoxCenter, otherTargetPosition, handleUnitScaleAxis, handleScaleDirections, - handleHandOffset, initialHandleDistance, - initialHandleOrientationInverse, - initialHandleRegistrationOffset, - initialSelectionOrientationInverse, laserOffset, MIN_SCALE = 0.001, @@ -533,41 +528,23 @@ function startHandleScaling(targetPosition, overlayID) { // Called on grabbing hand by scaling hand. var initialTargetPosition, - boundingBox, selectionPositionAndOrientation, - scaleAxis, - handDistance; - - isScalingWithHand = intersection.handIntersected; - - otherTargetPosition = targetPosition; + scaleAxis; // Keep grabbed handle highlighted and hide other handles. handles.grab(overlayID); - // Vector from target to bounding box center. + isScalingWithHand = intersection.handIntersected; + + otherTargetPosition = targetPosition; initialTargetPosition = getScaleTargetPosition(); - boundingBox = selection.boundingBox(); - initialTargetToBoundingBoxCenter = Vec3.subtract(boundingBox.center, initialTargetPosition); - - // Selection information. selectionPositionAndOrientation = selection.getPositionAndOrientation(); - initialSelectionOrientationInverse = Quat.inverse(selectionPositionAndOrientation.orientation); - - // Handle information. - initialHandleOrientationInverse = Quat.inverse(hand.orientation()); handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. - initialHandleDistance = Vec3.length(Vec3.multiplyVbyV(boundingBox.dimensions, handleScaleDirections)) / 2; - initialHandleRegistrationOffset = Vec3.multiplyQbyV(initialSelectionOrientationInverse, - Vec3.subtract(selectionPositionAndOrientation.position, boundingBox.center)); - - // Distance from hand to handle in direction of handle. scaleAxis = Vec3.multiplyQbyV(selectionPositionAndOrientation.orientation, handleUnitScaleAxis); - handDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBox.center), scaleAxis)); - handleHandOffset = handDistance - initialHandleDistance; + initialHandleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, initialTargetPosition), scaleAxis)); - selection.startHandleScaling(); + selection.startHandleScaling(initialTargetPosition); handles.startScaling(); isHandleScaling = true; } @@ -629,10 +606,7 @@ // Scales selection per changing position of scaling hand; positions and orients per grabbing hand. var targetPosition, deltaHandOrientation, - deltaHandleOrientation, - selectionPosition, selectionOrientation, - boundingBoxCenter, scaleAxis, handleDistance, scale, @@ -643,15 +617,10 @@ deltaHandOrientation = Quat.multiply(hand.orientation(), initialHandOrientationInverse); selectionOrientation = Quat.multiply(deltaHandOrientation, initialSelectionOrientation); - // Desired distance of handle from center of bounding box. + // Desired distance of handle from other hand targetPosition = getScaleTargetPosition(); - deltaHandleOrientation = Quat.multiply(hand.orientation(), initialHandleOrientationInverse); - boundingBoxCenter = Vec3.sum(targetPosition, - Vec3.multiplyQbyV(deltaHandleOrientation, initialTargetToBoundingBoxCenter)); scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); - handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, boundingBoxCenter), scaleAxis)); - handleDistance -= handleHandOffset; - handleDistance = Math.max(handleDistance, MIN_SCALE); + handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, targetPosition), scaleAxis)); // Scale selection relative to initial dimensions. scale = handleDistance / initialHandleDistance; @@ -662,13 +631,9 @@ z: handleScaleDirections.z !== 0 ? scale3D.z : 1 }; - // Reposition selection per scale. - selectionPosition = Vec3.sum(boundingBoxCenter, - Vec3.multiplyQbyV(selectionOrientation, Vec3.multiplyVbyV(scale3D, initialHandleRegistrationOffset))); - // Scale. handles.scale(scale3D); - selection.handleScale(scale3D, selectionPosition, selectionOrientation); + selection.handleScale(scale3D, targetPosition, selectionOrientation); // Update grab offset. selectionPositionAndOrientation = selection.getPositionAndOrientation(); From b953c49461dc26176336770f5cd1f6afdfb26eec Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 03:20:55 +0100 Subject: [PATCH 286/504] Commit for Better Logger - WL 21537 --- interface/src/Application.cpp | 13 +++++++++++-- libraries/shared/src/shared/FileLogger.cpp | 11 ++++++++++- libraries/shared/src/shared/FileLogger.h | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3650c495f2..f0ad0fe85b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -510,6 +510,13 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt OutputDebugStringA(logMessage.toLocal8Bit().constData()); OutputDebugStringA("\n"); #endif + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + + QUuid fileLoggerSessionID = myAvatar->getSessionUUID(); + if (!fileLoggerSessionID.isNull()) { + qApp->getLogger()->setSessionID(fileLoggerSessionID); + } qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); } } @@ -804,6 +811,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _deadlockWatchdogThread = new DeadlockWatchdogThread(); _deadlockWatchdogThread->start(); + // Set File Logger Session UUID + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); } @@ -920,8 +931,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // send a location update immediately discoverabilityManager->updateLocation(); - auto myAvatar = getMyAvatar(); - connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index bea28b2b6f..1ac87907bf 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -48,6 +48,8 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; +static QUuid& SESSION_ID = QUuid::QUuid("{00000000-0000-0000-0000-000000000000}"); + // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. static const qint64 MAX_LOG_SIZE = 512 * 1024; @@ -62,7 +64,10 @@ QString getLogRollerFilename() { QString result = FileUtils::standardPath(LOGS_DIRECTORY); QHostAddress clientAddress = getGuessedLocalAddress(); QDateTime now = QDateTime::currentDateTime(); - result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); + + auto FILE_SESSION_ID = SESSION_ID.toString().replace("{", "").replace("}", ""); + + result.append(QString(FILENAME_FORMAT).arg(FILE_SESSION_ID, now.toString(DATETIME_FORMAT))); return result; } @@ -142,6 +147,10 @@ FileLogger::~FileLogger() { _persistThreadInstance->terminate(); } +void FileLogger::setSessionID(const QUuid& message) { + SESSION_ID = message; // This is for the output of log files. It will change if the Avatar enters a different domain. +} + void FileLogger::addMessage(const QString& message) { _persistThreadInstance->queueItem(message); emit logReceived(message); diff --git a/libraries/shared/src/shared/FileLogger.h b/libraries/shared/src/shared/FileLogger.h index 15d211afe8..d9d7651147 100644 --- a/libraries/shared/src/shared/FileLogger.h +++ b/libraries/shared/src/shared/FileLogger.h @@ -26,6 +26,7 @@ public: QString getFilename() const { return _fileName; } virtual void addMessage(const QString&) override; + virtual void setSessionID(const QUuid&); virtual QString getLogData() override; virtual void locateLog() override; virtual void sync() override; From 7f4cc0ed2a6382e289eaf7907ac51537e5531fc0 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 20:21:17 +0100 Subject: [PATCH 287/504] Should fix the Mac Build --- libraries/shared/src/shared/FileLogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 1ac87907bf..56393f4f32 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -48,7 +48,7 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -static QUuid& SESSION_ID = QUuid::QUuid("{00000000-0000-0000-0000-000000000000}"); +static QUuid& SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. From dfbd25fd77ffe1e6b9b6ec5938dc250126ade69b Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 20:51:41 +0100 Subject: [PATCH 288/504] Now pls --- libraries/shared/src/shared/FileLogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 56393f4f32..652cca79a3 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -48,7 +48,7 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -static QUuid& SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); +QUuid& SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. From 7b39cb77916e032aa7dcb954c64b165e8f197d75 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 21:44:38 +0100 Subject: [PATCH 289/504] Should work now --- libraries/shared/src/shared/FileLogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 652cca79a3..46ba45e238 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -48,7 +48,7 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -QUuid& SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); +QUuid SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. From f109a86b23fb0476e8d9b40e1dac7229a7b9c042 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 23:05:22 +0100 Subject: [PATCH 290/504] Fixed small crash bug on exit --- interface/src/Application.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f0ad0fe85b..3ee5406ad0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -513,10 +513,13 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt auto avatarManager = DependencyManager::get(); auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - QUuid fileLoggerSessionID = myAvatar->getSessionUUID(); - if (!fileLoggerSessionID.isNull()) { - qApp->getLogger()->setSessionID(fileLoggerSessionID); + if (myAvatar) { + QUuid fileLoggerSessionID = myAvatar->getSessionUUID(); + if (!fileLoggerSessionID.isNull()) { + qApp->getLogger()->setSessionID(fileLoggerSessionID); + } } + qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); } } From 387e474889fd7961d63b198a6718a7ccc2ad285f Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 23:48:09 +0100 Subject: [PATCH 291/504] WIP --- interface/src/Application.cpp | 24 ++++++++++++------------ libraries/avatars/src/AvatarData.h | 10 ++++++++-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3ee5406ad0..e373e321f2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -510,16 +510,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt OutputDebugStringA(logMessage.toLocal8Bit().constData()); OutputDebugStringA("\n"); #endif - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - - if (myAvatar) { - QUuid fileLoggerSessionID = myAvatar->getSessionUUID(); - if (!fileLoggerSessionID.isNull()) { - qApp->getLogger()->setSessionID(fileLoggerSessionID); - } - } - qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); } } @@ -798,10 +788,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif - _logger = new FileLogger(this); // After setting organization name in order to get correct directory - + qInstallMessageHandler(messageHandler); + _logger = new FileLogger(this); + + connect(getMyAvatar().get(), &AvatarData::sessionUUIDChanged, _logger, [this] { + auto myAvatar = getMyAvatar(); + if (myAvatar) { + _logger->setSessionID(myAvatar->getSessionUUID()); + } + }); + + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("High Fidelity Interface"); @@ -2102,6 +2101,7 @@ Application::~Application() { _octreeProcessor.terminate(); _entityEditSender.terminate(); + disconnect(getMyAvatar().get(), &AvatarData::sessionUUIDChanged, _logger, nullptr); DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b4c36dba70..b5bce50e68 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -381,7 +381,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QStringList jointNames READ getJointNames) - Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID NOTIFY sessionUUIDChanged) Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) @@ -667,13 +667,19 @@ public: signals: void displayNameChanged(); void lookAtSnappingChanged(bool enabled); + void sessionUUIDChanged(); public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); void setJointMappingsFromNetworkReply(); - void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); } + void setSessionUUID(const QUuid& sessionUUID) { + if (sessionUUID != getID()) { + setID(sessionUUID); + emit sessionUUIDChanged(); + } + } virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; From 0820eadc5b154949466311ce190aad83c8b5c00e Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Fri, 8 Sep 2017 23:52:04 +0100 Subject: [PATCH 292/504] Fixed a thing --- interface/src/Application.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e373e321f2..874cf34726 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -789,9 +789,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif - qInstallMessageHandler(messageHandler); - _logger = new FileLogger(this); + qInstallMessageHandler(messageHandler); connect(getMyAvatar().get(), &AvatarData::sessionUUIDChanged, _logger, [this] { auto myAvatar = getMyAvatar(); From 561c3e4653011bd4cf961877db05d22b13b87d73 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Sep 2017 17:24:07 +1200 Subject: [PATCH 293/504] Make polylines and polyvoxes able to be colored --- scripts/vr-edit/modules/selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 122465cdce..6fdfdb80b8 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -29,7 +29,7 @@ Selection = function (side) { scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect"]; + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect", "PolyLine", "PolyVox"]; if (!this instanceof Selection) { From a7e04fcba66c23bccaacd9ab356d51847a82d64c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Sep 2017 20:14:09 +1200 Subject: [PATCH 294/504] Don't color particle effects --- scripts/vr-edit/modules/selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 6fdfdb80b8..c93a285065 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -29,7 +29,7 @@ Selection = function (side) { scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "ParticleEffect", "PolyLine", "PolyVox"]; + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"]; if (!this instanceof Selection) { From 6f4f8e583b3b2c204cf537b67b8b08269f694806 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Sep 2017 20:15:15 +1200 Subject: [PATCH 295/504] Don't kick off physics for Text and Web entities --- scripts/vr-edit/modules/selection.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index c93a285065..1d7fac9ed8 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -192,6 +192,7 @@ Selection = function (side) { function doKick(entityID) { var properties, + NO_KICK_ENTITY_TYPES = ["Text", "Web"], // These entities don't respond to gravity so don't kick them. DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; @@ -200,8 +201,9 @@ Selection = function (side) { return; } - properties = Entities.getEntityProperties(entityID, ["velocity", "gravity", "parentID"]); - if (Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + properties = Entities.getEntityProperties(entityID, ["type", "velocity", "gravity"]); + if (NO_KICK_ENTITY_TYPES.indexOf(properties.type) === -1 + && Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { Entities.editEntity(entityID, { velocity: DYNAMIC_VELOCITY_KICK }); } } From f6e22b0733569d27a55aac233224cb7fc33abc2c Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Sat, 9 Sep 2017 17:21:42 +0100 Subject: [PATCH 296/504] Changes The ID no longer relies on the Avatar Session ID as this changed per domain switch. The intention of this PR is to be able to group the log files easier, hence why it now relies on Interface ID instead. Additionally, when no ID is found when the interface is first launched, the ID doesn't appear in the rolled over log file. It will just appear blank. --- interface/src/Application.cpp | 13 ++++--------- libraries/shared/src/shared/FileLogger.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 874cf34726..a063dfd0a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -792,14 +792,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _logger = new FileLogger(this); qInstallMessageHandler(messageHandler); - connect(getMyAvatar().get(), &AvatarData::sessionUUIDChanged, _logger, [this] { - auto myAvatar = getMyAvatar(); - if (myAvatar) { - _logger->setSessionID(myAvatar->getSessionUUID()); - } - }); - - QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("High Fidelity Interface"); @@ -815,6 +807,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Set File Logger Session UUID auto avatarManager = DependencyManager::get(); auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + auto accountManager = DependencyManager::get(); + + _logger->setSessionID(accountManager->getSessionID()); if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); @@ -943,7 +938,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); // connect to appropriate slots on AccountManager - auto accountManager = DependencyManager::get(); + // auto accountManager = DependencyManager::get(); auto dialogsManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 46ba45e238..f712a341fe 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -42,13 +42,13 @@ private: -static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; +static const QString FILENAME_FORMAT = "hifi-log%1_%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -QUuid SESSION_ID = QUuid("{00000000-0000-0000-0000-000000000000}"); +QUuid SESSION_ID; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. @@ -64,8 +64,11 @@ QString getLogRollerFilename() { QString result = FileUtils::standardPath(LOGS_DIRECTORY); QHostAddress clientAddress = getGuessedLocalAddress(); QDateTime now = QDateTime::currentDateTime(); + QString FILE_SESSION_ID; - auto FILE_SESSION_ID = SESSION_ID.toString().replace("{", "").replace("}", ""); + if (!SESSION_ID.isNull()) { + FILE_SESSION_ID = "_" + SESSION_ID.toString().replace("{", "").replace("}", ""); + } result.append(QString(FILENAME_FORMAT).arg(FILE_SESSION_ID, now.toString(DATETIME_FORMAT))); return result; From b69bd0ef49f2c04862ca31c56a8f101082309a06 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Sat, 9 Sep 2017 17:23:18 +0100 Subject: [PATCH 297/504] Removed old Disconnect --- interface/src/Application.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a063dfd0a1..2fa141a44f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2095,8 +2095,6 @@ Application::~Application() { _octreeProcessor.terminate(); _entityEditSender.terminate(); - disconnect(getMyAvatar().get(), &AvatarData::sessionUUIDChanged, _logger, nullptr); - DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); From ce15d0ecd0ed8ea8b9a4d1b64af5ad8d12a3c01f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 15:18:42 +1200 Subject: [PATCH 298/504] Fix color picker circle and slider images clipping at oblique angles --- scripts/vr-edit/modules/toolsMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 5825b808da..43056954e4 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -2158,7 +2158,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = Script.resolvePath(optionsItems[i].imageURL); childProperties.dimensions = { x: properties.dimensions.x, y: properties.dimensions.y }; - imageOffset += IMAGE_OFFSET; + imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. if (optionsItems[i].useBaseColor) { childProperties.color = properties.color; } @@ -2209,7 +2209,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = Script.resolvePath(optionsItems[i].imageURL); childProperties.scale = properties.dimensions.x; - imageOffset += IMAGE_OFFSET; + imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; childProperties.localRotation = Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; From 18f4a3bf35e6ae276f6c6582a6d547c0d3681902 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 15:43:19 +1200 Subject: [PATCH 299/504] Fix hiding UI when hand is inside entity that camera isn't --- scripts/vr-edit/vr-edit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 84c6e269e3..26fbfd392e 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -453,7 +453,7 @@ function isCameraOutsideEntity(entityID) { var cameraPosition, - entityPosition, + grabPosition, pickRay, PRECISION_PICKING = true, NO_EXCLUDE_IDS = [], @@ -461,11 +461,11 @@ intersection; cameraPosition = Camera.position; - entityPosition = Entities.getEntityProperties(entityID, "position").position; + grabPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); pickRay = { origin: cameraPosition, - direction: Vec3.normalize(Vec3.subtract(entityPosition, cameraPosition)), - length: Vec3.distance(entityPosition, cameraPosition) + direction: Vec3.normalize(Vec3.subtract(grabPosition, cameraPosition)), + length: Vec3.distance(grabPosition, cameraPosition) }; intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, [entityID], NO_EXCLUDE_IDS, VISIBLE_ONLY); From acf6a433a99a5ed8dad7925a518a2ed21f34405a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 16:38:06 +1200 Subject: [PATCH 300/504] Don't scale z-axis of text and Web entities --- scripts/vr-edit/modules/handles.js | 32 +++++++++++++++------------- scripts/vr-edit/modules/selection.js | 17 +++++++++++---- scripts/vr-edit/vr-edit.js | 4 ++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index a0279ef9a1..8e9c5694fa 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -129,7 +129,7 @@ Handles = function (side) { return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; } - function display(rootEntityID, boundingBox, isMultipleEntities) { + function display(rootEntityID, boundingBox, isMultipleEntities, isSuppressZAxis) { var boundingBoxCenter, boundingBoxOrientation, cameraPosition, @@ -217,20 +217,22 @@ Handles = function (side) { faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); for (i = 0; i < NUM_FACE_HANDLES; i += 1) { - faceHandleOverlays[i] = Overlays.addOverlay("shape", { - parentID: rootEntityID, - localPosition: Vec3.sum(boundingBoxLocalCenter, - Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), - localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], - dimensions: faceHandleDimensions, - shape: "Cone", - color: HANDLE_NORMAL_COLOR, - alpha: HANDLE_NORMAL_ALPHA, - solid: true, - drawInFront: true, - ignoreRayIntersection: false, - visible: true - }); + if (!isSuppressZAxis || FACE_HANDLE_OVERLAY_AXES[i].z === 0) { + faceHandleOverlays[i] = Overlays.addOverlay("shape", { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), + localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + dimensions: faceHandleDimensions, + shape: "Cone", + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, + visible: true + }); + } } } else { faceHandleOverlays = []; diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 1d7fac9ed8..8023775242 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -29,7 +29,8 @@ Selection = function (side) { scaleRootOffset, scaleRootOrientation, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"]; + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"], + ENTITY_TYPES_2D = ["Text", "Web"]; if (!this instanceof Selection) { @@ -41,14 +42,15 @@ Selection = function (side) { // The root entity is always the first entry. var children, properties, - SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions", "parentID", "localPosition", - "dynamic", "collisionless", "userData"], + SELECTION_PROPERTIES = ["type", "position", "registrationPoint", "rotation", "dimensions", "parentID", + "localPosition", "dynamic", "collisionless", "userData"], i, length; properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES); result.push({ id: id, + type: properties.type, position: properties.position, parentID: properties.parentID, localPosition: properties.localPosition, @@ -190,6 +192,10 @@ Selection = function (side) { }; } + function is2D() { + return selection.length === 1 && ENTITY_TYPES_2D.indexOf(selection[0].type) !== -1; + } + function doKick(entityID) { var properties, NO_KICK_ENTITY_TYPES = ["Text", "Web"], // These entities don't respond to gravity so don't kick them. @@ -278,6 +284,7 @@ Selection = function (side) { function directScale(factor, rotation, center) { // Scale, position, and rotate selection. + // We can get away with scaling the z size of 2D entities - incongruities are barely noticeable and things recover. var i, length; @@ -308,7 +315,7 @@ Selection = function (side) { // Update selection with final entity properties. var i, length; - // Final scale, position, and orientaation of root. + // Final scale, position, and orientation of root. rootPosition = Vec3.sum(scaleCenter, Vec3.multiply(scaleFactor, Vec3.multiplyQbyV(scaleRotation, scaleRootOffset))); rootOrientation = Quat.multiply(scaleRotation, scaleRootOrientation); selection[0].dimensions = Vec3.multiply(scaleFactor, selection[0].dimensions); @@ -329,6 +336,7 @@ Selection = function (side) { function handleScale(factor, position, orientation) { // Scale and reposition and orient selection. + // We can get away with scaling the z size of 2D entities - incongruities are barely noticeable and things recover. var i, length; @@ -526,6 +534,7 @@ Selection = function (side) { intersectedEntityIndex: getIntersectedEntityIndex, rootEntityID: getRootEntityID, boundingBox: getBoundingBox, + is2D: is2D, getPositionAndOrientation: getPositionAndOrientation, setPositionAndOrientation: setPositionAndOrientation, startEditing: startEditing, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 26fbfd392e..01de12c07c 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -719,7 +719,7 @@ laser.disable(); } if (toolSelected === TOOL_SCALE) { - handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); + handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1, selection.is2D()); otherEditor.setHandleOverlays(handles.overlays()); } startEditing(); @@ -729,7 +729,7 @@ function updateEditorGrabbing() { selection.select(intersectedEntityID); if (toolSelected === TOOL_SCALE) { - handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1); + handles.display(rootEntityID, selection.boundingBox(), selection.count() > 1, selection.is2D()); otherEditor.setHandleOverlays(handles.overlays()); } else { handles.clear(); From ec6cae4105767401543339468bc37302d948aaff Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 17:04:50 +1200 Subject: [PATCH 301/504] Fix physics "grabbable" setting being applied without gravity --- scripts/vr-edit/vr-edit.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 01de12c07c..38f72950f8 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1470,16 +1470,20 @@ break; case "setGravityOn": + // Dynamic is true if the entity has gravity or is grabbable. if (parameter) { physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; physicsToolPhysics.dynamic = true; } else { physicsToolPhysics.gravity = Vec3.ZERO; - physicsToolPhysics.dynamic = false; + physicsToolPhysics.dynamic = physicsToolPhysics.userData.grabbableKey.grabbable === true; } break; case "setGrabOn": + // Dynamic is true if the entity has gravity or is grabbable. physicsToolPhysics.userData.grabbableKey.grabbable = parameter; + physicsToolPhysics.dynamic = parameter + || (physicsToolPhysics.gravity && Vec3.length(physicsToolPhysics.gravity) > 0); break; case "setCollideOn": if (parameter) { From be049009c8023d3d74805d259351544e132a600e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 17:51:58 +1200 Subject: [PATCH 302/504] Improve sizing of scale handles --- scripts/vr-edit/modules/handles.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index 8e9c5694fa..4c11cdfd8d 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -171,9 +171,9 @@ Handles = function (side) { // display smaller in order to give comfortable depth cue. cameraPosition = Camera.position; boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); - distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER - * Vec3.dot(Quat.getForward(Camera.orientation), boundingBoxVector) - / Math.sqrt(Vec3.length(boundingBoxVector)); + distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER * Math.sqrt( + Math.max(Vec3.length(boundingBoxVector, Vec3.length(boundingBox.dimensions) / 2)) + ); // Corner scale handles. // At right-most and opposite corners of bounding box. From afb47dcfb232108261c9968e5d12cc72d62982d7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 12 Sep 2017 20:43:11 +1200 Subject: [PATCH 303/504] Add undo and redo buttons to Tools menu --- scripts/vr-edit/assets/tools/redo-icon.svg | 7 ++ scripts/vr-edit/assets/tools/redo-label.svg | 10 ++ scripts/vr-edit/assets/tools/undo-icon.svg | 7 ++ scripts/vr-edit/assets/tools/undo-label.svg | 10 ++ scripts/vr-edit/modules/toolsMenu.js | 115 ++++++++++++++++++-- 5 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/redo-icon.svg create mode 100644 scripts/vr-edit/assets/tools/redo-label.svg create mode 100644 scripts/vr-edit/assets/tools/undo-icon.svg create mode 100644 scripts/vr-edit/assets/tools/undo-label.svg diff --git a/scripts/vr-edit/assets/tools/redo-icon.svg b/scripts/vr-edit/assets/tools/redo-icon.svg new file mode 100644 index 0000000000..bd0cea1330 --- /dev/null +++ b/scripts/vr-edit/assets/tools/redo-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/scripts/vr-edit/assets/tools/redo-label.svg b/scripts/vr-edit/assets/tools/redo-label.svg new file mode 100644 index 0000000000..19ae558bb9 --- /dev/null +++ b/scripts/vr-edit/assets/tools/redo-label.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/scripts/vr-edit/assets/tools/undo-icon.svg b/scripts/vr-edit/assets/tools/undo-icon.svg new file mode 100644 index 0000000000..566de28906 --- /dev/null +++ b/scripts/vr-edit/assets/tools/undo-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/scripts/vr-edit/assets/tools/undo-label.svg b/scripts/vr-edit/assets/tools/undo-label.svg new file mode 100644 index 0000000000..ca749f2765 --- /dev/null +++ b/scripts/vr-edit/assets/tools/undo-label.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 43056954e4..240dbfcfbc 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -252,8 +252,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Relative to menuButton. type: "image", properties: { - url: "../assets/tools/tool-label.svg", - scale: 0.0152, localPosition: { x: 0, y: UIT.dimensions.menuButtonSublabelYOffset, @@ -1660,6 +1658,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0241 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/color-tool-heading.svg", scale: 0.0631 @@ -1693,6 +1697,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0311 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/stretch-tool-heading.svg", scale: 0.0737 @@ -1725,6 +1735,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0231 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/clone-tool-heading.svg", scale: 0.0621 @@ -1757,6 +1773,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0250 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/group-tool-heading.svg", scale: 0.0647 @@ -1789,6 +1811,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0297 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/physics-tool-heading.svg", scale: 0.0712 @@ -1821,6 +1849,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: 0.0254 } }, + sublabel: { + properties: { + url: "../assets/tools/tool-label.svg", + scale: 0.0152 + } + }, title: { url: "../assets/tools/delete-tool-heading.svg", scale: 0.0653 @@ -1829,6 +1863,58 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: { method: "deleteTool" } + }, + { + id: "undoButton", + type: "menuButton", + properties: { + localPosition: { + x: MENU_ITEM_XS[2], + y: MENU_ITEM_YS[2], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + properties: { + url: "../assets/tools/undo-icon.svg", + dimensions: { x: 0.0180, y: 0.0186 } + } + }, + label: { + properties: { + url: "../assets/tools/undo-label.svg", + scale: 0.0205 + } + }, + callback: { + method: "undoAction" + } + }, + { + id: "redoButton", + type: "menuButton", + properties: { + localPosition: { + x: MENU_ITEM_XS[3], + y: MENU_ITEM_YS[2], + z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 + } + }, + icon: { + properties: { + url: "../assets/tools/redo-icon.svg", + dimensions: { x: 0.0180, y: 0.0186 } + } + }, + label: { + properties: { + url: "../assets/tools/redo-label.svg", + scale: 0.0192 + } + }, + callback: { + method: "redoAction" + } } ], COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. @@ -1917,10 +2003,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function getIconInfo(tool) { // Provides details of tool icon, label, and sublabel images for the specified tool. + var sublabelProperties; + + sublabelProperties = Object.clone(UI_ELEMENTS.menuButton.sublabel); + sublabelProperties = Object.merge(sublabelProperties, MENU_ITEMS[tool].sublabel); return { icon: MENU_ITEMS[tool].icon, label: MENU_ITEMS[tool].label, - sublabel: UI_ELEMENTS.menuButton.sublabel + sublabel: sublabelProperties }; } @@ -1977,13 +2067,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuLabelOverlays.push(overlayID); // Sublabel. - properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].properties); - properties = Object.merge(properties, UI_ELEMENTS.menuButton.sublabel.properties); - properties.url = Script.resolvePath(properties.url); - properties.visible = isVisible; - properties.parentID = itemID; - overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].overlay, properties); - menuLabelOverlays.push(overlayID); + if (MENU_ITEMS[i].sublabel) { + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.sublabel.properties); + properties = Object.merge(properties, MENU_ITEMS[i].sublabel.properties); + properties.url = Script.resolvePath(properties.url); + properties.visible = isVisible; + properties.parentID = itemID; + overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].overlay, properties); + menuLabelOverlays.push(overlayID); + } } } @@ -3086,7 +3179,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }; // Button press actions. - if (intersectionOverlays === menuOverlays) { + if (intersectionOverlays === menuOverlays && intersectionItems[intersectedItem].toolOptions) { openOptions(intersectionItems[intersectedItem]); } if (intersectionItems[intersectedItem].command) { From 68c4a2f7f60d3617c3b187f44f7bf44a2f5ddd55 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 12 Sep 2017 20:53:15 +1200 Subject: [PATCH 304/504] Add History object to handle history --- scripts/vr-edit/modules/feedback.js | 6 ++ scripts/vr-edit/modules/history.js | 99 +++++++++++++++++++++++++++++ scripts/vr-edit/vr-edit.js | 18 ++++++ 3 files changed, 123 insertions(+) create mode 100644 scripts/vr-edit/modules/history.js diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index 23a82e66aa..e87a0867fa 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -23,6 +23,8 @@ Feedback = (function () { CREATE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/create.wav")), EQUIP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/equip.wav")), ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")), + UNDO_SOUND = DROP_SOUND, // TODO + REDO_SOUND = DROP_SOUND, // TODO FEEDBACK_PARAMETERS = { DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, @@ -35,6 +37,8 @@ Feedback = (function () { EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, + UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.2, haptic: 0.2 }, // TODO + REDO_ACTION: { sound: REDO_SOUND, volume: 0.2, haptic: 0.2 }, // TODO GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, @@ -68,6 +72,8 @@ Feedback = (function () { EQUIP_TOOL: "EQUIP_TOOL", APPLY_PROPERTY: "APPLY_PROPERTY", APPLY_ERROR: "APPLY_ERROR", + UNDO_ACTION: "UNDO_ACTION", + REDO_ACTION: "REDO_ACTION", GENERAL_ERROR: "GENERAL_ERROR", play: play }; diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js new file mode 100644 index 0000000000..d1c03c218a --- /dev/null +++ b/scripts/vr-edit/modules/history.js @@ -0,0 +1,99 @@ +// +// history.js +// +// Created by David Rowe on 12 Sep 2017. +// Copyright 2017 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 +// + +/* global History */ + +History = (function () { + // Provides undo facility. + // Global object. + + "use strict"; + + var history = [ + /* + { + undoData: { + setProperties: [ + { + entityID: , + properties: { : , : , ... } + } + ], + createEntities: [ + { + entityID: , + properties: { : , : , ... } + } + ], + deleteEntities: [ + { + entityID: , + properties: { : , : , ... } + } + ] + }, + redoData: { + "" + } + } + */ + ], + MAX_HISTORY_ITEMS = 100, + undoPosition = -1; // The next history item to undo; the next history item to redo = undoIndex + 1. + + function push(undoData, redoData) { + // Wipe any redo history after current undo position. + if (undoPosition < history.length - 1) { + history.splice(undoPosition + 1, history.length - undoPosition - 1); + } + + // Limit the number of history items. + if (history.length >= MAX_HISTORY_ITEMS) { + history.splice(0, history.length - MAX_HISTORY_ITEMS + 1); + } + + history.push({ undoData: undoData, redoData: redoData }); + undoPosition += 1; + } + + function hasUndo() { + return undoPosition > -1; + } + + function hasRedo() { + return undoPosition < history.length - 1; + } + + function undo() { + if (undoPosition > -1) { + + // TODO + + undoPosition -= 1; + } + } + + function redo() { + if (undoPosition < history.length - 1) { + + // TODO + + undoPosition += 1; + } + } + + return { + push: push, + hasUndo: hasUndo, + hasRedo: hasRedo, + undo: undo, + redo: redo + }; +}()); diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 38f72950f8..293e85a775 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -83,6 +83,7 @@ Script.include("./modules/hand.js"); Script.include("./modules/handles.js"); Script.include("./modules/highlights.js"); + Script.include("./modules/history.js"); Script.include("./modules/laser.js"); Script.include("./modules/selection.js"); Script.include("./modules/toolIcon.js"); @@ -1535,6 +1536,23 @@ } break; + case "undoAction": + if (History.hasUndo()) { + Feedback.play(dominantHand, Feedback.UNDO_ACTION) + History.undo(); + } else { + Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + } + break; + case "redoAction": + if (History.hasRedo()) { + Feedback.play(dominantHand, Feedback.REDO_ACTION) + History.redo(); + } else { + Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + } + break; + default: log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); } From 3bb3e57f71ba943d40342fd5872387bd44a768d1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 12 Sep 2017 20:59:03 +1200 Subject: [PATCH 305/504] Add undo/redo for creating entities --- scripts/vr-edit/modules/createPalette.js | 12 ++++- scripts/vr-edit/modules/history.js | 60 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 8e46783979..44e4373476 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -317,7 +317,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { isTriggerClicked, properties, CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, - INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }); + INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + entityID, + createdEntities; itemIndex = paletteItemOverlays.indexOf(intersectionOverlayID); @@ -350,7 +352,13 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { Vec3.multiplyQbyV(controlHand.orientation(), Vec3.sum({ x: 0, y: properties.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); - Entities.addEntity(properties); + entityID = Entities.addEntity(properties); + if (entityID !== Uuid.NULL) { + createdEntities = [{ entityID: entityID, properties: properties }]; + History.push({ deleteEntities: createdEntities }, { createEntities: createdEntities }); + } else { + Feedback.play(otherSide, Feedback.GENERAL_ERROR); + } // Lower and unhighlight item. Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index d1c03c218a..a12d804e16 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -63,6 +63,41 @@ History = (function () { undoPosition += 1; } + function updateEntityIDs(oldEntityID, newEntityID) { + // Replace oldEntityID value with newEntityID in history. + var i, + length; + + function updateEntityIDsInProperty(properties) { + var i, + length; + + if (properties) { + for (i = 0, length = properties.length; i < length; i += 1) { + if (properties[i].entityID === oldEntityID) { + properties[i].entityID = newEntityID; + } + if (properties[i].properties && properties[i].properties.parentID === oldEntityID) { + properties[i].properties.parentID = newEntityID; + } + } + } + } + + for (i = 0, length = history.length; i < length; i += 1) { + if (history[i].undoData) { + updateEntityIDsInProperty(history[i].undoData.setProperties); + updateEntityIDsInProperty(history[i].undoData.createEntities); + updateEntityIDsInProperty(history[i].undoData.deleteEntities); + } + if (history[i].redoData) { + updateEntityIDsInProperty(history[i].redoData.setProperties); + updateEntityIDsInProperty(history[i].redoData.createEntities); + updateEntityIDsInProperty(history[i].redoData.deleteEntities); + } + } + } + function hasUndo() { return undoPosition > -1; } @@ -72,19 +107,44 @@ History = (function () { } function undo() { + var undoData, + i, + length; + if (undoPosition > -1) { + undoData = history[undoPosition].undoData; // TODO + if (undoData.deleteEntities) { + for (i = 0, length = undoData.deleteEntities.length; i < length; i += 1) { + Entities.deleteEntity(undoData.deleteEntities[i].entityID); + } + } + undoPosition -= 1; } } function redo() { + var redoData, + entityID, + i, + length; + + if (undoPosition < history.length - 1) { + redoData = history[undoPosition + 1].redoData; // TODO + if (redoData.createEntities) { + for (i = 0, length = redoData.createEntities.length; i < length; i += 1) { + entityID = Entities.addEntity(redoData.createEntities[i].properties); + updateEntityIDs(redoData.createEntities[i].entityID, entityID); + } + } + undoPosition += 1; } } From 93a5cae7d2d81d4f84b419c904d4419ef3c329d8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 12 Sep 2017 21:39:30 +1200 Subject: [PATCH 306/504] Add undo/redo for deleting entities --- scripts/vr-edit/modules/history.js | 14 ++++++++++++++ scripts/vr-edit/modules/selection.js | 21 ++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index a12d804e16..b31009ab2f 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -108,6 +108,7 @@ History = (function () { function undo() { var undoData, + entityID, i, length; @@ -116,6 +117,13 @@ History = (function () { // TODO + if (undoData.createEntities) { + for (i = 0, length = undoData.createEntities.length; i < length; i += 1) { + entityID = Entities.addEntity(undoData.createEntities[i].properties); + updateEntityIDs(undoData.createEntities[i].entityID, entityID); + } + } + if (undoData.deleteEntities) { for (i = 0, length = undoData.deleteEntities.length; i < length; i += 1) { Entities.deleteEntity(undoData.deleteEntities[i].entityID); @@ -145,6 +153,12 @@ History = (function () { } } + if (redoData.deleteEntities) { + for (i = 0, length = redoData.deleteEntities.length; i < length; i += 1) { + Entities.deleteEntity(redoData.deleteEntities[i].entityID); + } + } + undoPosition += 1; } } diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 8023775242..be9ccd689c 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -15,7 +15,8 @@ Selection = function (side) { "use strict"; - var selection = [], + var selection = [], // Subset of properties to provide externally. + selectionProperties = [], // Full set of properties for history. intersectedEntityID = null, intersectedEntityIndex, rootEntityID = null, @@ -37,18 +38,17 @@ Selection = function (side) { return new Selection(side); } - function traverseEntityTree(id, result) { + function traverseEntityTree(id, selection, selectionProperties) { // Recursively traverses tree of entities and their children, gather IDs and properties. // The root entity is always the first entry. var children, properties, - SELECTION_PROPERTIES = ["type", "position", "registrationPoint", "rotation", "dimensions", "parentID", - "localPosition", "dynamic", "collisionless", "userData"], i, length; - properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES); - result.push({ + properties = Entities.getEntityProperties(id); + delete properties.entityID; + selection.push({ id: id, type: properties.type, position: properties.position, @@ -61,15 +61,16 @@ Selection = function (side) { collisionless: properties.collisionless, userData: properties.userData }); + selectionProperties.push({ entityID: id, properties: properties }); if (id === intersectedEntityID) { - intersectedEntityIndex = result.length - 1; + intersectedEntityIndex = selection.length - 1; } children = Entities.getChildrenIDs(id); for (i = 0, length = children.length; i < length; i += 1) { if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { - traverseEntityTree(children[i], result); + traverseEntityTree(children[i], selection, selectionProperties); } } } @@ -90,7 +91,8 @@ Selection = function (side) { // Find all children. selection = []; - traverseEntityTree(rootEntityID, selection); + selectionProperties = []; + traverseEntityTree(rootEntityID, selection, selectionProperties); } function getIntersectedEntityID() { @@ -517,6 +519,7 @@ Selection = function (side) { function deleteEntities() { if (rootEntityID) { + History.push({ createEntities: selectionProperties }, { deleteEntities: [{ entityID: rootEntityID }] }); Entities.deleteEntity(rootEntityID); // Children are automatically deleted. clear(); } From 922b712b35510d5411750686da0135eba41937fe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 12 Sep 2017 21:58:09 +1200 Subject: [PATCH 307/504] Add undo/redo for coloring entities --- scripts/vr-edit/modules/history.js | 12 ++++++++++-- scripts/vr-edit/modules/selection.js | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index b31009ab2f..6fb5416df3 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -115,7 +115,11 @@ History = (function () { if (undoPosition > -1) { undoData = history[undoPosition].undoData; - // TODO + if (undoData.setProperties) { + for (i = 0, length = undoData.setProperties.length; i < length; i += 1) { + Entities.editEntity(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); + } + } if (undoData.createEntities) { for (i = 0, length = undoData.createEntities.length; i < length; i += 1) { @@ -144,7 +148,11 @@ History = (function () { if (undoPosition < history.length - 1) { redoData = history[undoPosition + 1].redoData; - // TODO + if (redoData.setProperties) { + for (i = 0, length = redoData.setProperties.length; i < length; i += 1) { + Entities.editEntity(redoData.setProperties[i].entityID, redoData.setProperties[i].properties); + } + } if (redoData.createEntities) { for (i = 0, length = redoData.createEntities.length; i < length; i += 1) { diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index be9ccd689c..0337749a8f 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -424,25 +424,36 @@ Selection = function (side) { // Entities without a color property simply ignore the edit. var properties, isOK = false, + undoData = [], + redoData = [], i, length; if (isApplyToAll) { for (i = 0, length = selection.length; i < length; i += 1) { - properties = Entities.getEntityProperties(selection[i].id, "color"); + properties = Entities.getEntityProperties(selection[i].id, ["type", "color"]); if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { Entities.editEntity(selection[i].id, { color: color }); + undoData.push({ entityID: intersectedEntityID, properties: { color: properties.color } }); + redoData.push({ entityID: intersectedEntityID, properties: { color: color } }); isOK = true; } } + if (undoData.length > 0) { + History.push(undoData, redoData); + } } else { - properties = Entities.getEntityProperties(intersectedEntityID, "type"); + properties = Entities.getEntityProperties(intersectedEntityID, ["type", "color"]); if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { Entities.editEntity(intersectedEntityID, { color: color }); + History.push( + { setProperties: [{ entityID: intersectedEntityID, properties: { color: properties.color } }] }, + { setProperties: [{ entityID: intersectedEntityID, properties: { color: color } }] } + ); isOK = true; } } From d718ea6928b4537b151174fc1d46a2daa42df096 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Sep 2017 09:17:28 +1200 Subject: [PATCH 308/504] Add undo/redo for move --- scripts/vr-edit/modules/selection.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 0337749a8f..c0881bbf01 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -29,6 +29,8 @@ Selection = function (side) { scaleOrientation, scaleRootOffset, scaleRootOrientation, + startPosition, + startOrientation, ENTITY_TYPE = "entity", ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"], ENTITY_TYPES_2D = ["Text", "Web"]; @@ -227,6 +229,10 @@ Selection = function (side) { function startEditing() { var i; + // Remember start properties for history entry. + startPosition = selection[0].position; + startOrientation = selection[0].rotation; + // Disable entity set's physics. //for (i = 0, count = selection.length; i < count; i += 1) { for (i = selection.length - 1; i >= 0; i -= 1) { @@ -254,6 +260,20 @@ Selection = function (side) { }); } + // Add history entry. + History.push( + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + // Kick off physics if necessary. if (selection.length > 0 && selection[0].dynamic) { kickPhysics(selection[0].id); From 97b29880050ed3fd498ebad9ac4730493ab27444 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Sep 2017 14:13:23 +1200 Subject: [PATCH 309/504] Miscellaneous fixes --- scripts/vr-edit/modules/feedback.js | 4 ++-- scripts/vr-edit/modules/selection.js | 14 ++++---------- scripts/vr-edit/modules/toolsMenu.js | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index e87a0867fa..f1456df5e6 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -37,8 +37,8 @@ Feedback = (function () { EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, - UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.2, haptic: 0.2 }, // TODO - REDO_ACTION: { sound: REDO_SOUND, volume: 0.2, haptic: 0.2 }, // TODO + UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, // TODO + REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, // TODO GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index c0881bbf01..02d4b56d47 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -23,8 +23,6 @@ Selection = function (side) { rootPosition, rootOrientation, scaleFactor, - scaleRotation, - scaleCenter, scalePosition, scaleOrientation, scaleRootOffset, @@ -327,10 +325,8 @@ Selection = function (side) { }); } - // Save most recent scale parameters. + // Save most recent scale factor. scaleFactor = factor; - scaleRotation = rotation; - scaleCenter = center; } function finishDirectScaling() { @@ -338,8 +334,6 @@ Selection = function (side) { var i, length; // Final scale, position, and orientation of root. - rootPosition = Vec3.sum(scaleCenter, Vec3.multiply(scaleFactor, Vec3.multiplyQbyV(scaleRotation, scaleRootOffset))); - rootOrientation = Quat.multiply(scaleRotation, scaleRootOrientation); selection[0].dimensions = Vec3.multiply(scaleFactor, selection[0].dimensions); selection[0].position = rootPosition; selection[0].rotation = rootOrientation; @@ -394,8 +388,8 @@ Selection = function (side) { // Final scale and position of root. selection[0].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[0].dimensions); - selection[0].position = scalePosition; - selection[0].rotation = scaleOrientation; + selection[0].position = rootPosition; + selection[0].rotation = rootOrientation; // Final scale and position of children. for (i = 1, length = selection.length; i < length; i += 1) { @@ -462,7 +456,7 @@ Selection = function (side) { } } if (undoData.length > 0) { - History.push(undoData, redoData); + History.push({ setProperties: undoData }, { setProperties: redoData }); } } else { properties = Entities.getEntityProperties(intersectedEntityID, ["type", "color"]); diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 240dbfcfbc..2b93c215d4 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1561,7 +1561,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - id: "deleeteRule1", + id: "deleteRule1", type: "horizontalRule", properties: { localPosition: { From 8c9026c89207e084a872ce1e1f1b5842ca332383 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Sep 2017 14:13:48 +1200 Subject: [PATCH 310/504] Add undo/redo for scaling --- scripts/vr-edit/modules/selection.js | 134 ++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 02d4b56d47..fdc413bdab 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -31,7 +31,10 @@ Selection = function (side) { startOrientation, ENTITY_TYPE = "entity", ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"], - ENTITY_TYPES_2D = ["Text", "Web"]; + ENTITY_TYPES_2D = ["Text", "Web"], + MIN_HISTORY_MOVE_DISTANCE = 0.005, + MIN_HISTORY_ROTATE_ANGLE = 0.017453; // Radians = 1 degree. + if (!this instanceof Selection) { @@ -300,6 +303,26 @@ Selection = function (side) { // Save initial position and orientation so that can scale relative to these without accumulating float errors. scaleRootOffset = Vec3.subtract(rootPosition, center); scaleRootOrientation = rootOrientation; + + // User is grabbing entity; add a history entry for movement up until the start of scaling and update start position and + // orientation; unless very small movement. + if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE + || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { + History.push( + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + startPosition = rootPosition; + startOrientation = rootOrientation; + } } function directScale(factor, rotation, center) { @@ -331,23 +354,86 @@ Selection = function (side) { function finishDirectScaling() { // Update selection with final entity properties. - var i, + var undoData = [], + redoData = [], + i, length; + // Final scale, position, and orientation of root. + undoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: startPosition, + rotation: startOrientation + } + }); selection[0].dimensions = Vec3.multiply(scaleFactor, selection[0].dimensions); selection[0].position = rootPosition; selection[0].rotation = rootOrientation; + redoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: selection[0].position, + rotation: selection[0].rotation + } + }); // Final scale and position of children. for (i = 1, length = selection.length; i < length; i += 1) { + undoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition + } + }); selection[i].dimensions = Vec3.multiply(scaleFactor, selection[i].dimensions); selection[i].localPosition = Vec3.multiply(scaleFactor, selection[i].localPosition); + redoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition + } + }); } + + // Add history entry. + History.push( + { setProperties: undoData }, + { setProperties: redoData } + ); + + // Update grab start data for its undo. + startPosition = rootPosition; + startOrientation = rootOrientation; } function startHandleScaling(position) { // Save initial offset from hand position to root position so that can scale without accumulating float errors. scaleRootOffset = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(rootPosition, position)); + + // User is grabbing entity; add a history entry for movement up until the start of scaling and update start position and + // orientation; unless very small movement. + if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE + || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { + History.push( + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + startPosition = rootPosition; + startOrientation = rootOrientation; + } } function handleScale(factor, position, orientation) { @@ -383,19 +469,61 @@ Selection = function (side) { function finishHandleScaling() { // Update selection with final entity properties. - var i, + var undoData = [], + redoData = [], + i, length; // Final scale and position of root. + undoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: startPosition, + rotation: startOrientation + } + }); selection[0].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[0].dimensions); selection[0].position = rootPosition; selection[0].rotation = rootOrientation; + redoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: selection[0].position, + rotation: selection[0].rotation + } + }); // Final scale and position of children. for (i = 1, length = selection.length; i < length; i += 1) { + undoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition + } + }); selection[i].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[i].dimensions); selection[i].localPosition = Vec3.multiplyVbyV(scaleFactor, selection[i].localPosition); + redoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition + } + }); } + + // Add history entry. + History.push( + { setProperties: undoData }, + { setProperties: redoData } + ); + + // Update grab start data for its undo. + startPosition = rootPosition; + startOrientation = rootOrientation; } function cloneEntities() { From 003c1c7bc59889170a5f7aefff91c4f5939700cc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Sep 2017 15:16:16 +1200 Subject: [PATCH 311/504] Add undo/redo for group/ungroup --- scripts/vr-edit/modules/groups.js | 62 ++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index fd78b1ef76..c3e4172ebe 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -83,18 +83,33 @@ Groups = function () { // Groups all selections into one. var DYNAMIC_AND_COLLISIONLESS = { dynamic: true, collisionless: true }, rootID, + undoData = [], + redoData = [], i, lengthI, j, lengthJ; // If the first group has physics (i.e., root entity is dynamic) make all entities in child groups dynamic and - // collisionless. (Don't need to worry about other groups physics properties because only those of the the first entity - // in the linkset are used by High Fidelity.) See Selection.applyPhysics(). + // collisionless. (Don't need to worry about other groups physics properties because only those of the first entity in + // the linkset are used by High Fidelity.) See Selection.applyPhysics(). if (selections[0][0].dynamic) { for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + undoData.push({ + entityID: selections[i][j].id, + properties: { + dynamic: selections[i][j].dynamic, + collisionless: selections[i][j].collisionless + } + }); Entities.editEntity(selections[i][j].id, DYNAMIC_AND_COLLISIONLESS); + selections[i][j].dynamic = true; + selections[i][j].collisionless = true; + redoData.push({ + entityID: selections[i][j].id, + properties: DYNAMIC_AND_COLLISIONLESS + }); } } } @@ -102,9 +117,17 @@ Groups = function () { // Make the first entity in the first group the root and link the first entities of all other groups to it. rootID = rootEntityIDs[0]; for (i = 1, lengthI = rootEntityIDs.length; i < lengthI; i += 1) { + undoData.push({ + entityID: rootEntityIDs[i], + properties: { parentID: Uuid.NULL } + }); Entities.editEntity(rootEntityIDs[i], { parentID: rootID }); + redoData.push({ + entityID: rootEntityIDs[i], + properties: { parentID: rootID } + }); } // Update selection. @@ -114,6 +137,12 @@ Groups = function () { selections[0] = selections[0].concat(selections[i]); } selections.splice(1, selections.length - 1); + + // Add history entry. + History.push( + { setProperties: undoData }, + { setProperties: redoData } + ); } function ungroup() { @@ -129,6 +158,8 @@ Groups = function () { hasGroupChildren = false, isUngroupAll, NONDYNAMIC_AND_NONCOLLISIONLESS = { dynamic: false, collisionless: false }, + undoData = [], + redoData = [], i, lengthI, j, @@ -172,9 +203,17 @@ Groups = function () { isUngroupAll = hasSoloChildren !== hasGroupChildren; for (i = childrenIDs.length - 1; i >= 0; i -= 1) { if (isUngroupAll || childrenIndexIsGroup[i]) { + undoData.push({ + entityID: childrenIDs[i], + properties: { parentID: selections[0][childrenIndexes[i]].parentID } + }); Entities.editEntity(childrenIDs[i], { parentID: Uuid.NULL }); + redoData.push({ + entityID: childrenIDs[i], + properties: { parentID: Uuid.NULL } + }); rootEntityIDs.push(childrenIDs[i]); selections[0][childrenIndexes[i]].parentID = Uuid.NULL; selections.push(selections[0].splice(childrenIndexes[i], childrenIndexes[i + 1] - childrenIndexes[i])); @@ -186,10 +225,29 @@ Groups = function () { if (selections[0][0].dynamic) { for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + undoData.push({ + entityID: selections[i][j].id, + properties: { + dynamic: selections[i][j].dynamic, + collisionless: selections[i][j].collisionless + } + }); Entities.editEntity(selections[i][j].id, NONDYNAMIC_AND_NONCOLLISIONLESS); + selections[i][j].dynamic = false; + selections[i][j].collisionless = false; + redoData.push({ + entityID: selections[i][j].id, + properties: NONDYNAMIC_AND_NONCOLLISIONLESS + }); } } } + + // Add history entry. + History.push( + { setProperties: undoData }, + { setProperties: redoData } + ); } function clear() { From 709e7e7d90a0a308092b503236d2e63b2b77af4e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Sep 2017 15:25:40 +1200 Subject: [PATCH 312/504] Add undo/redo for clone --- scripts/vr-edit/modules/selection.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index fdc413bdab..60ef02285b 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -531,6 +531,8 @@ Selection = function (side) { intersectedEntityIndex = 0, parentID, properties, + undoData = [], + redoData = [], i, j, length; @@ -556,10 +558,19 @@ Selection = function (side) { properties.parentID = selection[parentIDIndexes[i]].id; } selection[i].id = Entities.addEntity(properties); + undoData.push({ entityID: selection[i].id }); + redoData.push({ entityID: selection[i].id, properties: properties }); } + // Update selection info. intersectedEntityID = selection[intersectedEntityIndex].id; rootEntityID = selection[0].id; + + // Add history entry. + History.push( + { deleteEntities: undoData }, + { createEntities: redoData } + ); } function applyColor(color, isApplyToAll) { From aab63bf1098028a9be4c2b8dadf50d35facb1147 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 13 Sep 2017 14:32:22 +0200 Subject: [PATCH 313/504] Added support for R11G11B10F and RGB9E5 cubemaps. Weird colors though but it doesn't crash --- libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp | 23 +++ .../src/gpu/gl41/GL41BackendTexture.cpp | 2 + .../src/gpu/gl45/GL45BackendTexture.cpp | 2 + libraries/gpu/src/gpu/Format.cpp | 4 +- libraries/gpu/src/gpu/Format.h | 6 + libraries/gpu/src/gpu/Texture.cpp | 48 +++--- libraries/image/src/image/Image.cpp | 161 ++++++++++++++---- libraries/image/src/image/Image.h | 7 +- 8 files changed, 199 insertions(+), 54 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index ef9b6c4297..8d4259e240 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -19,6 +19,7 @@ bool GLTexelFormat::isCompressed() const { case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: return true; default: return false; @@ -94,6 +95,11 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + // the type should be float + result = GL_RGB9_E5; + break; + case gpu::DEPTH: result = GL_DEPTH_COMPONENT32; switch (dstFormat.getType()) { @@ -244,6 +250,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC5_XY: result = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + result = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -396,6 +405,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -495,10 +507,18 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::R11G11B10: texel.format = GL_RGB; + texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; // the type should be float texel.internalFormat = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + texel.format = GL_RGB; + texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; + // the type should be float + texel.internalFormat = GL_RGB9_E5; + break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT32; @@ -694,6 +714,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 5998aebb33..cde4ff5f75 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -114,6 +114,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -131,6 +132,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index d8b3968ed8..795a630ccd 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -143,6 +143,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -158,6 +159,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index b15f8d929f..7efe4d3ed6 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -24,11 +24,13 @@ const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_B const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; -const Element Element::COLOR_COMPRESSED_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 }; +const Element Element::COLOR_RGB9E5{ SCALAR, FLOAT, RGB9E5 }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index c4d88236da..0654b23581 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -187,11 +187,13 @@ enum Semantic : uint8_t { COMPRESSED_BC3_SRGBA, COMPRESSED_BC4_RED, COMPRESSED_BC5_XY, + COMPRESSED_BC6_RGB, COMPRESSED_BC7_SRGBA, _LAST_COMPRESSED, R11G11B10, + RGB9E5, UNIFORM, UNIFORM_BUFFER, @@ -240,11 +242,13 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = { 16, //COMPRESSED_BC3_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 8, //COMPRESSED_BC4_RED, 1/2 byte/pixel * 4x4 pixels = 8 bytes 16, //COMPRESSED_BC5_XY, 1 byte/pixel * 4x4 pixels = 16 bytes + 16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes 16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 1, //_LAST_COMPRESSED, 1, //R11G11B10, + 1, //RGB9E5 1, //UNIFORM, 1, //UNIFORM_BUFFER, @@ -306,12 +310,14 @@ public: static const Element COLOR_BGRA_32; static const Element COLOR_SBGRA_32; static const Element COLOR_R11G11B10; + static const Element COLOR_RGB9E5; static const Element COLOR_COMPRESSED_RED; static const Element COLOR_COMPRESSED_SRGB; static const Element COLOR_COMPRESSED_SRGBA_MASK; static const Element COLOR_COMPRESSED_SRGBA; static const Element COLOR_COMPRESSED_XY; static const Element COLOR_COMPRESSED_SRGBA_HIGH; + static const Element COLOR_COMPRESSED_HDR_RGB; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4b836512c4..d817d39883 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -683,6 +684,22 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); + auto mipFormat = cubeTexture.getStoredMipFormat(); + std::function unpackFunc; + switch (mipFormat.getSemantic()) + { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; + } + assert(mipFormat.getSemantic() == gpu::R11G11B10); + const uint sqOrder = order*order; // allocate memory for calculations @@ -716,17 +733,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); - auto mipFormat = cubeTexture.getStoredMipFormat(); - auto numComponents = mipFormat.getScalarCount(); - int roffset { 0 }; - int goffset { 1 }; - int boffset { 2 }; - if ((mipFormat.getSemantic() == gpu::BGRA) || (mipFormat.getSemantic() == gpu::SBGRA)) { - roffset = 2; - boffset = 0; - } - - auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); + auto data = (const uint32*)cubeTexture.accessStoredMipFace(0, face)->readData(); if (data == nullptr) { continue; } @@ -806,29 +813,24 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // index of texel in texture - // get color from texture and map to range [0, 1] - float red { 0.0f }; - float green { 0.0f }; - float blue { 0.0f }; + // get color from texture + glm::vec3 color{ 0.f, 0.f, 0.f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { - int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents; - red += ColorUtils::sRGB8ToLinearFloat(data[k + roffset]); - green += ColorUtils::sRGB8ToLinearFloat(data[k + goffset]); - blue += ColorUtils::sRGB8ToLinearFloat(data[k + boffset]); + int k = (int)(x + i - halfStride + (y + j - halfStride) * width); + color += unpackFunc(data[k]); } } - glm::vec3 clr(red, green, blue); // scale color and add to previously accumulated coefficients // red - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data()); // green - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.g * fDiffSolid); sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data()); // blue - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.b * fDiffSolid); sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data()); } } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index f274dc54f8..f51319fadf 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -12,6 +12,7 @@ #include "Image.h" #include +#include #include #include @@ -260,8 +261,6 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f return texture; } - - QImage processSourceImage(const QImage& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); const glm::uvec2 srcImageSize = toGlm(srcImage.size()); @@ -331,10 +330,81 @@ struct MyErrorHandler : public nvtt::ErrorHandler { } }; -void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { -#if CPU_MIPMAPS - PROFILE_RANGE(resource_parse, "generateMips"); +void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { + assert(image.format() == QIMAGE_HDR_FORMAT); + const int width = image.width(), height = image.height(); + std::vector data; + std::vector::iterator dataIt; + + data.resize(width*height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = (const uint32*) image.constScanLine(lineNb); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(glm::unpackF2x11_1x10(*srcPixelIt), 1.f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + auto mipFormat = texture->getStoredMipFormat(); + if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) { + compressionOptions.setFormat(nvtt::Format_BC6); + } + else if (mipFormat == gpu::Element::COLOR_RGB9E5) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_SharedExp); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(9, 9, 9, 5); + } + else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + // WARNING : With NVTT 2.1, using float 11/10 produces an assertion in the compressor + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(11, 11, 10, 0); + } + else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + MyOutputHandler outputHandler(texture, face); + outputOptions.setOutputHandler(&outputHandler); + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + nvtt::Context context; + int mipLevel = 0; + + nvtt::Surface surface; + surface.setImage(inputFormat, width, height, 1, &(*data.begin())); + surface.setAlphaMode(alphaMode); + surface.setWrapMode(wrapMode); + + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + while (surface.canMakeNextMipmap()) { + surface.buildNextMipmap(nvtt::MipmapFilter_Box); + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + } +} + +void generateLDRMips(gpu::Texture* texture, QImage& image, int face) { if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } @@ -388,10 +458,10 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { @@ -399,10 +469,10 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { @@ -410,19 +480,19 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { compressionOptions.setFormat(nvtt::Format_RGBA); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_R_8) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); @@ -449,6 +519,17 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { nvtt::Compressor compressor; compressor.process(inputOptions, compressionOptions, outputOptions); +} + +void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { +#if CPU_MIPMAPS + PROFILE_RANGE(resource_parse, "generateMips"); + + if (image.format() == QIMAGE_HDR_FORMAT) { + generateHDRMips(texture, image, face); + } else { + generateLDRMips(texture, image, face); + } #else texture->autoGenerateMips(-1); #endif @@ -926,24 +1007,46 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); +QImage convertToHDRFormat(QImage srcImage) { + QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + + srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < srcImage.height(); y++) { + const QRgb* srcLineIt = (const QRgb*) srcImage.constScanLine(y); + const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + uint32* hdrLineIt = (uint32*) hdrImage.scanLine(y); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color.r = qRed(*srcLineIt); + color.g = qGreen(*srcLineIt); + color.b = qBlue(*srcLineIt); + *hdrLineIt = glm::packF2x11_1x10(color / 255.f); + ++srcLineIt; + ++hdrLineIt; + } + } + return hdrImage; +} + gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); gpu::TexturePointer theTexture = nullptr; if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image); } gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = gpu::Element::COLOR_SRGBA_32; - formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = gpu::Element::COLOR_R11G11B10; + formatGPU = gpu::Element::COLOR_R11G11B10; } // Find the layout of the cubemap in the 2D image @@ -991,9 +1094,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_SRGBA_32, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_R11G11B10, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_SBGRA_32); + irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_R11G11B10); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 3bf45ace98..c8be097542 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -13,11 +13,11 @@ #define hifi_image_Image_h #include +#include #include class QByteArray; -class QImage; namespace image { @@ -74,6 +74,11 @@ void setNormalTexturesCompressionEnabled(bool enabled); void setGrayscaleTexturesCompressionEnabled(bool enabled); void setCubeTexturesCompressionEnabled(bool enabled); +enum +{ + QIMAGE_HDR_FORMAT = QImage::Format_RGB30 +}; + gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType); } // namespace image From 1c05311056cb64385ff93f69de0d66310174e0f4 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Wed, 13 Sep 2017 16:48:18 +0100 Subject: [PATCH 314/504] Some code cleanup --- interface/src/Application.cpp | 3 --- libraries/shared/src/shared/FileLogger.cpp | 16 +++++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fa141a44f..8de6d93dd6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -937,9 +937,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // you might think we could just do this in NodeList but we only want this connection for Interface connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); - // connect to appropriate slots on AccountManager - // auto accountManager = DependencyManager::get(); - auto dialogsManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index f712a341fe..50b0ccb43c 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -48,7 +48,7 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -QUuid SESSION_ID; +QUuid _sessionId; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. @@ -64,13 +64,13 @@ QString getLogRollerFilename() { QString result = FileUtils::standardPath(LOGS_DIRECTORY); QHostAddress clientAddress = getGuessedLocalAddress(); QDateTime now = QDateTime::currentDateTime(); - QString FILE_SESSION_ID; + QString fileSessionID; - if (!SESSION_ID.isNull()) { - FILE_SESSION_ID = "_" + SESSION_ID.toString().replace("{", "").replace("}", ""); + if (!_sessionId.isNull()) { + fileSessionID = "_" + _sessionId.toString().replace("{", "").replace("}", ""); } - result.append(QString(FILENAME_FORMAT).arg(FILE_SESSION_ID, now.toString(DATETIME_FORMAT))); + result.append(QString(FILENAME_FORMAT).arg(fileSessionID, now.toString(DATETIME_FORMAT))); return result; } @@ -151,8 +151,10 @@ FileLogger::~FileLogger() { } void FileLogger::setSessionID(const QUuid& message) { - SESSION_ID = message; // This is for the output of log files. It will change if the Avatar enters a different domain. -} + // This is for the output of log files. Once the application is first started, + // this function runs and grabs the AccountManager Session ID and saves it here. + _sessionId = message; + } void FileLogger::addMessage(const QString& message) { _persistThreadInstance->queueItem(message); From 2427008ff333ad534c2c0e3c14c7e3de0a4b90a5 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Wed, 13 Sep 2017 17:40:04 +0100 Subject: [PATCH 315/504] Static --- libraries/shared/src/shared/FileLogger.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 50b0ccb43c..b019b69fb8 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -26,7 +26,6 @@ class FilePersistThread : public GenericQueueThread < QString > { Q_OBJECT public: FilePersistThread(const FileLogger& logger); - signals: void rollingLogFile(QString newFilename); @@ -48,7 +47,7 @@ static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; -QUuid _sessionId; +static QUuid SESSION_ID; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. @@ -66,8 +65,8 @@ QString getLogRollerFilename() { QDateTime now = QDateTime::currentDateTime(); QString fileSessionID; - if (!_sessionId.isNull()) { - fileSessionID = "_" + _sessionId.toString().replace("{", "").replace("}", ""); + if (!SESSION_ID.isNull()) { + fileSessionID = "_" + SESSION_ID.toString().replace("{", "").replace("}", ""); } result.append(QString(FILENAME_FORMAT).arg(fileSessionID, now.toString(DATETIME_FORMAT))); @@ -153,7 +152,7 @@ FileLogger::~FileLogger() { void FileLogger::setSessionID(const QUuid& message) { // This is for the output of log files. Once the application is first started, // this function runs and grabs the AccountManager Session ID and saves it here. - _sessionId = message; + SESSION_ID = message; } void FileLogger::addMessage(const QString& message) { From b8065d46ce5f8a65968568dc1a2302954747da91 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 13 Sep 2017 19:15:51 +0200 Subject: [PATCH 316/504] Fixed bugs with conversion to packed floats --- libraries/gpu/src/gpu/Texture.cpp | 1 - libraries/image/src/image/Image.cpp | 171 +++++++++++++++++++++------- 2 files changed, 128 insertions(+), 44 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index d817d39883..4c2f4960c7 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -698,7 +698,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< assert(false); break; } - assert(mipFormat.getSemantic() == gpu::R11G11B10); const uint sqOrder = order*order; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index f51319fadf..8ada88f008 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -205,6 +205,20 @@ void setCubeTexturesCompressionEnabled(bool enabled) { compressCubeTextures.set(enabled); } +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.f : value; +} + +uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + return glm::packF2x11_1x10(ucolor); +} gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType) { // Help the QImage loader by extracting the image file format from the url filename ext. @@ -291,8 +305,8 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { return srcImage; } -struct MyOutputHandler : public nvtt::OutputHandler { - MyOutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { _size = size; @@ -310,7 +324,8 @@ struct MyOutputHandler : public nvtt::OutputHandler { virtual void endImage() override { if (_face >= 0) { _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); - } else { + } + else { _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); } free(_data); @@ -324,6 +339,51 @@ struct MyOutputHandler : public nvtt::OutputHandler { int _size = 0; int _face = -1; }; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + if (format == gpu::Element::COLOR_RGB9E5) { + _packFunc = glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + _packFunc = packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + } + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + struct MyErrorHandler : public nvtt::ErrorHandler { virtual void error(nvtt::Error e) override { qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); @@ -336,20 +396,8 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { const int width = image.width(), height = image.height(); std::vector data; std::vector::iterator dataIt; - - data.resize(width*height); - dataIt = data.begin(); - for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = (const uint32*) image.constScanLine(lineNb); - const uint32* srcPixelEnd = srcPixelIt + width; - - while (srcPixelIt < srcPixelEnd) { - *dataIt = glm::vec4(glm::unpackF2x11_1x10(*srcPixelIt), 1.f); - ++srcPixelIt; - ++dataIt; - } - } - assert(dataIt == data.end()); + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc; nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; @@ -360,38 +408,55 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { nvtt::CompressionOptions compressionOptions; compressionOptions.setQuality(nvtt::Quality_Production); - auto mipFormat = texture->getStoredMipFormat(); if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) { compressionOptions.setFormat(nvtt::Format_BC6); - } - else if (mipFormat == gpu::Element::COLOR_RGB9E5) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_SharedExp); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(9, 9, 9, 5); - } - else if (mipFormat == gpu::Element::COLOR_R11G11B10) { - // WARNING : With NVTT 2.1, using float 11/10 produces an assertion in the compressor + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(11, 11, 10, 0); - } - else { + compressionOptions.setPixelFormat(32, 32, 32, 0); + unpackFunc = glm::unpackF3x9_E1x5; + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + unpackFunc = glm::unpackF2x11_1x10; + } else { qCWarning(imagelogging) << "Unknown mip format"; Q_UNREACHABLE(); return; } + data.resize(width*height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = (const uint32*)image.constScanLine(lineNb); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); + std::auto_ptr outputHandler; MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); nvtt::Context context; int mipLevel = 0; + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats + outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); + } else { + outputHandler.reset( new OutputHandler(texture, face) ); + } + + outputOptions.setOutputHandler(outputHandler.get()); + nvtt::Surface surface; surface.setImage(inputFormat, width, height, 1, &(*data.begin())); surface.setAlphaMode(alphaMode); @@ -512,7 +577,7 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, int face) { nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); + OutputHandler outputHandler(texture, face); outputOptions.setOutputHandler(&outputHandler); MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); @@ -1007,8 +1072,21 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); -QImage convertToHDRFormat(QImage srcImage) { +QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + std::function packFunc; + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return srcImage; + } srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); for (auto y = 0; y < srcImage.height(); y++) { @@ -1021,7 +1099,12 @@ QImage convertToHDRFormat(QImage srcImage) { color.r = qRed(*srcLineIt); color.g = qGreen(*srcLineIt); color.b = qBlue(*srcLineIt); - *hdrLineIt = glm::packF2x11_1x10(color / 255.f); + // Normalize and apply gamma + color /= 255.f; + color.r = powf(color.r, 2.2f); + color.g = powf(color.g, 2.2f); + color.b = powf(color.b, 2.2f); + *hdrLineIt = packFunc(color); ++srcLineIt; ++hdrLineIt; } @@ -1035,18 +1118,20 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& gpu::TexturePointer theTexture = nullptr; if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - if (image.format() != QIMAGE_HDR_FORMAT) { - image = convertToHDRFormat(image); - } + const auto cubeMapHDRFormat = gpu::Element::COLOR_RGB9E5; gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = gpu::Element::COLOR_R11G11B10; - formatGPU = gpu::Element::COLOR_R11G11B10; + formatMip = cubeMapHDRFormat; + formatGPU = cubeMapHDRFormat; + } + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image, cubeMapHDRFormat); } // Find the layout of the cubemap in the 2D image @@ -1094,9 +1179,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_R11G11B10, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(cubeMapHDRFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_R11G11B10); + irradianceTexture->setStoredMipFormat(cubeMapHDRFormat); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } From 18ce5ba30f5c1e0a0487e00a4942acd866be374f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 08:53:20 +1200 Subject: [PATCH 317/504] Add undo/redo for physics --- scripts/vr-edit/modules/history.js | 27 +++++++++++++++ scripts/vr-edit/modules/selection.js | 50 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index 6fb5416df3..a2b403f87a 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -48,6 +48,27 @@ History = (function () { MAX_HISTORY_ITEMS = 100, undoPosition = -1; // The next history item to undo; the next history item to redo = undoIndex + 1. + function doKick(entityID) { + var properties, + NO_KICK_ENTITY_TYPES = ["Text", "Web"], // These entities don't respond to gravity so don't kick them. + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; + + properties = Entities.getEntityProperties(entityID, ["type", "dynamic", "gravity", "velocity"]); + if (NO_KICK_ENTITY_TYPES.indexOf(properties.type) === -1 && properties.dynamic + && Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(entityID, { velocity: DYNAMIC_VELOCITY_KICK }); + } + } + + function kickPhysics(entityID) { + // Gives entities a small kick to start off physics, if necessary. + var KICK_DELAY = 500; // ms + + // Give physics a chance to catch up. Avoids some erratic behavior. + Script.setTimeout(function () { doKick(entityID); }, KICK_DELAY); + } + function push(undoData, redoData) { // Wipe any redo history after current undo position. if (undoPosition < history.length - 1) { @@ -118,6 +139,9 @@ History = (function () { if (undoData.setProperties) { for (i = 0, length = undoData.setProperties.length; i < length; i += 1) { Entities.editEntity(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); + if (undoData.setProperties[i].properties.gravity) { + kickPhysics(undoData.setProperties[i].entityID); + } } } @@ -151,6 +175,9 @@ History = (function () { if (redoData.setProperties) { for (i = 0, length = redoData.setProperties.length; i < length; i += 1) { Entities.editEntity(redoData.setProperties[i].entityID, redoData.setProperties[i].properties); + if (redoData.setProperties[i].properties.gravity) { + kickPhysics(redoData.setProperties[i].entityID); + } } } diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 60ef02285b..2b6155a032 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -652,6 +652,9 @@ Selection = function (side) { // - Requires child entities to be collisionless, otherwise the entity tree can become self-propelled. // See also: Groups.group() and ungroup(). var properties, + property, + undoData = [], + redoData = [], i, length; @@ -661,14 +664,61 @@ Selection = function (side) { collisionless: physicsProperties.dynamic || physicsProperties.collisionless }; for (i = 1, length = selection.length; i < length; i += 1) { + undoData.push({ + entityID: selection[i].id, + properties: { + dynamic: selection[i].dynamic, + collisionless: selection[i].collisionless + } + }); Entities.editEntity(selection[i].id, properties); + undoData.push({ + entityID: selection[i].id, + properties: properties + }); } + // Undo data. + properties = { + position: selection[0].position, + rotation: selection[0].rotation, + velocity: Vec3.ZERO, + angularVelocity: Vec3.ZERO + }; + for (property in physicsProperties) { + if (physicsProperties.hasOwnProperty(property)) { + properties[property] = selectionProperties[0].properties[property]; + } + } + if (properties.userData === undefined) { + properties.userData = ""; + } + undoData.push({ + entityID: selection[0].id, + properties: properties + }); + // Set root per physicsProperties. properties = Object.clone(physicsProperties); properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); Entities.editEntity(rootEntityID, properties); + // Redo data. + properties.position = selection[0].position; + properties.rotation = selection[0].rotation; + properties.velocity = Vec3.ZERO; + properties.angularVelocity = Vec3.ZERO; + redoData.push({ + entityID: selection[0].id, + properties: properties + }); + + // Add history entry. + History.push( + { setProperties: undoData }, + { setProperties: redoData } + ); + // Kick off physics if necessary. if (physicsProperties.dynamic) { kickPhysics(rootEntityID); From 1e2a3a13da223f8f97d8e9abf9ad2f0f13b499c7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 09:31:32 +1200 Subject: [PATCH 318/504] Fix grip-delete entity history --- scripts/vr-edit/modules/selection.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index 2b6155a032..f4033dda4a 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -262,18 +262,20 @@ Selection = function (side) { } // Add history entry. - History.push( - { - setProperties: [ - { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } - ] - }, - { - setProperties: [ - { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } - ] - } - ); + if (selection.length > 0) { + History.push( + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + } // Kick off physics if necessary. if (selection.length > 0 && selection[0].dynamic) { From d501a8f7cccf155d1733809ff321e88de3a8493f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 11:32:58 +1200 Subject: [PATCH 319/504] Fix scaling and changing hands history --- scripts/vr-edit/modules/selection.js | 4 +++- scripts/vr-edit/vr-edit.js | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index f4033dda4a..fb05611266 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -262,7 +262,9 @@ Selection = function (side) { } // Add history entry. - if (selection.length > 0) { + if (selection.length > 0 + && (!Vec3.equal(startPosition, rootPosition) || !Quat.equal(startOrientation, rootOrientation))) { + // Positions and orientations can be identical if change grabbing hands when finish scaling. History.push( { setProperties: [ diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 293e85a775..0197fbb51f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -522,8 +522,10 @@ function stopDirectScaling() { // Called on grabbing hand by scaling hand. - selection.finishDirectScaling(); - isDirectScaling = false; + if (isDirectScaling) { + selection.finishDirectScaling(); + isDirectScaling = false; + } } function startHandleScaling(targetPosition, overlayID) { @@ -557,10 +559,12 @@ function stopHandleScaling() { // Called on grabbing hand by scaling hand. - handles.finishScaling(); - selection.finishHandleScaling(); - handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. - isHandleScaling = false; + if (isHandleScaling) { + handles.finishScaling(); + selection.finishHandleScaling(); + handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. + isHandleScaling = false; + } } @@ -739,6 +743,8 @@ } function exitEditorGrabbing() { + stopDirectScaling(); + stopHandleScaling(); finishEditing(); handles.clear(); otherEditor.setHandleOverlays([]); From 6d42e82711a52e19664c055b7b6f5fed4b1add65 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 11:52:36 +1200 Subject: [PATCH 320/504] Condense create and clone to have one history entry --- scripts/vr-edit/modules/createPalette.js | 9 +++-- scripts/vr-edit/modules/history.js | 48 ++++++++++++++++-------- scripts/vr-edit/modules/selection.js | 2 +- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 44e4373476..fff8ac34fb 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -318,8 +318,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties, CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), - entityID, - createdEntities; + entityID; itemIndex = paletteItemOverlays.indexOf(intersectionOverlayID); @@ -354,8 +353,10 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); entityID = Entities.addEntity(properties); if (entityID !== Uuid.NULL) { - createdEntities = [{ entityID: entityID, properties: properties }]; - History.push({ deleteEntities: createdEntities }, { createEntities: createdEntities }); + History.prePush( + { deleteEntities: [{ entityID: entityID }] }, + { createEntities: [{ entityID: entityID, properties: properties }] } + ); } else { Feedback.play(otherSide, Feedback.GENERAL_ERROR); } diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index a2b403f87a..5fb0712db3 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -46,7 +46,9 @@ History = (function () { */ ], MAX_HISTORY_ITEMS = 100, - undoPosition = -1; // The next history item to undo; the next history item to redo = undoIndex + 1. + undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. + undoData = {}, + redoData = {}; function doKick(entityID) { var properties, @@ -69,7 +71,17 @@ History = (function () { Script.setTimeout(function () { doKick(entityID); }, KICK_DELAY); } - function push(undoData, redoData) { + function prePush(undo, redo) { + // Stores undo and redo data to include in the next history entry. + undoData = undo; + redoData = redo; + } + + function push(undo, redo) { + // Add a history entry. + undoData = Object.merge(undoData, undo); + redoData = Object.merge(redoData, redo); + // Wipe any redo history after current undo position. if (undoPosition < history.length - 1) { history.splice(undoPosition + 1, history.length - undoPosition - 1); @@ -82,6 +94,9 @@ History = (function () { history.push({ undoData: undoData, redoData: redoData }); undoPosition += 1; + + undoData = {}; + redoData = {}; } function updateEntityIDs(oldEntityID, newEntityID) { @@ -136,6 +151,13 @@ History = (function () { if (undoPosition > -1) { undoData = history[undoPosition].undoData; + if (undoData.createEntities) { + for (i = 0, length = undoData.createEntities.length; i < length; i += 1) { + entityID = Entities.addEntity(undoData.createEntities[i].properties); + updateEntityIDs(undoData.createEntities[i].entityID, entityID); + } + } + if (undoData.setProperties) { for (i = 0, length = undoData.setProperties.length; i < length; i += 1) { Entities.editEntity(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); @@ -145,13 +167,6 @@ History = (function () { } } - if (undoData.createEntities) { - for (i = 0, length = undoData.createEntities.length; i < length; i += 1) { - entityID = Entities.addEntity(undoData.createEntities[i].properties); - updateEntityIDs(undoData.createEntities[i].entityID, entityID); - } - } - if (undoData.deleteEntities) { for (i = 0, length = undoData.deleteEntities.length; i < length; i += 1) { Entities.deleteEntity(undoData.deleteEntities[i].entityID); @@ -172,6 +187,13 @@ History = (function () { if (undoPosition < history.length - 1) { redoData = history[undoPosition + 1].redoData; + if (redoData.createEntities) { + for (i = 0, length = redoData.createEntities.length; i < length; i += 1) { + entityID = Entities.addEntity(redoData.createEntities[i].properties); + updateEntityIDs(redoData.createEntities[i].entityID, entityID); + } + } + if (redoData.setProperties) { for (i = 0, length = redoData.setProperties.length; i < length; i += 1) { Entities.editEntity(redoData.setProperties[i].entityID, redoData.setProperties[i].properties); @@ -181,13 +203,6 @@ History = (function () { } } - if (redoData.createEntities) { - for (i = 0, length = redoData.createEntities.length; i < length; i += 1) { - entityID = Entities.addEntity(redoData.createEntities[i].properties); - updateEntityIDs(redoData.createEntities[i].entityID, entityID); - } - } - if (redoData.deleteEntities) { for (i = 0, length = redoData.deleteEntities.length; i < length; i += 1) { Entities.deleteEntity(redoData.deleteEntities[i].entityID); @@ -199,6 +214,7 @@ History = (function () { } return { + prePush: prePush, push: push, hasUndo: hasUndo, hasRedo: hasRedo, diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index fb05611266..b06b75fba9 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -571,7 +571,7 @@ Selection = function (side) { rootEntityID = selection[0].id; // Add history entry. - History.push( + History.prePush( { deleteEntities: undoData }, { createEntities: redoData } ); From 8e25ccac7968c9f6fd0d789fe7b328c7a83081ff Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 12:20:33 +1200 Subject: [PATCH 321/504] Tidying --- scripts/vr-edit/utilities/utilities.js | 5 +++++ scripts/vr-edit/vr-edit.js | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index 8dacec8c2d..e8c79b9886 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -107,6 +107,11 @@ if (typeof Object.merge !== "function") { Object.merge = function (objectA, objectB) { var a = JSON.stringify(objectA), b = JSON.stringify(objectB); + if (a === "{}") { + return JSON.parse(b); // Always return a new object. + } else if (b === "{}") { + return JSON.parse(a); // "" + } return JSON.parse(a.slice(0, -1) + "," + b.slice(1)); }; } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 0197fbb51f..0adf6fd098 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -71,7 +71,7 @@ button, DOMAIN_CHANGED_MESSAGE = "Toolbar-DomainChanged", - DEBUG = false; + DEBUG = true; // Utilities Script.include("./utilities/utilities.js"); @@ -1544,7 +1544,7 @@ case "undoAction": if (History.hasUndo()) { - Feedback.play(dominantHand, Feedback.UNDO_ACTION) + Feedback.play(dominantHand, Feedback.UNDO_ACTION); History.undo(); } else { Feedback.play(dominantHand, Feedback.GENERAL_ERROR); @@ -1552,7 +1552,7 @@ break; case "redoAction": if (History.hasRedo()) { - Feedback.play(dominantHand, Feedback.REDO_ACTION) + Feedback.play(dominantHand, Feedback.REDO_ACTION); History.redo(); } else { Feedback.play(dominantHand, Feedback.GENERAL_ERROR); From 299a8078e3f44115c6de8081c3a7c27eb2a46dfe Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 17:08:26 +1200 Subject: [PATCH 322/504] Revert handControllerGrab.js related changes --- .../system/controllers/handControllerGrab.js | 50 +++++++++---------- scripts/system/libraries/utils.js | 10 ++-- scripts/vr-edit/vr-edit.js | 3 +- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d7f4ebb99d..f6eec7ab76 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -14,7 +14,7 @@ /* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, - setGrabCommunications, Menu, HMD, isInEditMode, isInVREditMode, AvatarList */ + setGrabCommunications, Menu, HMD, isInEditMode, AvatarList */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -369,8 +369,8 @@ function projectOntoOverlayXYPlane(overlayID, worldPos) { resolution.z = 1; // Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); if (scale) { - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); } } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); @@ -380,8 +380,8 @@ function projectOntoOverlayXYPlane(overlayID, worldPos) { } if (position && rotation && dimensions) { - return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); -} + return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); + } } function handLaserIntersectItem(position, rotation, start) { @@ -539,7 +539,7 @@ function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, off var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; function isEditing() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && (isInEditMode() || isInVREditMode()); + return EXTERNALLY_MANAGED_2D_MINOR_MODE && isInEditMode(); } function isIn2DMode() { @@ -1313,7 +1313,7 @@ function MyController(hand) { }; this.setState = function(newState, reason) { - if (((isInEditMode() || isInVREditMode()) && this.grabbedThingID !== HMD.tabletID) && + if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) && (newState !== STATE_OFF && newState !== STATE_SEARCHING && newState !== STATE_STYLUS_TOUCHING && @@ -1752,7 +1752,7 @@ function MyController(hand) { var nonTabletEntities = grabbableEntities.filter(function(entityID) { return entityID != HMD.tabletID && entityID != HMD.homeButtonID; }); - if (nonTabletEntities.length > 0 && !isInEditMode() && !isInVREditMode()) { + if (nonTabletEntities.length > 0) { Controller.triggerHapticPulse(1, 20, this.hand); } this.grabPointIntersectsEntity = true; @@ -1765,9 +1765,9 @@ function MyController(hand) { this.processStylus(); - if (isInEditMode() && !isInVREditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { + if (isInEditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { // Always showing lasers while in edit mode and hands/stylus is not active. - // But don't show lasers while in VR edit mode. + var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.isValid) { this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; @@ -1827,16 +1827,16 @@ function MyController(hand) { var pickRay; var valid = true - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; - valid = !(worldHandPosition === undefined); + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; + valid = !(worldHandPosition === undefined); - pickRay = { + pickRay = { origin: PICK_WITH_HAND_RAY ? worldHandPosition : MyAvatar.getHeadPosition(), direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Quat.getFront(Camera.orientation), - length: PICK_MAX_DISTANCE - }; + length: PICK_MAX_DISTANCE + }; var result = { entityID: null, @@ -2289,7 +2289,7 @@ function MyController(hand) { return aDistance - bDistance; }); entity = grabbableEntities[0]; - if ((!isInEditMode() && !isInVREditMode()) || entity == HMD.tabletID) { // tablet is grabbable, even when editing + if (!isInEditMode() || entity == HMD.tabletID) { // tablet is grabbable, even when editing name = entityPropertiesCache.getProps(entity).name; this.grabbedThingID = entity; this.grabbedIsOverlay = false; @@ -2389,7 +2389,7 @@ function MyController(hand) { equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); } - if (farGrabEnabled && farSearching && !isInVREditMode()) { + if (farGrabEnabled && farSearching) { this.updateLaserPointer(); } Reticle.setVisible(false); @@ -3431,13 +3431,13 @@ function MyController(hand) { var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); if (intersection.type != RayPick.INTERSECTED_NONE) { if (intersection.objectID != this.grabbedThingID) { - this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "laser moved off of entity"); - return; - } + this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.grabbedThingID = null; + this.setState(STATE_OFF, "laser moved off of entity"); + return; + } this.intersectionDistance = intersection.distance; - if (farGrabEnabled) { + if (farGrabEnabled) { this.updateLaserPointer(); } } diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 0f367e0cfe..a5e97d8949 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -6,16 +6,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -EDIT_SETTING = "io.highfidelity.isEditing"; // Note: This constant is duplicated in edit.js. -isInEditMode = function () { +// note: this constant is currently duplicated in edit.js +EDIT_SETTING = "io.highfidelity.isEditting"; +isInEditMode = function isInEditMode() { return Settings.getValue(EDIT_SETTING); }; -VR_EDIT_SETTING = "io.highfidelity.isVREditing"; // Note: This constant is duplicated in vr-edit.js. -isInVREditMode = function () { - return HMD.active && Settings.getValue(VR_EDIT_SETTING); -} - if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 0adf6fd098..854bdea561 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -18,7 +18,6 @@ APP_ICON_DISABLED = "icons/tablet-icons/edit-disabled.svg", ENABLED_CAPTION_COLOR_OVERRIDE = "", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", - VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js. START_DELAY = 2000, // ms // Application state @@ -1392,7 +1391,7 @@ function updateHandControllerGrab() { // Communicate app status to handControllerGrab.js. - Settings.setValue(VR_EDIT_SETTING, isAppActive); + // TODO } function onUICommand(command, parameter) { From f22e2de52579d4871ea896567b516e57bdc8f6fb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 17:21:45 +1200 Subject: [PATCH 323/504] Interim method for disabling controllerDispatcher lasers, grabbing, etc. --- scripts/vr-edit/vr-edit.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 854bdea561..d94c84e9d9 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1389,9 +1389,12 @@ updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT); } - function updateHandControllerGrab() { - // Communicate app status to handControllerGrab.js. - // TODO + function updateControllerDispatcher() { + // Communicate app status to controllerDispatcher.js. + var DISABLE_HANDS = "both", + ENABLE_HANDS = "none"; + // TODO: Proper method to disable specific laser and grabbing functionality. + Messages.sendLocalMessage('Hifi-Hand-Disabler', isAppActive ? DISABLE_HANDS : ENABLE_HANDS); } function onUICommand(command, parameter) { @@ -1598,7 +1601,7 @@ } isAppActive = !isAppActive; - updateHandControllerGrab(); + updateControllerDispatcher(); button.editProperties({ isActive: isAppActive }); if (isAppActive) { @@ -1613,7 +1616,7 @@ var hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); if (isAppActive && !hasRezPermissions) { isAppActive = false; - updateHandControllerGrab(); + updateControllerDispatcher(); stopApp(); } button.editProperties({ @@ -1628,7 +1631,7 @@ var hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); if (isAppActive && !hasRezPermissions) { isAppActive = false; - updateHandControllerGrab(); + updateControllerDispatcher(); stopApp(); } button.editProperties({ @@ -1673,7 +1676,7 @@ // Close the app because the new avatar may have different joint numbers meaning that the UI would be attached // incorrectly. Let the user reopen the app because it can take some time for the new avatar to load. isAppActive = false; - updateHandControllerGrab(); + updateControllerDispatcher(); button.editProperties({ isActive: false }); stopApp(); } @@ -1691,7 +1694,7 @@ // Application state. isAppActive = false; - updateHandControllerGrab(); + updateControllerDispatcher(); dominantHand = MyAvatar.getDominantHand() === "left" ? LEFT_HAND : RIGHT_HAND; // Tablet/toolbar button. @@ -1751,7 +1754,7 @@ MyAvatar.skeletonChanged.disconnect(onSkeletonChanged); isAppActive = false; - updateHandControllerGrab(); + updateControllerDispatcher(); if (button) { button.clicked.disconnect(onAppButtonClicked); From 04305155d098365f8753a4ec315d6d1f75cb4bac Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 1 Sep 2017 00:49:02 +0300 Subject: [PATCH 324/504] make building tools (besides 'scribe') optional --- CMakeLists.txt | 5 ++--- tools/CMakeLists.txt | 30 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be513abddb..5652a20335 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,9 +101,8 @@ if (BUILD_CLIENT OR BUILD_SERVER) add_subdirectory(plugins) endif() -if (BUILD_TOOLS) - add_subdirectory(tools) -endif() +# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway +add_subdirectory(tools) if (BUILD_TESTS) add_subdirectory(tests) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5de44e8897..cf11ef9e7a 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,23 +2,25 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") -add_subdirectory(udt-test) -set_target_properties(udt-test PROPERTIES FOLDER "Tools") +if (BUILD_TOOLS) + add_subdirectory(udt-test) + set_target_properties(udt-test PROPERTIES FOLDER "Tools") -add_subdirectory(vhacd-util) -set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") + add_subdirectory(vhacd-util) + set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") -add_subdirectory(ice-client) -set_target_properties(ice-client PROPERTIES FOLDER "Tools") + add_subdirectory(ice-client) + set_target_properties(ice-client PROPERTIES FOLDER "Tools") -add_subdirectory(ac-client) -set_target_properties(ac-client PROPERTIES FOLDER "Tools") + add_subdirectory(ac-client) + set_target_properties(ac-client PROPERTIES FOLDER "Tools") -add_subdirectory(skeleton-dump) -set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") + add_subdirectory(skeleton-dump) + set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") -add_subdirectory(atp-client) -set_target_properties(atp-client PROPERTIES FOLDER "Tools") + add_subdirectory(atp-client) + set_target_properties(atp-client PROPERTIES FOLDER "Tools") -add_subdirectory(oven) -set_target_properties(oven PROPERTIES FOLDER "Tools") + add_subdirectory(oven) + set_target_properties(oven PROPERTIES FOLDER "Tools") +endif() From 378793d18ebae32b30a0b296801dcced19ad1c07 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 08:48:47 +1200 Subject: [PATCH 325/504] Proper color circle --- .../assets/tools/color/color-circle-black.png | Bin 5270 -> 9588 bytes .../assets/tools/color/color-circle.png | Bin 76746 -> 129833 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/vr-edit/assets/tools/color/color-circle-black.png b/scripts/vr-edit/assets/tools/color/color-circle-black.png index 3494b63b708f8ba2945cc6ac445c0c5bc7a13444..4b62c28a4daadf7bdfb4002d2c9516bc5f8fd2e9 100644 GIT binary patch literal 9588 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 Q006+~tofNTJ=Yum2Y0~9`Tzg` diff --git a/scripts/vr-edit/assets/tools/color/color-circle.png b/scripts/vr-edit/assets/tools/color/color-circle.png index 408139972ebd4fc7254c19a67dbc9ab20c1e3cc3..8bf261338242913351cb016523b97347261013de 100644 GIT binary patch literal 129833 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�+*!U0g4-ZW-JsS$|hAS4k+F98)I6wl4O;_t!~NkRBd zz5sC>05v)ojm`qA#KDIUf!7-PT1)@nM=AcFM+0D={=(n!^5;*Tul_v((L4GL0mrq_ zALZB2KR=@%#-c&#LWDNMpRtSoZh`-=Mc)#fo2`AbaBd{N-pC~_!@ff_L{@YItOYQs zEms#^0Fn}@5U(z9qgnqOC%nB$Mg6@=vd~PJm4rAG5IKL}?@fsl{gL$&y*uEVen4pK zEJ5HysMk#-ERLr2E$M`2t@|R-hyOsR^dZH2D3g4r#4$1PL!b`p#sE-jdojfiVT(XD z_*P7T)bIZkiHDAs-c$;I55Ie?q>t`&P{m(N`Kf?epI}$i`3!lBAuBoprst$4VR|;! zkqrc)9g+8roPYA)pVfQ_0oI=$rg^Kpf5|?eremstNQO$OnR%`@$J=>fw?O45uqyW5 ziePRSehUAh{Ve`X|Gz$?2H<0_0NU?+dAM&(C;hY$@CBsb75J5S? zXMA)eRB^Lt<7p!raoZ=pV*hJc2>?Tu*%y#Qvy(^0CEaox{Xx>q4G1MX&p2zFXD#db!v*t6y=qlUT?G^p226}iY@>m z2niyF*YXBIp&cwESZbZH{EUk^be2IX!Xg78i2^~J0%4V~6m}J4M92+Jv;uG6d#$Cv z@8$o0`g1@4KCBtAPk-_C&i{Nd7rNXC*vTs}^wz*Dv!9hX&`#(9Yr6kcBamUmr|?|2 zUGx`G08hfck>~>x>v`MXHTOM!t-RkyyKbdZ_^OeQas`ACesM~J=9zPRZwGrHy8Hdu z%$gt?jsYG)SCET;E;8&VaNOT6dHJinz`8aUbL;AgULQxyIt&*1r77G-u~)7sirffQ zN@7TEzoP&+Bfv{TRZTi@BXNMk@oO`tMK5RE;F|8wutgp>fE~ zfe9~j1@UK|U%}=XU5L2>Mx5^u0Wy{l2MA|4FM>41S0l{Wx$&_3$0m3v7z&KvNAZF< zd1X_~z5r-702Y)SgVF-6mGdd9UjuvIkA|DC!a)zs1F!R$&}9rdvMi<>ORor;)(CUBwzIyl<}T7hP|7nkHB3S=}O!1hTfIPxU!icZ*`!*XL4 z1Bkf6;Jkn&WJDGVCex*7;iw35M(hi5p#my3@Lexozt+}Y`rrm2Jol3h)A^8Q!2W

    (zX`>jOyMuY(1INw1NBp2?u z&pc4Wd+NkkMR_%cy`Fn*EChmoEj0`%0=*e-GipO))FhZuV-ky7-51XWLW~ZWpfFYj zHP#s5Sjh`O`?!{hzX__tQL_Q6xK4RERAZc4kkM=xpcUZ_u?~r_vI(IZSUc+}fuhS} zH!950q-Rijdra)rhTDp$PiKdi&;wcE|CiUqG{q~+U9XUM^1v4qfRkX*!|$BR4oEI{ z;omI^01?(hR7Nu3A?O{@7QEXr&qcH_M3P?mel+zlUEE!1AViQ-(ik8Y?C27>Jw}A` z=ZR>q2=>JKR`#K>h;EHxtV3D^vjSXG z3?u0ds-a7Kru*7nSi>``u7>e3ZXy{F=tYKEBsUWGTh|CX3c!SxqUHe@nF8rGKX#5c z83)W-fX;>&fY@kIehOzoJVtw*0*3&#Hm9}{khUnQxUT8}diTFbEg_`+p`2SUt(1k; zQSAhX^4SMegfVgZX_Zfi{&plFcmT+WPYDD&I5_&+kGgBFD1ej!z{679V;9$D77a>w zKjO-jC;wfqQ_+%HHy(UedX)=#(L99BfH-|~65^ZC?1^geVKS%)^Eac{U@mso8O7jfA4~px$O1EDMnP?cJgx8PjRR|qd04!66URA3F=LeB zpdBdS$j*HMboh@6XunGf>}efPDr1G!^~9f}bWu=*ibhyS1Kd8pv%Fd)pMdzun9Ws( zw`C?BQ~161sYQBDD4|cfXnW%j7cWO)6;Jx+BSX?4lmJR#C-(T~Dyo;Rp)z1cp$-eJ z0FaG!3Tl9?&SyXUvN~v@d6>CLdiVEpXd0|(YU26gCcuQ>!L#r+5Mo1Vx$KbJ6CLai z^m>N3zhBGAOO3_Peo&eA&!7RYPyfQx+v;E29H96A?~d|*pN%T80HtgVfyG~(tC7L0 zY=AQjz{)HrUbZ*KKipUTV%lFc`cbg$>u1C720G+oqyxrMcQf6Xj?j#C;XyzbixcCV2XW>Q#X7!!UjB_jW6UJ_gmegm&Vs@zN8Vlc7#f7CCvc%5KqoBoI{88f=Hf9-aqQkm zq$b1kA{dA*aZ}WXwSxh z8>d>l(FgZm1RM2xaz;681aN`~Wd8AXbXSSfzwN>+C*fZ%UaSikK zz*9FkOz{3*;Q#u%0qqG8*Z+p31x)_VPn!G*2>`jim4;!w{Z4p*mE+?~=%%1NCl>== z4@6`1gT^C3h#OnMpc1qxJp$(p2v@)k76&T&Fx3>SCTyDSr;9k`c+N=N7oq-QUyO_p z02S^}Gy>5y5D9`E<S|BxgU8sZXQVNvdI@XEym6hhu^(d= z>m9ItUd>BWy0Py+ploC_>cC3_raKrGfXw8$YgVAg?xZasxr6;Opqv#1bEra*MzSxLOkSKCeBY+w%R5Sw7JAtSX zwiRA+|C%QF{U5Ob06w${uuuQux4!)O)n1vun*MQeI~Merctv)iF%Z1JDcj!gW+wE& z8j}p|1bBPL{d;NzUVrw5CfP6U{SRse2cp~GO|ICPv8tFRxMG8Qz@0Yvl@txEuEkzJ-GyQ#~R=wZm7EbmpiRs>c zzVYOPj>SU5u@#PCp+LK*9(#4wQ?$iA{tX@wfxl z=ouh234oRIRi|^NCq`)sREW^U7$5uZt_gpi+QAaC`H7Q2ah>*pMui)~63nradem0jT0c7WgUs z0a%!bAS4t`ODEEsLKl#4_ml740OT!zGid${{{PFr?&Z(#S+k5C5^%=<1J{g+I@SJ_ z$LW;)XYPabHQdPd_m)bB?*hLA_P12HpUPC{?KG{9fv5Tr3LSI*qky3CLVZj7_j&JL z!S$Jv7-6c^Vlr#o=CpEOP3|>+PYK=>;*cYc`-vZo6)i;Eu+g?f{{A+5Yu|t)%RnU& z-WoF|Ei2RkI~BTz;K7}~Ht}Al{rypz!n$?AjTWet@nqKp*}P5UR6xkziK7{qq)~|H z@CF%C=b->Q`{45lCj+yzgWn+r%DaAqR5|Z$bjt+iO|&pM{2>&r1fP@MVJ~zlvCzqY zN1+>wB*3)#cc03qKyfF|JrkJUpQvetJh6jqi$TR)MvaBGQ9p9 z3(mh^5&RzfqD=!2v<3Ky^WqFK`J4E3+l6LC6~9%)@Rj&s6JjEN z=s0dW_n-IYA-G(X$Tte~srK}}F$zL~u-WF9gMTW#tC`U(s`I5}i;Ho+zKDR@2omzwldl(;zgtBF*y`8H^p3JMKr4Tyt%Pq>j7Q@c(3$d}X`(9P z1CffTbs7hP`EKnJWe1$N;~jD{lM24Uh_=n!@R*uataH+BZn_bmBAUiq;& zcT*FG`KU=;Pxahr^x`^pgHxFUdCiL_&)H1fqe=hsW=#*J=nmqvZ4r3Gu=kMSF%@S! zM8FV)10YZ8b*@Hf62@Ni70eH6{%RnQHD3BCw@~oB(p;cu0ETFuGJ<4cyTYVXW1h}C zv<7KcpOpG{E9fX><@f8|&$h?iM6Ut?$U#4^t@?9Qa7heo3?pB#`7L5UhRV61d%Zup zDPM5bfQGvzVgRkW-%Wm?V}&;-!FP8HzWOkM21G%jHG;xfum9@2P1XoR=sxi4yb)2L zbV5UlcEPm~kvA5>@v%G0;7K$M{G6J>LJx4<=J((y>|em2^{+it_&=Txfc@5&KfdXD zy5CvI&WdxtLI>D_T2NenXZx!fgwY@do6*nP&z^|GAA+mqz@H(Sr;#T9D*Q6kBtVd+ zK&RxH^vXw?412{rNQ91%oP`O@R^Q6vyJA+OlM+hcet;JcHb~Rd}xbh*z zZ#7;xBL!V9z({%y6k(r{g|P}v(pwp5eE}3YGYYhyeHA*?N7)8j^Lu0D#+QwN0?Kd? zdwN?rg=_}*&#m#5ENmB8=Ge_d3SSBS@<0dpQLM~`^WOrRW@x@R&0$@9Cqu$4ma|6a zHpMYGQ439>N$Y6jgeMJvVuIlakarODO-d~EDSQAL2R+5RyMXGGe?oUVWQt|;{Rg29 zsevNr#XTzkO(@W_raOv2JZ_f>*FXqYMrNn_2H=}s{(h~&fB9hw;3G8vKc0)MUns(> zpLKD+JPWT}`WQ7>J?2WNh(a9p2l$hApN^h z8?w#?u)X*Xv6HT4ew_$Vg(F!L@MDtqSL>Y+(Z=1Xp;G==A>c@benZi-!BN({^fAEk zZ>8q1zw2gMQ}eb&BSL02r%?~ZA}YJ(I-Y=(nIA94Fj!l^#{F|lnuHrzJ~bXA^USlG zD2W$WhCOLJz_pQ1nWh8eACvr`Y>vb6+SCn-yAB?TLO$TUd6~k800K#vuC)f~|1%>A z0C^pB$%LXonNxoj)Zf}6@W(GV0R9mh0Q>a6{L;&xzrE+x&ol*gk!QbhyE6pfih!`w z26)q89NhmQ;ByN6*&2g^8usH{I~iLe!}fOQu zzd;B90J%v}3%$1gjfjrY0H}?C9SyL&7T84s&(vhGgQH0Wy;21XNt39RK1Y|Dmv8SB z#-y4Vza#On*qTh2G@({*5GM08wPXD`+8}PP`MUNc_~1nz~MC+FsJNU@qxDjr}A2s zKG!On-kR0L&ikKV)2&tXYc8HSZnYi2Ho+CHHd+}w%I^@g#|U(jHS)h#Zo4bQYUjU- z|7_vg?@|k(KJ&MRCh6h+K54M<^rq4RUA#wLRxv^Fy$T2dAxhR>-RuGUkEdsb#^dyB zv{u)M==R5$g7bGW!!gnrxPbUUoWG(T8rYkY1k2ufv^u|Jl$3uv!R{ zdf$Su&}o8Jd^xSx&DrP#Vo@dpB{d8n+6M=9AyJ@_-5Qg1@N>i2s}x4IpKc027Q#T_ z$1XJhq7is{tblIt|9|!McLCtV>0SN%%)}p%t&CU~>H%pr;T@NV* zG3UZZg+EeZup%6^g`f>W17J-kj+JTF8Y3ZYaL^R<;x=d|&i)Y81&Ur{h-2o7y9)(c ztp%EFSUfd4$_=nsJ=>rLuToO^40)kX^uB*>7`kHKf6plRXWL3SG6Bl;EM4!A!*)Oy z@`NVP8lu|*){nY?sV#7e9kB8bK(jj7H6B*;?N7P?aE;{EySPGF*8Xrf3hq#LL{o_4 z2d(fW4t>(`qMGx#XgZSU&vINaAVPe?Ynefexr+e>%`%C{SNe)h>=~_-Rnhkcv;e?A zj(@_w9{-8|pO*@MZ2+t#7@Fe#BQJk^-D7;dFNA+*+JLj7ZWkK{g(7rZqT$RJ;faoC zgl&L0jSsp#N6Osj!k9yLlo3o`$%Ew4N1zle6k?-wtomN5U2&~Ok6Q=PqhO?q{*81A zSQOONZoAP$XcX2n#0nb3gqw>U|2+H8-&!*ul}uP&5O}hOG&kbg#U@0H29LBgfzg~&nDIQwaDhx#hRPW@uP z-->vWA9qSb$y(DNpe6{$#vqCjr=0xT-&-d?5ASk4Mm1_lsd^C)W92ug{uv?%)=qZ{ z>fSAfT~C)TcI2ZY9{UP(w!ZC3V*|S@?p=EBXN{nAMohsvR+}_GY@Cn8K3|6jIUx+w zEBcbqf_b0{v4%_*UUK6@@yeWj2(fh{!-_;kKo<&B=j7jY+5lW%16(-1efrma)ytnh ze7(r~1Jk|X+|IXy=ONC_Xmkd)020qu8FLfz$s$#4gc#frj7UZi&cnRpKc?2lUgf*fyll%OGo--G!(Fz82#I2WY zhhl$Q(iI0dG)UzHFnTZ+R>4{PXT?9$q?k~1_kGhP{%;(OtTi0hkRh*zG2+*Xiir9B;LOfmj_(#z3A6 z`HoHm?Lcf2;Ptb#g#)n#GtjSN#^!Q*f18d5IBx&L_+9p^@Spkr{TYh@-xdEaJ#HJk zg50~K9k>=J+<5?4BPP5`IoJXfcq_o4zQbQCPd>=@Rj0Ul%3>eIY0rv9FbGyhV-_=H zk<&pyAb}m4NqGnXBMS_V#wRx?qgcA4Y%;K0pmw9n$2M9%+gf9ww}LwfVKAa!Lcz0R zV%a8rGghcu;n&APGjJmb+$e@T8vIu8U8H^TimLVxD4K_DWzY0)THSAO0ss5bPr;Ja zuTJ7)1VBUibJ2`KTK-lJ-pI?IpmRnA;#ywX>9^cecz}zY8V>36Z@Gz3!aJzvWcoqz z{HDcjr6KTsVpl~-ZaxrNu~j=c!3zKV+0g_etlH7EzBNPB#RSg*OH5m+*TGi8$SaJ} zOMs{UaHaMsbPlL-$!djEX#vh$0^ha)UTgDTcti;JJ`KQ+)@k#RW?;43ui*VN@!*PD zxGF5P3lzd}jIS5gPwqeaBo$>ZMRHJ~1{M6(>lh6`Is}BT6#gjdgZaN9Amkwv47}XW z8VK5i8arwOvN($=0%p-Y(HSV>w4PGvMT*&3-vXtQ8VM6AdDcPi%&j9a-E8H1D_RaC zbwH&tAh!Xu7|{fpHMqetg6d5d^hz%{y^m`B)5Yh9-+LE=zl;Dd1%QAnb@&}bW04Ah z3Sdd!>m2?dim)$YcTI@=x~4;f#()c=Ev##mK@SKuP<(m!yxnUj=e;uRdv*2$^mafA z^I7Q?#0s5CDlEm{k7!|TJTRI@#YDuf(r4V`;%78qit7J}0HGQJP4(FkO~NDgz`&0# z8vyMCaGkfePyhNj;D4nX@QV}smG5;h|CztQS#H3ULf>lw^3K05+JpT6<3)WG@s%fs zKM2S*@}{gOP?P?qBd{jPFI(eQSa+1E+bD3z%e!9p^;heAN3+oFpXVvM7W>h__nj?p z)iR8P$V6CQqbjxNTZ;Nob2}#mc8+}2KS4?SB2~amMm$fBY-OAf0z*Ndw2q)dgF54* zGff2+mdj3XJqlXHZht0*0rkC-dRGKdB21_=#z{JWsfBGxnJ>&|q7H$@f2m2CqCo(` z*w(xzp5zDQP8plxuqCi^_?yK4JFV+Y6_qQ3d(_53u_#7$*ki*W1?Q*p*X1XGRuqv7 zb#yUw?8DgXMHKfa}ZWR>)H zrMXxM`mPpm2TI7TY6@15<(FuSZ25Z_|EOb|FaLYmFUQQs`#u+E^P5~&Rddj-@BzvW zcNC|!%Bw2#om%|3fnmKsT@%#PWDUH*A^~s+Lm?x+7)!2OH6=G2%@o{w7HFYcZCtoD z+WvJDLO>VIff7x6neaggf!0F*M?j9X(aS(h{flD0%FCbUcQSQl;kuxd|54CasS6c> z?SwoX(ZPDhnh^oya34AG1JY@aqkqeUxhRGTZU-8hUn_$%kUqhME`f?+=k}*!hIa(yxz`mi>XQB*dG#>m69oMLrltkv84qubsStl1H#b~N z@YWnZ8k^|l-1?dg@B=S@|MmAo0PJKUwy$c)z6=r#a(LWesy+LF{^yA zw}o2k+97BW6=Q2j)V3EsQ|3SMz5_};2q6drP2HnO|M4ie$k?r3R!EAP4CfH6N(k1FTP&i=2V7 zB@vJPzPdLB8Z(uN5A;eT>jKheL-7CdaR`kl$N*^;r0)UrHU^p^Kbx<;{rany8UVZ> z0`S%AJb%{=Kydkw`1PO78;*5suM!Lb=fc74>wgR0|Iy<=dj5}cgq`PkF7$aOJc`^* z)18YcT-(J~-}bs!Z3&5}1?F~#5w#_+%XC8hPFdxnsX0&$%uO1@XqUW0$$oJ6LrkPy zvY<)GyZd@9nu?fcsC4-b2(N%4>5?lBe$jzY=@r(7K@l|?!A9ETao5*T())m7@yyWw zVbBxv8C1~EfcE+4Y=CC1PQm19rv?>f-%WYEnf94Vuo{RH?-Tdg zH|Vhiv~MRUgtgDH6}P7y3D}Av!}|F-2f^e+ARi_~eXNRS8er_VVCTG?M99qmL-ci; zu4`RKQMi@+vcsHe4u%xUnH1+$=G>cxh6;*x@Us?B#$vl|1n^|b>wT(eDjYjG3=Uxq zr0jc?-@p*zK)nTW&_$T0it0J9>!!7_SZOr4af=C%TJXo8w`?6;&m+?YDFRrA1&LLz zXw6&1fTGZ+9q-l3oDj*S=3>w`0HF|jW`M6DacOqb+4qKxfKq&>m}VDze3xI)snTP# zE{pr7fX$2FLAQ}$6oouR7Qo1wih?jqp<}=z@ejhE-}7dGi=yp=oFlyo4gtN9(Fp*7 ztD69?UwL2dr&<&(ikzb}JJef3WH3f`ra&i1qiM z?rVRy&;$$|`~Jt@QSgVf%9pwF_&8OoFvyY?jmLfs>NyOB;yX6>>B<_BRxFe{AgBtz z*rwOZrvxN~GpP?4sJrB#NZ`V4rYaQt^OYiyZglcwmFm2RTuP?#vX)@V?WrhG3=oi(VK8d)sae%EW2+I+~V zdfw-%r$Qg&bVcGEpwsrG5KjmQOLARsr-KlZt=9I?t?|vqIyg+>6WRbMH3th9zv6sH zQ!qfj1z5xoGo|6p>x&V|zJ}}h9K^59giD|`d^-pCY}x|OIq>kjXfhYJb2QflUFuOf z@uvKhc|`&KO!@wl6u{N>z41eR(Z8ywALyOBylF00raUXbADYMR?%SgQxA5BH2Ea`= z3hR6UBQYRo;#Ywvs(H{;RlZbelwk-?y+XE!Jh46(X!=*?JSwJkG?RRuvV{|%ne;C( z8wHYA!AUBHxNbGnOaRDrJ*SwSeExV1&!nAE-p?2^&e&3OQVVUM2F%11ub$7BHp|M| z*yQWr!tAX_fGW^N4T9EKDAdBKsmk%c2aG<2Ev{#X9*xkdGYs0NZ6QcJ+m%m&4A`vXrHP%2;q>*IUfE#q%;Pikp6CM8&BsJv@Oerv z2*7AKeepF@7qCX0i9j>ZecS#2MkuHte_smxev51XGzD02oaOZLf8tT%|OkIhhQ zC-7v>qF>*f4%8LDSz1b-+_Rs8E!F`$ z+NhfT)ZgMxtjmykW4X&Id~A&ig8LP`&p!Ur?pF*qF1Y?B+>Z0f`_zq(5eI_H=R6nX zaT1@}<%eYhAg47#_B1GbKPbRR#Sn`P11Jby6#c2hfR^AUgpn!QX7kUH$%V#t+*Kf0fn&^%lS-@Bi6q1oj( znbdYG;4m{hAF4SRTg~Zzf}{U8-+pV2_i9XJ6|Jc0`zl^;>1@ig5#%P zB*|uO0ft=1<8?j13maDfJ)H>>WRnDRw$DRq2-N#BBn9E^RidgNTv%H>>iVFQmL%}` za_XTJJ|LU50R7~5#`(KKD+BKv#fFnDVvUZ=^iI4==L0GwFdJCBO!HG~1iV?9b5yuH zLhVpK{NeR8{|{7H{8S4N3p*zuB8U!^;B!_4A#REk{@(RQ!3+XWvkd98?Jw7_IQ>u8 zNPr6si?1j6MmYFVtMO8^@!x%{2H=OzZ{rJeKzaQ8MS4Kl0iUfauBQ9$XWnWTw&KtG z|19)V9B781dyjv1OFP!(a<+QT08rRPeLS>@mHOE$9|L(Sn}vyTyQn(;lg*I2BQ(sp|}H>D6gF~FzOT{I#Q`0UMtmm{g5b9 z)*G16X)uLh)D*E0wWAJlE2Q9kwcC$E8Ps;YS^4X7*V`3Ap2jc475vs5Hl9gdpS-L`0hff{fNo7mdPV`tK=$A9|nxI9>m{&;Zy8I^YYg zzZ3XfYEC{|KD9 z5TqE#Tbrs%JM-_JqAVbUbXaT$I8D@6ZHG%SV{u@&x8LXp z;1&9x5Q0Ik$5nJ7s76KmYk;O9(8VrWrGKPx5F1M&zn1p1yX5=3Cib;v|M9a%;WQ1< zKK&oQ^z!Fd&Wq{__ICje`^oo5^IY%KD0Cvfe(Fbp`t82{=ll0YnYcBCt%`Pp}_s?gV@K#DiE{0eUka)46OYs%tE{U4|BI?eZ+0z*<2nU?CN z_xhWxEr0)+7~PBz4T0>`UKc`uGb7zm+??jodKigGN- z&`rl`;$Jm5vH9ZlIOhp|vHPC(`ez@uqs|SKE<+UTiQe}B5ca_61%N5>@%8)9wfM6& z@w@Xw&DwMwDCA8Oa6K64_3N+ThwMxECI8zkeQ-1Y%L(}(efilJt{2*?)A~v~aMd)( zPUwg(=9dLXL!uy00wMS!Ar8FdJ>T6gN9E4YG*}UTxqzOP-+hK?XGzZMC+6z3)&&t6 zlzEVHUfa!fLM$!>%-pFols&HnV;nu%qnQ+S&cT^*(8#}&z2rBHGOb(Ewp7%>%hxMc$qN04$0%z4 zu=1yDfZ1bT6`E5mKm!3MVVHn|J-)hmvh&XcSGe~j9zCot?sV7GAwaNZ^T5|)#3}4< zr#A*ZYO?og-V@#V3KBxcP>go&ulZf^6~Rn0u&7A_oC2}oMzSCX+iQ5vgOM(^lZlmj^jAQdxP8ub?c-= zb5V89L5wCsb~HsPQv;odFIp(CciZ)Ih5RK$YgQKl}QSHpNqK1Mn1l#)qQ|FKUwdieN3! z5>ss;Oe+4i*lK8af8{g4leCjD5Wfd#TneS0gP(_KmFUt(@V;^w+@FVyi9)#1t`EQ( zp-K_fIPtNNzW9^Fs-jpfU}5TTqxppLQ|ak89a%G zpwG8Q3I@7`OS_lg6)T0$LL+fJs+ho~c#6$=D&Lx=YQ7g5yT9*8J-#y`v3DLOZ={t6w9`NCgF1lX0&S1M_R^ng2uR7o&IcS|ld} zTC;vP+EhPCnguuu`RTL;u^AWzSy`w0t`na_h?f#*%tR^2Q?P*rmw_(S`=zp z_*b63ZXK9BFV!w}zt9^8>Kqrk=vbJ!`oVq3tyAnv`h4X)4SWjZ&SR!5;c zTv7|Qh-GSLG#0$oK*=O!QT^kSQt2W$D5)_?8h-!bo{(h_x zDHlzFzNazYE>Qrn2FEL7B{tzo&W_v`oF;UU%RioT$w)62!UOz zv6uA$>H)5#46eR^Mn`z7QTX(o@Na|v-yr?luJX4%1nA9jcicI7Gy=K|ZO=0GBMAGP zob?piKodMS0#@T)jw##2)DGzmU)TBgvh7k!keOcPq3?|zp+fH~fm?R;JDv+AozEA- z!6CN?xOflQ@NDPvhUyQXl=LR;lubt7n9wxnT!%IcscA;)grC>_y4Bc9g+MeKF5C?D z*BJrO^jP%-Ah>_OP_osp(T+Gl?&TtE{k5wzrjhWwRUux~97Jt^H~9i(5>y=Ypxb)` z?W?9BcnPH4bup!@Rl5>?L+6R7)Y;K1;rS==+dMH(DsEF0c0?K=oP#<0zsVN9zG`|9#my)6&O@Y-)rsubua(_Z)92YQy&2P4Q1jstCWG! zV;KD=Z70ndh-O)=yN19n!2Bzyzu%L`z4+h4<2>(g-AGoxBgmwV&46n48w>S7byV95 z*|;|ow^G7GVcD|XUpn--X2(J}F3K}F{8?FH)KhufQc$aAPE^Av4Fd|dt=9F%h4ny< zl0X*C+Ayps0TnZOP=bmYgM=%3# zUo|{AK3UNMUPOVdgP&|70hKVAL{W0@pR)iut{1s5uf%9_dQy|BHG;#&N1#Inr05LI z_JFU~45ygHiwmG@RD&s#*u)f*qQ>#AYYfU%&C~`cC9i2r(z)a3v)b^1M2MYm4=Tc# z!iFYlFcawgn>fsc;B^C_gZ$O^C$1+29{>Kq4`~2?;1u3BYh;Ng8Ukx*e{LcyHx2#> zns8;aK=0V|#px-$p4)xi>u83N(3wpHSPgrI5Q!b9>F|gR6)tLSdJpay0Q_StkJ+v_trcMy$5 zY@TLlUgdj$x>bP->43m0F8BvdGywWCZO{LU>HYslxq5GaK&QJ>fwpj4TTK4$4a&b2 z&=EegeyKEN(H|%O-PQlET)H1?ej8)7djy!JV!jLQtebS+UibD`@22}ZnqVZZGeK!E zzg(Le^)BeNxMO3 zdeep9^`wmsKDP=AuAJ`D|5@!V(1Mox7ljkbQ(qayh^aoFy(#Q^OF^DN>_^w+XfIq$ zg@j!%8~99dy!(VJkP4_{Y*Ggqs#jVG#HMC6aJ=Qh9Zx~DXHLEZ+nV9j=rf}MQ8>sx z5=ChEtUdpyD)6Gl`9lrBKitmnxdA8z_V0iB|KEL$ z;`DumM7-SYT^P{)XBK~f4GEHE4ZzkA*mjIo-Q`eV-Wr;cA?$%J3DA-m5f{uVWb}rA zk38)t8|4*x_VO4#$PVyo;1HAk@eU7}M$k#+G}NwPZWoZhEgS;7CMvizhGuivD3h{! z4G;!ETjS~E);DCK){V#z`~Ry3p;9H)FQEz^L0JM$pNd$_2YnCVEgH3=*~6U?)(z=* z+wV07bXKb6wYtt%-=!uhG#v|-I!|bhg|9tmFC4-%-x?ReSok7QsisP9Rz@}5DUN!b z%byZ1QZgSnwa;opu^l7__iRP~xofms@AyY#pd|j84&A{bgsIRu+c9xd$Bl+6wn0dV z5Nq7g+fGC4Djs-60|4KQAGGhpf9rqaK?=aX^8Us7jMGOW;#)Nt{A$OA=ktY*z?vpu z6aIPl`Ymt0QW{YYV5#U=SHP@07^LbqRPVzfbm5(XS_%1jKD-lVb%}#DIj{x~snx*V zzDe!;kH)wM6cN0kA}+NKXjk-lEkX6BS8JYHEcjOJ)AL)jZdU7mUcL`%0(5AQn(8ef zHu-CY@T)L#g=AEU!?pi@lKr?`Ys})|EC9rEB;d(E{yQ}C5t20snBOS~My%aYZbXj; zG|qgAPAIE#X!HPnV)_H^1egjIi`J;98%5Q&Uixtb&@>XjJM!$p$KR6$mZO2`b%7># zC1_MBd@Q7s&5fd91RWB$NG72ROk=d312GRGHYuGMkXIi9)lhbZK(&0n3IYGdwKc%w zzklSE|6dmRRoKpImG6;;z)qgqaBdW&;r}P%&Er`g$Pv0}eXis8k@w%X?E#aaJ818F zK&%YAs};ehJ8*f~AEE zVgA~TZGaW;ZF22Tp#j~`W2-ly0+Wnd%RFya$^cb_NQeo)8`*&ADE2kwaucU+`V`NN zs>L4r{zxwu#iKd6H{`pR>dLCH(g_K{U7`jSaPxAXA8WoJ!aycHx8SmsK+>n?`loA3 zp;-8KD{e}RXxg!EM+lxt*f_Eg571BI-D_ZE!UB!)+=%qt8Yo?@4&FTzl%@fuSMiFD zK%L{RskndsrYhPE>aiW?p4Q8+FE%KPjDfR&pxglbxrPE59cTaj*QWZbihkg_4}dlB zn}Fg@kH=1$gL?nl{g?6f?xWXbSF4 zhlS6_w%L-Ng%a^pMi)=nolwe~DA^Q&>sqjejIcz;%^=o<=~!f?o8Zj=KWL3ik>HaP*== zJ3%dMDU##n;wE6#(a4wHasWo+JS$3IR6JeC&|K!u1A5JCB;i%G%0&_8DY08uU~Y-0 zt?qnPLgvf&TBh7Viv?Frfqp$`rfaQ+&r?6?8ulV&sqnMaapy7M$O1`a+mpQ;b5ZWJ z52LtAI?#p#qtQ(mp*=%3Fj3i9jufvM|iApx%-{ouKoh!B|MCm zG9EHbbtG1*-(VFKM7!WcxC=3zih><4%rPRPBIRPrF|i<%$_djHXfn zMI#cq^GjX;$l*JTY{cSilwpu2Y?^pgoG_aKO~H?&3FJ{Cz$N9zNzMM)MFoXQm6QV%IK(>N6^ZfJ6hpHGlv5 z{LeFgUf_DV_XZgERtxZbCk;Tlkbd8_;@e5Nyp@ezd%UYCfubK4egl_ifVanZ#Y(=o zTmD8EKf<;~d^VT7KS`76KR6l*-*vt+D<8Dg}(2Od!0znq@Ex)J7fPU3L{s} zfJgR#5=9P`ApuT5bb)?!=06}&Ae$z+Sovd%Uy-86g1?scXLP_Dz8=CA5EsH=y!W3+ zx55Wff};r$5tz*&X-pMWzdcAU6@^#0EYt+ z)icA&-SmvGO#1pP;Af)um*B=vWex0?T}BryyDP#M*%Y|u0g&(iTGN020tJB309=9S z?LrX;>VCr2$Gd_iTuB35JcCg02D81?Y@;V-0_|F;wE`{3IMkN6%p_~gkq8Fw4J<_;t4zS+9guk6y z)vG87o7||^F;c-Bf!a}%cHI;(siYVI4H)IT#-?CX%MYuKaV^|KURijrzvumXB?wj` z(ETa(RzjZgg-H;L%t*r^{r1&kD@p<4`!o6iY{4%knibV7Y=vhoVDd9}z!Tvdi|wKY zVG4b~B)*?fgXw}=;Bzvy?QWU;dD6eHiEQ?{1xcXSYT|K0sf18k5Y#E$Pjm$`qk$Vp z4hKRbeuOXyTG-tey-8E>yJjGw`d%7<@@r_70$9-jXX|>tC<&d&@O`L(D;ow1waCxC zP48a}kAD-7k*DP$Iqwds&=>rAk!3VN!|v=if0PRqz9Xbw1x_A9#gD|bkJK=XmJike zGHcYp3mk6A+Q@69@1|FKSLl%@mkB=(-QjnV>THxEY-hPD2{?$iJ4+TSPG>~G(0^ZM3& zVgFda1_kM3v&MS2Lo_<2fJ=_JqYF%EA~F@i7w#c>c8xqRuWzjZ(;$Ig5Bpo=hNuK^ z+&IMC0yAnT46Q&Bfb! zbw&xC74vZcDd-xE)e50T3~u6a$I&|@ngA&cK?nCU#H?O#oT}BDm7O9BD{EL5?sc#> zc9%Ua+};*-6Kd}K`Zz46k^x>`qYC>;u775tv5N0lP2JPm2U;v>h1W()8|Z|?yy|Hu z6i9W%Q-VN~*oEHc$S!<#Ld^hcK_?c-2T&FLmWqFlYPL=Qn|M#tyuTAi3S02;9X}3X z_|s7V$_Iea3V{%;re0@dJ;U&}Xfli`%kZ- zLakVBE1qls6e0gv1$hvt3vWK`#W>mtdoKRbt&b@Trjfu}3T`^5cpOv#CxSy`*&OhU zb|uZi$<(h)#`nB@ziR;Ydwln`$sZ?^{}~*A#Z7R96s&Xt1M8i1lNbI&q;-Vf+|uM( zpJkf_x4@hs)ZrVHZ^X@;T-gKa#)4L&_i1=aL}*RXauNQ*K|wr8_ge>`VmYWl=ZUa# z+*UZ}Y(mpNk71?CU*#`k?SxSQ!wGG`+Y^?uAQF(S&@fS;5^x6b8p-7V`lBgw6yYW$6cxK4cqAIRq zn7cS$2u+2H=ibo*UWFW)$4ICIuMMikqoDwRD?fW3?*Gae0`T3BP5x+L{Huk2RmiWH z2E8wR@TfshhR)xLezgA0$L)Kux0Zi@YYeQJ_0M8nLD+Tn5iO-{r z!K#p-ApsqIP-y`pk>BQkx5?Z_iLix$cRkIwV=w>xIkD3-V3OC*{J7@Q#{$N!$$r*w zp>7p8aJ}1~k7;D@|KuVV8$@m-7n?w74}4ZCplATZ z^o_=gh#rAZ`Yok4k=876sKAU4Xf*F<{dGu=jHNkjo; zK_;+v!`C#xck}Jk7Qn>@U{$crD1o!^pB0q9TJu|J0oLn%+X~?A9{>CL-xr$K5!bm! zlP0IQTx%qBCW>V(YC|>#CN>HB1ilrI>k{;~21n>8%38=&qYw*!cA>kDH!+ogGKrhu z;Puqo2?2SZpNk|H`a!&RSNyHf{pu;z+dW997jLrdu@#*Pr1pd-dlyH_zlADv z%H`L4+^I(+AQ$j+C`bz8^v(~!g@PTmk(i6?SDDrqe*c}ye=4zESHx9d4c1!-fCwEz z-&i0tKnkv(3sGfpvZHw9iG=`?A#?~&|IG|17!bA15pqIbqy*SgAZ{hdX2j{d(fKbM z05pwDyJrgg*`H&I=J8p0R?z3vSO|-i{cp|60R90kl#Bp;8{Vk}ADz@6MGUUo??bi< z&Kd~4j`-;vlD}acHvH!KA3fUR9OOxiR#&BhE8R)47Ry}WB0GZJs*7jw2j4wAh4Dv2 z79e=k>(W3v2J6LW>t;wB4RM2FA&XDJ_1Ztcn`WkO7MR8u&G)egN3zByw};ND$eT$N z$EBx3ZB#VM;W8FDqZxesnLevJ3fd5G@%fM|ObrhoC$YUj5IlCL*7s(8u3Uy1Z-CC0 z)>lwAL}Me|^3Z7$Xi8K;IGu%W;p=f?hl>ok1A2FVbFY($cg;$jd!;tw#Z^Y+u9_-x z@>AOrtroqlnKp>r~YJ_GV$`$!Kq`CglwHdgHub1$^BE_Jv?JkJXEBWS^u)xT@h!c#+@4Dt72$Gchc*1(qR7-)&NWw_a;aSzAs z9#ifp<(t#&hBWN41p?6(+aXub^6_NeJQN4(bLdrp9~Y>SP6${VgGkM|n6gdi0w7;& zR|G4vqSq=-PSUn;Ml^vWA;bR7>vjqI_peW%Yn67WEPr(aR>zOvc&6-pJqrG~=r$>T zlFvu*1z7CJ7g1Rk$x4oNQ(4O5gx3cN?=0_jcd-48&#xx)CV7sv35s7&g2(54yt8vL z>?jFwTgj0(JNReFhq6YFQ?dKnuqVRqX$!(DTCgnc5J5-Y>jfn3Aj(u}fEKmTUDrCl z+ys2b-T(*$;5*M-3VWcqpXmbZBkBvT9^1KZfL8qH3t`l=N5mis^G4YPurlRpN4$>L zU$4Izv9WRD8X`;x=%aV}S%$<=wBr2ruNxI&J^5CF9u~z)t>s~HzO2!Fbm@oH&bAsF zq0Hc_`LgnvIa>wP8Ukuu46=!-U)7`3ZjxCfSm_Eb>|?*mX$MDsZkNiF;>n+^e<^QX z2?2|xFR!x2iCKxyOl$)Ht+xOt32qmD-b-&ogg|L=rl|`F*)O2)LOIa+n6pwM2`0B- z4s?-jaN_(Yt;F&6fteN)s_DK^C04C_LBRSaCS<@E^@#wY)rfYFt2@>-9{2;VL z>JX^LotgVQW(2hT_gydl|7)EF;5#4Z{;vf4_z4T^55WA7G#Kj~fK34SbYTNwrpCZ1 zH4kA8u-|(hRFwT>8m?_iH4=&!;8FAdLpD5Fh^Ntkb55L&(;%&l73#v&!AJ!YMR9Ku zs%FS~rDR8Aql-d-3{f~08gMj%>Q+v|IY3ziD-7q|c?hDu?IL4lg38E~`YI4=d*5$O z3SejZtZA0BO&b^F7;~Fwkbpr&+>y;#M8q$sad6WxeP^Nr=!7}9K9t5S7vfdnTsi4& zf6Wt$A~j0ZOw3gHtV28Edm)nU6k1>ng2Fn0?lvn;f}%=xv;}W#j?Yt^KkW9_@;r0c z)2{B~^X;PeLm2af@ojhR6S@*>G$;Zd@qSakeIROZ z)p77qFM@TLkl#=D)60o}!ka&T-2Wd9{I78YTb65;yRI(4W7!n&@q5@i;7k1|w!LZ% zt6N1}VK4}2n?alP)PgV?PQPma>)M>j9Qo)W;4O^-tW0eb3gXRl?trjN?h0p}p7m~i z`E*8z-)c6X6ABgIb~4M}281$5@~R(5k1yON;eA5`LaAnRTkz{3Tj8SW z7eYwo18}7YaI`c(ivWM^V|+RmF#YN=R;hqjCs57VAepk)9;WzLI!4zwXUXp1Zt3vLKvu^&mSn3(6+l`_9W)&^=Nyj^9C8i*C8 zVT)aSg(R>u-9d1SGHH&&WqVmgJkt@_5*a|VAm=;%Q+fVP5NRQ_h;;%T;!(wQUC^@f z;b$RfC-lqeehA%wEzU=5s+Ad+$bFX+HhInfwTE|tWUP< z3S?YuTY3O^KlOJeLx_>L=o19H=bwIo!Z>M8R@(&NhHgUc=3faRnvEgTyGd@=?SKvi zk#v;T&qU$qdBu+k`F<|O(W#{K_AEaCs73yYE&!K{zoG#4C-}yfKfd9ysUH_P{jzsMHAcScu%=azkfrG#1?nFv$=F1T+wxtX>eE|~z`aY1YpG>LwS0_Zq-bbISoq4!R_ z2WoUmfDbA8ONfyu#!TD9^}&f;}j^3R=+@kG}Y?0-@LY z{p*Ip7RQlAc-DvuHDu1tg^coayChMqADe+8`~fnZV2w6`X@sgLP#ORf{=e5im}o^J z6`(XMb>fd600M1uMgvfVX?t?)|yVtGG*kjrEkSxcoHbbA+yP`*U6F3ymQD z-Ko$S0a3q^tJS zyP|4P&43cEz%YOKohVHe(;SgDm<=+}QE3Vwogw}WB2TLJo%(wF@VuJ}t#Zes zIfpBiJ&N*R{cfBo_OB+>X(YiK3uIpN8(53|GDKgSxvY!mUR!@&JiJ34)!-sFxb8Cy zVa8-a)3Q$SL!km{h`E-svL5}xZx+!|P3LjGsmJ5T3vauxQrAU;~A z5Qeq9RZvZe!S!?Gp>;c4{5+-KxyTE6hTgPLl!{2)ThR@h#sG@TXJKPRaM;5iv;`2i z6-JKsZMRK)VG1OQ^9-Uui+0jAND6b9`-x2@&_LuMfPuu`} z`TCyz)#B|B6!$A-!!PjuS6dekJczw(1a6{~4;c}B1iqMTu?_(A_Q2U2ZLze*t9`Vt zdr5tr!gcP%hFOBLBlPYUiZL!0=l6Auz@pe+`B|Nibpcgg{~Z^<(LiQ(HSg>^X~f1v zA+GL!S_`02nE0s}!d|$ZLzTiP=#18Bd*>T!5=>~PR=EsHi7Jm7T+yYx{ z0Q9bfHDXyGtx0`C^jeXJQW9l3KRpFt+pZ4APSDTaY(5v|O z0yvQJ-&aa|pf6&poqvZYWG8~a7IlCY(y4}O(nMZ_icu>d5C~&e!V}@+q+I$XUE#F- z0PW{6yYtta8|B}>1qA`wF1fr%w1dy?gc3Cn)PsB7Vlm`N-^t0b@1? zASm17h3`RUmAiCS?CgIVWTkz*-gE0+YT)d4^j&WQNR)_vi}t7BRKV30&#$=w+7bcS zKh(ea{V9M)v;dD#0|VFh9j5q9bVc(u6O#ZT6H{SK!Y_FdCpCwNtQUiyU@3j|4L5NH9R zn)sDdq2lo?1>jOTSl2PF#VO9ttD5w!5RBc*AHUNvw;}EpNK;$_Lh~ zcB|?2Y)5~|y+?IE+vcK9kVO!v?2UnC>~I&zBVW&K#C9%tdn%w))nj(FPV;XNrJztJyIaW}-T`0o)W-oq2Tq7~SDX>FCPV0GqJDWz{0sLooH&bV?2 zmG`n}8YS{^5rXyrn2jI2T<5w{pt&hI(H(T0G#mG*$C(QXBU%40Oo!P1w&);4zzrh< zEE;?kZhLC=E(RKPalq~Q!h0y*Cq%r2r!-CRQ#ux9 zum<4u`u)wLxyQF>9iqMut!;GpA2sRW2s} zPKmvQpqyk^5 zf^Cw&S$6WWL@*x$I~ACEbuG>oll-IbxoY3+!tKu9fpGyD@PYuiL?%|KLVmZJX+Jsd z;nGUrXw>oiI9E0nbR%UFo@dMGXvAYOu6o>GdDR9CY6`l2gW3>v4ME5IFTBdtUVMq% zoq~6dZi3z3{#gT{kO9*)f>L>7F?}wBhZO&Qf)bQMfpvgT=ON$=5#DLx7uyu^A{XCx zHpiKS0?h@tZVU4yobdWC41ZENS&2k9Bwx5ooM_0qq!w*hsK}(9G+D zY-ZGz-4zYs?R5TH^Z(X{0DSAmgXYz3{38|h_bvRDdHn+39RFT__Q@Rq7zx_%gMm^> z*f`^N{4u{QYp2y2Y=CjG;9q}rcFNH!n_+eF$AWLi$@E{3+lx7kEja2`Yz=E-GkQRU z+0<&F3w1nCx!Fk&CwujF@tk3SfOMV!HH;{S?(||rOAP`y9nsvUG$V1>ep@GXZ}1I* zE?KXIX5Gpa6pVAR-X&q)NwI5p?I%jCt18Cxq%iaC)orJ1l%d>ifdrK2Z7hhpLaz!| zbXa^9%sCbPSj3+_N8wIUR2^#rpDEn&%*4tYPsK{zf6)VA*QJp0&O7Y`>Q(_4YqNf} zZ!VB(BTM6iER4`;S=cUcF)&|o3~WpAx5ftGn?JnRpMlSx7qW2HbmSAbxdDE$dDI!8 z{dj{R-qa~3=Yf?JQR@f%q?p&D=_L9Cnf%n^zFO0BnQh>*^iGt;dC%m+nM|{u6tKj_*%mmQJj*1?n+^Q(BQ|#PQ7&e z>vq(w$wZ(D_6*sO>2ov}}Rs#U8A zd!XpoY&zowLgzI$y*H0?9}}sKbx++zDGT?pH%oW~gl1)HyM5nRk0~e`ovL>}-Kz%2 z=pD*pL&ok_0REa<*pI`ECM=~zF}jKv{T76ak&VWzL=^?2xLIrVx=$edt1-E&NqJ6T z#p-JwT*3dPC{I$UtHeCf2%mZObCD^lcgk!DjTeAxhs3TqQhW1e*><M4*?Z^U8&BQ2=Xc7)qbNpM=GYB)Vw69RhsWlBvJ^~ZmdzbT96a!}M`_cb9pYd@~ zZ*bP~p2=EGrt2Y~4`}odRXA$P&mz6zuM{m=OR zb0GcR0K8oQ{;s|_i%Zu-h`ygU1P?!sJ@9xgZ}Sx3kIMTW#r~<$1Q-y&Vh!LMl-bXU zEV1^ibDS&0Y!(Z8-R*~*WNU$~g;^!WbtSEzc+YhSSloWO|8M>DeKmUO3S93*>vY=` zcIi0ShIj}RA!kjDwyE(ZFCvm0Z?@0}hp)qra_XN8+QAplJE{kL6z-*X-dXu(CEi;J z;bfFA8Y39=5xpv&H);V&(QJ@}Q|*B_D`#EPfhy|52`a+EJeA%j=>t)mcW(afPw&mA zoge4fQTP5;F;#n{lM+`b!PM_jSu>nE3Yuvw0PXI+dsFAm>hS{t zdY1?QQ2_QK5d5mxe;~DR`M7M25{Unv%apF8fE8VJD;H*?Ou!ZIdo&4WmPJDpnb8#T zfye#(R|WAbvEiza(Q)p^cjag4oV~}#)u^dBTT5Ia0IK0}ocSH7gg>WGkQdm$w>(xF zhk(Tp)Q=Nn#>A`Wdjf3c$th z7+M;){hd1p2^s|8BNW%qqF}#YL(*SJKTS=5-wUoK=zlg4{s_MZqd^APT9*xbteMI= zcV1-)L^49TvyJelClTQBvzq%3`i>_$U4}`64{DP%+$2R)#Gr#KIvc90KA>N zZ}_-H_wz3J_P{;gE`U!|C?X9o8t6Z6Xhu^YpiisoY>3VzdR>66E#_@&JnQ0D8ErV9 zfooCXM#vgSx+{Vv2$L*KMEhE5S+1USSQhdMbE{ihX`H%8wRZLH95pygEX0y}3C(0E z`W6+|b{2_jsKU}1_;YS++`HJRP?Rw30PCiqi<@PBzBC%Dxln6%=mlSFsso&F2DGYu zztGe?VA3-M>^pr;9~6zkg)IuU^o_B$!1>O7=cPYSUVl1 zHAM6Xi{q29rE7a%Y5w%Qfc77x0Q&F$ov{J<y~Pw{r=jKtb=zd8iTUO|oO7=EJntCSxUTzt-nrJd_YRq`zO}yjzVGus zKkqTdHLh`8AGdY8@R=I;>x0cS0Q6Z@v+Z3rcWkQJO8g61Q6hSA#T_r~T7RGI`;O$d zlg-77-xp_5`39^w2M4Nw`#&xxAp9Kc)c=sQ$q1Y^;jB6YS)8YM7q|-bLB|`pF_1C= zV%5KG;CKb~8#mQC+5ohA6qWa?3bw^;J*`YP)$%3j;n0DWNdwPK;C)1$aCMC8+^}u1 zi3k+MQ63ELBFHK|hFBOYkpRl7V0sR0?FV34bZ6=|lPRlUoaUH%_CE*u;(Gxr$I6R9 z=@zhr<2oi4wlM)>0B|W9q-VH3&&@C#7-VNSr%JWZ{59*$=9-exZ+VvAlk7Yg`!3{ zGFKpWyORm2?eZ#FV8@;luvwM%O7NfSkb>yQQMF_8qY}4O{PNMofzJJ&8fl1!LgF9I z2*3}1IWYfw1O2Bc2VP_Se}Le1Ba1jz{~s=VkHcqJfE>aoX*Eh_OK_hD@f8|@0h$x> zoVxVg6CK@(sK?`CEU;=G4zJm+pZo(g8snsWkbCr%8P9{ak_65Hm3t!?ROni+I9k)f zsUJZ!MCqvR&+}s|Y3DBfgWdoWM4W0Hrt?2aV2^D1YofnxS|p|UxX`2ucoYT~ib z{eu|+_#@vtnE$)WB^ap%a8&-s-%M<0q|d2MaZtM*5}UZXW|)=CdJMahra${`un>0rCq&0HbIISo*EFk3uQvnTA1)DBqcF0$(hu zUN(6^D&?I`?@APB6;v77tuXNyq_mZ|&($%whLHHRkwa{O;BjF)VbxcsJOj}hm{wovPG{tUL_2q5*k&ZR}!Ji5yh^~uufKN6M%c`#YIww(%N2B4o9;F;)##GPF=Ru zA(@oTjGaRrt)K-ClY_ZZ+6yHe8+>8?6h*$JUcW*R$bLEzK?+j6uG#~v+GDPW2rD1& zpQ;}{`}@`R#MKsvZw!)5f2R?IK%o#3TqYOWgiSaH6NT6d`b>5Wq6T?v542Xv)X9uO zqMEaxb+^G~WLP8^ZdY^#KA0&%yzhF|{@wdItWCanO9HCcO2~kyD&q+Y{Oa;#KegLM zSX!yQ&r`hU{VABg12+oWaTT1@#;ecZ$lA38D$4tv97Qw<-AG&Ew#uLXu4R&f)@$YbgLJ*fqZ8=Ckd#;XKRgyy{y$d6mmfRT8 zDPWNJ>=ojwI>+%x3}SAbP^id{?`}gtz@B6jw6zRW8)uP_S#YGpmEQtER*az}wr&|H z&3}pxSaJxQyspiohLcSbuxN3VreRB0z?NvgTM5uI0`PtQ^SKa%`^^6byH5k)>#VPT zrDCqtTkrM9qG_=cf?3P=8?gCNpWf0}gh5T;OtrA~eiz~^` zd)qIMUYkecim}cQiBWV*=8A(T^)*&$FN9ho58#4g9WIt`7Xyer7T9LgAfk+YlmO38 z^=>7|qm=)cP|Xxt@H5urI~I871=84R0)w?tXtiv;Vs_srb1ZmE@u1kt_A(v|OBm+t ziQ$|&Va)9i7o@$C>}3)lArmqVKun{$4QCk{k3jw!1~54V@cr`h`I3}81@Kjg5>zQ* zW}-8M-ET$;rg5_wyh13|du5J&7Yxi+<6??k?&WwwBrtq?uR9j6Nt91T_s#aO(y+JpuRrR1oiEK)Mb!rbcYJ$wM?}c80w(FCL9k8)7tZXAu$V;ytw{; zmhdPHKv6w)5cKU>;x#*?U2BH(5mxKW3ON~@#BN+j8u;rEti^(x$Dih0 z2Q(V=L<^x23(0r3N(mk079)q*U9ef?0yCT`1uEyZM@@xJSFc%v;3R>2E>M&l^e2$w z=0y%gQzeAn`AX7w&VJ5Twz^JqAl!MoWp(XWfq#V)<4T1INqw^58@o|xB*6jDUydm`Zv1@=|YRY{7=@7|xNE7$&NyJdH8DG?)Y%6=Qz zpH<@bR$-|u{W9t_*#@|9u3t+{31r=aC4=0=lIbjfM_a3ye(|wdDHvWs0Iopr8*3YRju16*vMr}=lJ#@w3sk`_9<$k=tHIBw^U4Lq zcFfiREmvE`X)P8qub)8VQB zBXjo)P6VC@#bvm}kqF(p57N3oG(=VRh3QZD0@+ZCRoN4&>{uXGj1*Z~RoLTaWIsfa z*k|yw7Z89Y3GmCD$ooG1aR~lQw>>lV)q4-1(O}n8neeP-g|ETW zSNik5fyc)t5S0Wdu8|CPs7DFZzDH8ZwnLmh=W@u6r5_yu$vdD7j+73=Zl8DD0Vluo zcH^;LzttYI3OuWX;8Ozq8E6RE7dO2jR3!$XE?e0W=wQM7oUXxaN%COh=jlV{GRg|-80 z6~H(rMRFAUPkQN%a5;M=zr);B5@$vN{i#(tJ-2@Q*SA@VkFl5`dfWu`H~* zKcvfXQwc!uKwujP7n1>B!1SxO!7k`CXxcW_;+2F1?RsMoY*irJkreI1x!5h4SCfpZ z?Ecua5rQN^aS^o`i&n!b0rO4L7-WBE+}Fzr04O+g1&`Z$)N2PfT}1@qVs9Gv=gNNN z0JbAHQeS|n&1qHv4+Bln9Pr1^%MM9lEr9&@d1H`@|43+ZV)R-)KW&0Jta^COSwLTj z7b(Oh6E86_T_uM;2$cSoupWFE&rfGo{bNruR`D**`5Og2@9SZnRLqGI#$3KL--j8g zut&52{y($NK?3}P)I#t(^ZyHg)}PFp@edOHa%KPWKSLb;FwU^QQrVknqMj`(&Q)7L zwFwLZ6S+|k21kLe7Sx;veLR3;9FDdL5Q*@9?hRgV=aZaV)xTG5mMio9SK3WGG$!xE zj$^@semNiB&*6E@aN{9&=w9KQHLKs%VoH+(U&FrnXhD0Ou|5G^Xw~?tWrC1+g00VePA+`%E`%BVRid>_Uf5`$>;29oMD`TrB*WQ4 zb1>+!jbS~}xYAZC3B)953|o{`BDG2OS~5gN2O=&UgOeo0ZR<7s2Z96e31VKC-x@jq zjR?8Bpc3(xCvLe*dIC9i8tE><0{Q>jQz`^#V9 z1SkTbdxw|v`&h-N7V*`L`XgvW?%O*dSnc5NtDFUeqA=)K{y$R@v^_F|VmGWka3m8= zu~E&WuY&C+0b@zw@x7kr?pa%AU1IE$7>T~H>iz90^<0T77=Ku0^h3Y?T%c|wA;>Ej zhz1pPnJZ<&we{#o0G{l-`SWP7_s_Gfm$!b!?m7Tj@aHDr{UC_|2KBHm@!NhEpM$cY z1HlZ=MPPwbQCMsh*hI@?h_*xf{@4fFE4eSLsK*2Cj4KllyMB8k_{F;IpdkI-gvjn* zW=Md0M^IkY&M_d!1Jdtor&v0|Dq*&uuSi)2uxEpU(_*;V&XP1XA43%AcQ_ZcVf%Rn zA7TiAMMoV}P-Io(vTd+TtNHepguYebK^8LL79bkOW3|jemL8EpsHr*!__(RRtXBznU&BC z20*&K4M`3)>Df(Gbl}C4jQ#r(7=y1)?wbl-Aq#IS$4e4`L72_2(`f>b%SF?u+?L4! z9FHs5YB&khziMX*Wj=b&W6@4<r*(=dbFEUR~!fyh@C_t-ul6n7+v>YyRqUo^PJ>il5ju@gh`|*UU^kI~=s5>%< zfhHjOFu?`TAz*B%!N@6KYK3w}#P;fn7&uY0fs!GQV;pz*xdr>DcU1keo|Bd|h|$#6 z8X?%4rzD^$M6zuHZs<`p5}NX#&}c15!Kn8`ds63`REvqs5*K~+wVVLlrvSdU)&1wf z$JfqBB?tSEzV{FaDlj(*My@Y^doE{69#)5q72k9+Qbwx$VV(FzlMY-z1)Q(O_;SMF z6P6%{N00;`hXCgRK{pnVx^q;n-R?oEVho@m*z*DV4oLvpuMpsXi}PQ$6`cRzM+|}F z1QW0f^<&XlzKaBP)HE4RzAFfy!@b+oGH122h%q9Rai4%@jzTJ};?gD{+F-^h`1vHs z%De!wXiEsm*C5Aq1n^dQyGU@%q=*m(#2}S!X(=plvsD`-CsA{fM8WEOJf`$? zA!2=f!ctuZUAEvelPaNakogX;*X^zHj;9*H4akC&5C{m4a=fI+I-uGgeKH|Jpd5bx zI~4Po2F2gYG4Z#*=hc6F`Ja33{d+Y6I(p}d18@bY7{(XS^G|dVAqKJ6+aT_Lw?xCm zp`)#|>lyz6Z9I<)2nDmO2IPwd{-~lj_I6yS>TE(C&f9#ZB;i zBKtlEqLs-|Tj1MO-~oyK`xL4Xwpd}kdd7!N`6le7bhm`UMa!c>@=)0~4ThHh__PUB zd0cR%y$?u5Ts*!eF>DKt1VcljqEyVu51-)3jCzJHKr(x_OK{=@19yrFCprRSh9^g5 zKoWq}PbOdb;(QpluGaU_Z)XJHcfKpZ|2-b8pPCS)??N)(%W3@qXaNjWS$co{2uN7z zH*Em#N^c&zcZ&u(B{(XSf$T30T+{kk9o*x{q8~ZHMC~Pesw5d|a_qY)q=KP$Y&sy; zw~sU7P z17Hcti(zqEaf{Z<3FD8uSkBoZ0&$&I?R!OAnqZWeWGMfyNROe3fwUq&0{bAazjab+ zu!@Z5a4O9heXX{~pc&YAf2*uZ(lV>1 zuj+p`LZ5f5*?c)>T2wGNuHbzmC`+EVN6X!^Um0yk3)eXcr=~HoE1vU~N!hR9$VO9~ zWJJTA5syJa;XQcM5k%|ilr7=PRlQ1Zn@s7yyvi$J#fg;&uY-r+XEHG)mGQ`xJn5vep_V zE3DETsfUkkj}t8m!AFRdGvOLI+8GVWB_8w=EFRkyww@Uu;pKTnM8pXXpl#Cx8Bw4h zZ1k_h5ZhCPxGXSf1yFc#L5>P$c4F&)x5aizcibldE8-wiX+v##3kVPajQy1986joh zRbV51Z7fA7_boJJ?nXY~DPTYfiC92!dCge89(Q7!1JQ0d9)~w48O50@-6m4M^0Nx+ zU)-Bzd%0PAh1}>|^oSDqSb2dLboTu}rveV#+AmBTIB9{EYx@7@1mN4>Hx>PRKI_-E zl`H?7_-IZ9a6(|@B%6Re!`8iYB|=k#g+PeSy~DhYI`g@>Fk*m%lC+`q@bd>}g8%99 z!1`bM<_?KKhTq`?hVwy|E2Dr{Lc&o=n`lwk;@T@)&e&;m|CCKMV#(FpI- zDsV-DbAg072^5pG0MPUXh#$Fc?WApz6$XPr@F;>q#sw+NPUigEes2`=RC1t1l8#A% zQn7eq%dP?k^LHQw#G(_B8T!5tR_FV!jNd?`TqyW$vyxwpys+&OCU_U(}i_T!1+jtpguwb%OF*xaUxs^ zBGzZYcc=u-XVY^$AOShhfDHW)GK0UGgI|J52`LS)=uO0RJt?Yqm`=`ty0GGPz}PPK z`=yL;LKE@*5ill|L8~&rnk&G0T7c|43D;>F;r0 zXaYD-6f$dnJCYeZkzyiD%?wBdPX_zXJ_brlhFek*d}IYm3;HstI?j*15e5;7pk>Ur zIRfxy_2c794E$FeM#(+tQG-6xFj!?cNK%0iv#AO@GyR=gwld9nhY{Hj`{0C?@1$y9 zwLx?6Zb3fkI9fZGeuu!rDq2zC64L)40&Qgbna}mW{c~W)`^H(3!0rd{t?3>t`tJwK ze|-PV>u4W+%#oBQG95Om$VU8f%TIacpr(L(ClX?-Dq=wpECC-80nyi`L1{7emAL|{ zf{N(bY;0N%O?<{6(Aom7C;iq5NdPlFVycKmLN+?A9TNzO!7B_*6j186Fj8S;t_1j5 z6BbN1+Ho9#{R^F9o`{FwAkYLRtc&sNddegqO9H7cEQ|>=pG}oqfue&vHwDSS@4#dV z+Y{4TW40tfu|pn80`UKQY4H5#3j9}f0u;eD2S`!fPLTv)Z(T{?S_;(eU1As$gCJ7S z(qlda)PZob)p0|n1bCVmy#*#5r5lh?g;n;yvv$H;a2Gau~qYFY1QD$M6(4;Lv_(2!v4Iv;4gR-C4CK=!65AoL~XI z96LPgjL6s8r?KA8HU+@c3Gs%XJI_|?*wO7BZWmVaB}{Ek(*~V ze#kfqYILvhBU~Q?3wUjEVk7~ZCl2?seJgUZbJ6xdSe;nlhKa!J8vc*f^&;&7X|oFr z)SOTT3GPa^*~onvcD=8R+YjXoxoKd^RuU!+n5-o}z=;fjh<(u-D97_)0`N%ikI%53 z&zv-50^km?VYaMIFFxIOx6Q(TBgs%+)CbJqH^Ce42!kO+oA%>%jq@Ia&jf)i?sVp! z$n>HXfCU%dY5$)C*P`l0<-WofAp5N5``t7pY2_-o^4Ox1zs8YRCx4&q6#>N?v})Ld zQF)zJ@X%Hm!i0~F2ev{rwKXtdxWoMbPsyO}#9h#dcu;8l0Q{Tr29$c7+citzRS(<< ze2W1PgRd`;h7f_2k@%3LS5yZ>jMN!1N5a)ljIlIAq45zzcD>H0?5TdHRW~`=8e}}l z7nN{+zwkLYR{dDA;mJKadk;UJY)um;=0EPbWe^Hs2qB`iQa{PPe-?o0v6ckjH@<7V z{j1x1j|1T1`W_B|r$e8UzImMef{@nROJA9+&42-?lf>)7j~kA zJZ$sL-m`bp2ge5j4Fzj{D1wTNM{%~;*rc zln^x!0>3uL&(M^@eNZyVMnsB?knMco3~8MUQP2`VP28)BWef65>jKM`Deku?GVgyp z>^IS>v0wj~0DS!Uwa*sNl?uzFpN)3}|IqLMkP?BfLO`Um_`#LkSe}c~$zep*p)C*s zZ7QmPv!52090YI*RFJ>ocRyB;^`b*&7vAq=KNu-n3GwU#bl92`N@wg%69QK}FGi}v z<7>gx`Sl5Q{~*!|_at(;f~-Y`_k|Pm#MkGcB$feQ1qZS5fSM+t0j%d=rUX)JAXef( zi;5f*1?6H`$a&TSGi8r2+z&&fkEL37Zf+B#F;rlAVkpDNh%1i*XESVfLtzu15*1-V+5QGl2y8T+7EGudgkh(+#TpJ|e{vZMF z{Ej3~EPV_dH0)u<9*K)su$|;MTw~P|p)^uLlj`t#mmdA>)j!KyDgOt?HJ!ltOnw-v ze~PTD+&RC0@(CKlRzM)RjluY~Cn=-D9}DiWs+KfLOM|2ujl)@R2Hz{B0!4)sd64zb z(V_5!`@I|4P_o?YRcnxO!PPlXR?saeT9H37@QClpBai`A`O0@f+5DD7EV|DC@>8l}|f2n7X7ruo%px+Wt0JqVksOMYEFNN`MJb&(=es=jA|1@w>I|4ehgq?{QpXG;3ZySn&Mm~&g1$^7&K}&NSGE%op z3WAI0cv|zbSx6Z6Crg{!ZS%i897jK6WZ(1<$+NmxU^}gWN>N=gpvNI_llM|<~8v8pBovy8@X~&Wk9G8qiV2y1J8`vQazve!*d&NrQJW88)?Wr2ioj5IPp96!yF( z3(~YG-{|Znb8czekbr!ww{#p&aNW~!8ZuT1SAYgz*2h;|k<7Zr{$qmEUtfO`SY|>~ z&Ie|444DOI6b+(C!b&!Hkl){G$*6f0zkYl^Cjh_xSuJ@V^$K{T@{dhY!MrsD%u9pt zTViljsW-9+-T>ltC|EN24;A+^q?-c)Gs-yvD%1IAtpU%ojwX7TA2^rt>awc zH^`&n7cbQ=@sSMssOWA>A0vOMz$-oHr*17A zpD6}CQ)^ooA&$k{IDMg;Yg6(10iI(_q;!iCO!yUYbLKfCK20v}c|c<>|2Q1b93oD=XyJ zpa1*#M@|6#{<|UhM@RoIcv47|;7a8W!D<*c4`mL)-o!pK$2I9JJJOp;aMzCcSDod2>ejnNCUA{UH;aN-)bi3B2wS2ze$SXMKb zK3LEv#K17E@F(Z&Njo7H#zgsd2$}D^1sEhxVH#V1KPC&gGF_=D8moN8=pnwpu>6U3 z!%4^lBHbC-N3|FZ5GBghJG(~b!k7Eq8Fru0KZ9yM7A=Z03Q0y6CAZ{>uY851C~(5_ ze7-^V5>Hf9C#(;c+Y4VTdeBZ#02b_#WZ3rc?;qRA3Bb2L3jz>d(r1Ng+Oh?Oxl!C( z0B!`PO?}SB6^I1;v#Q(DGy$-#J^F%-fnJSvxv0>`hNR|U((9cC9AibB)t14jXRxxI zC1c-bk`TI@FTHtYosG8x5&!YUqv98D7ge3JeP!f5H&Ucr*dCIR8wwHi`ckg|_FCpq zga*)^BxU#=hL*vSg}w>QVY>G$13t;XDKp=6;6MI<#|-GrQK9aOiEtRir4d^cRD35__;0XxCeao4ziNrN2-3c|PC)v@WRU3}v;@wf8pl@0 z!lGIQcbq_=?vb_#{DPTIWt{W=A#^(y@H(XQ1*u3=B(2@>`tvKt_v8fN@4c(~w~MQ( zU1;6C=eswF!~tg(S%(Dc=N`Bdn!%y9G{Du($UQNzI>(kC*yfh@L|i*0RY3>z{~1J! z!Mas`ta?su1s15CBpjVS(k`Gjx?WoLr8cmf;n5KlonY48bJ{OT=P@{qEG-!BYz0pn&@jcm%)oNoQl||jBd~=Z$wNIq0ci)(CMTptEca7xfUXe*tWDl;X|eFh`^OQH!=Ni zEPyKkAT|1jx(MkZX)@4WRrNwts#}pDv$a4`Tv^%313LPohOV-Ig-rqc%EJ}>sxQB6 z#$POFR=t0}o;aK`z<`*2NY(s-Qobe!WPC0M?}=I=H3||*591-LNAilop92zuU&8ErO%M*7+h}D0P zBxCk<2Ymw26vzc{gjWA6T?Jbx9a`GHD0Wrbs(l8P>+TxuJ{0lI2JJXMvkGQb<_w0epgBo&$?2U$OLA5J720+?N@=pIj7PiIP^^7m(Qv z5u|D0c0RU9xeKlZfvB3H2^uix3eU3m<_X z5W8#v1W#D7jpqifW85Cv7q5I#LaaMNQus{RR^-~rry}}&ifnf45Pnew+!PWRj2ma* zf+oP6971n$!#OaL{YQIYY)b|n8xf!}NCuD)hlISA5C~W~2uUw%4@~F zN+1Z2+o5&RrUU+N{_F=>egDrh0eEl!e^>p-h2Xy<2*&lq;XvbFVEwPy-t0h0=Q4(S z4s<6$4gR1->u^rQ%l&o(9RRck#(t}l^MERIZk<8iMp13i&kik(Yk-Ul)50`4Ftc9& zAieAGo+Kgqk?$)PeRcjV{v{VKC#$Qze)MERxgHJ| zUINOkkhB>hI{&1!K4QhuAptBF&=J!LV0!Lq^pN)Z)yN-%RzckmE8+4zVhELy3K^bu zWr~6AvpZJ{ET{`fBq$R<8yZ>i7kAJHNL4=NFEF^B!0K%AG9p*mUYp{65*`Un9~ATe zVv&5gA{HjJBXao&a={*?G9XD;3x0#VUsU-8xw)nGOSz|iIqy9~0NxMr+XmwI2ci5c zegn8|INq1*_XFuyPb5%Fj?CmXvlOz1_Tixd*hq4rC1i6Co}qZ|)<(o$bG-wK#c2_u zCtyt9U1(OIOf6QhGob?+CJyH_{Uu=u&J~!s*PX70vm?B2I~-g2+lyRa`Vk7+X5z5u;40$ z)W;x56+P|>efXXtkb=yAOg5ebXT#QQgW}qk)%GyCN{L1>5y}Z<@*N0R*q6QyFd1-O z$864%&-Enh;)WU3uFnGPF>nd&mNuVS+;T z+>MY1XRqGNy=#Kq@Au>^E6yp>t&?Jn3EDX6-#1%e=Lu7Le{j^JSw=JSp6GlDG$2wz z+icD7Z_9@0{Tu8%{+n?u*!W1Z3pQv)F(RP~{!}YnJpVbvZb<_$p>tKvgoAXsPr2$P*hcYwgEn>4f$UD9kUS*!aG53fC19s1UYMBR4vFE z1Wh3mtS)?r3cr1hSifTCqYwszDi#A9R`-*Pw*^)Ugbxs#{xNq%831IN$#q30S5?N=VbXwY97zMS z*)?GQw`*lO~zOmhCov7v{;YLezC1Hy}&C~SM zz<}gpoG*G6j}`0yiy22|N-LY$3 zk?eis=h5v(wg3I#_r82a1&TKX8q>_a(jAxPma0Neh!ooCHWMFm4lzk^=+!G});6w) z{dj_0mRn&VMO-FbT#qE?`+4TdeN(|R=e+;}qhZ}20d0LtUt6Ge7CjLk1m&2qa4$^j z!tkCjpk;624v4k~GWyFnx9N!lHF~aug68D=+t80l#}}Fhpb(UD-w9K?--+GJjgaZ)gf4~0x=U;kPFaJ{l{AwFJF(3g69_RT8 z*0Kt4Nq&{P1);+m5O2a*{|;v!FAsgwuYr>yQr z&ov|%C0U9d&~tUZB$PpdfxYNycT`}g;Cbvn>ErK!BGe&~U{D_3gS1SP@P5w-fpHg0C++)VB8(~ohEPrj0xc@^z&bZUj#kaY zTs1*D3}Q~ROAwf1oPxZOv46A{Q0IWb&=I!uk5_sD)Up_p0|v7hA^$O?fo#*QPeeGI zq`+z`vDc6DR5TT!f-fw6rrw8$LuR-RKnjdk`V#dCt{c$Q0|oTYkQF5MES*qtLScj~ zNfW_uAa?kHJW$sXfJa{Q3#|cPK>u+8{wF^p!L9yA2s|pi8*67p=fL<}bQ%~tpH#KS zd(lZSa}@AgoRbPLJy6!XSz89qjsb76f<@nxkX&pTv>QqQUhy-6Y|j>hFHsYknUf|M zFPqU#om%3AL_89}f+Rz-cMc5xIcrOzOf4Aa2sCa{a5>&!Vnd62ytFAc>VKJ6U>gbJ zlFURUgC57v)B;O#fuvOF86f)X1-iEdv@Y5KMV{7xAZVQgktY?XjsU4DBw3Kn_gm)) z1O%eo|HgkeGa=T~hkf4(2dU!;?+d|+1`KlZkFKZ}tFc0o5akv~srGTMp711&@1N7? zxFzFuWKDyD0Em_dE(%oWgQ@O?b$i}CF)?jxCETJ#e!^KxV96EuI{rc>0H0RfUmXF= z$2vF1OtH}gO5hbrAF0wE9snXe$Lk>gcgPpsH7fk0UB!&5c);Z5|#a zflU+tOt=psi`_$e2bgu}U5&2=n{a^r%H}_ljZ4PDcn3L6aSv8%x!%oe8ObS@5Bqst;Rp)kzK@w<$TOs!o;rU^FRV0vn5cfcOk4yIyC~3*} z##4WPP~zOzaho4VAo%(x!~2ggcUQ0Zg=8XhJK*f~GfC68>R$QD4+!|TfNJtBp}0lu z3nMZRsSQA@EbPgVU97GgZKnNHCM25PjQN5blsKpx%E567MtK)9#gREJnL9n+*o-2Cv*D ziBP;=X8)De0QQ3b9k@D4AV(QNx$$-Tq~8)wwEj&?^JY?tvG&9uBYHWoPM%bup-`Am zS6q?sj3h5!y#$)WiHX)q^tKIh0ZRENpH%XDWI+v5EmJh$>v&VM1mN#HJofjV*1%5n z?+Wz4(JIL1H4eS;gB*BN+^Du@K+KEOOZD9UPwfPE5Uojpy7k7{JUt`|D-xiS^W&@= zCE(bZxDm~D5S0i?1&9#7sj3;Jcqg~vW#2Qc+AlK~zsx(B#Ksrf0vX0fqaTbPSOFs{ z!66iW4yZ?gIKP=h=s-MdHpr;ClKcZQhiP~>b}~dZ#68D^m+=0hw$QS?xZZw)(Mc8FAb*u4#CqRXL0|TnT`Lksf}h$r8uY&jlaojHd-Ix#sge5kkT z$Nv{(z!NT>os@RUVnjs-j^}hluZ(EmfJHK(+rxrh4&29-1C#e{w^a1?$`>u1>^_K9 zqG4GPWr2gqpydYq^>F2jCp=6Dk zAnX(Z2{_R&_)gk3z{Cv;ALCO7h3tsTe?SSCNL51tLM(`_FK=37zukOL^;2g-6Yhi7 z;r-@65R0nb2<%JX>)gW*GJd9zfrfT?Z%Jfjyd^QvKy)Z4E>_3&0=2aBIRYV}^9jx8 zp413G6spWml#YJjwOu_d$!*i8b*c2~r#9 zGtrs>^nwOm>`ybrJ`_Jn3S{%c+`?D zzA&nqSdunkw_GMW+U%BfffGO=PFqbUf=FaU(~a;TMk2!mAT#-T-i+D_v+M3^8z4y4 z5P&`JW0eE@aFT5rIkI3<^0`ygh-&iCTyEXwVM46m5W(TKe;07U+ym;OO{A?(h zJL~;CGk+kd4(5t>SQQ4teP9GGB%3+W0$|G9G~o!sO#lnuVO6fTB`_vf=%K4t=@96= zzi*^}?WAo{+75o7{>dYF$M0_5-SpyuTy+5@UEiC{cGvIxlm^IpPZV%Ez@VLgZnZcG z=gtur#n*@xVUZb6S?8lrO#nzr&%dV;csyq%_;0ob1+)=h|B7w^wge!-m|%CCd+`}= znI@`sMTkquM`<;TJAT(@^cO+95eiul%18#WQDz9hR7N_4b8rgzcn$nMyQToEasyTB zQbk|<0Z`bu#`)d)G#0@Fy1rqK<(s00AOq#^CC_KLr9$ z!T!_=P*AmT*Fi@(KZvPQ@Fc@bi0%-6#*8kB%X9z?5(Fon6XpcbNQA%KqHz__ePM#M zdd7(WlYtcQi4H?jke@%Q(@+96R$7WRZwp}{#Svg=0*cLKTU+C^{4Bt335d|EYik~e z-{ihHZF?+Q5NBT<5P|bX!L~$b$z~-kG$*uKn6MKjL8t?uD%gBO-tIv%NY6a1U#F5V z1fUd58IT41$pcH{B^cotu%ySWOAM3a!=%D}WB)0b^X+rv7Z?QpK|CZYC`>a+Hf(}9MuHb}J6vd)o-Keg=8{Iy0Tg#<8Qb^IY{Q)zB94-UxSR$1)$epF#_a_ z$jHR9loFs5Vh+zd>JqN^3yHu2rq@kS;|_N}B&1cm2f{S#=qBrx z=c?EU2Y{J1g>lXQEAkh#`zDkHa~p&`N4Tu`Dgj`8iCGaWbPz210L?F8HL7;>{K-vE z9RhvPesoe?V3j(0z-L6fCR?mTX@E@9#DrWR1J(l(YVpT{2Z4Nqqt6!;j&p#K&W`kG z9RX+oGZx5&5&dprvShbPmZXrqfz)pb(kSao6T=DT8E-5nuL~7GlqRl5CGBORI z{>@n0nE=N!Uh5)65duZ<-ojvkD{~Cg3_jl6=WPE3D0>hSi-oyRNu<162^Y7&SST50 zPj;rbXMj{p!e203Gu4GAe_qvFm7Y};$@QjM0=T{0c8PkfDS~2LF)#>n5KH}kdyVDa z{J(zR{<(jaZ~$}w{`u!We`*<={hhsgU-$1HN*>4qe<=SQ+{zo|gfj*1VSUuDpa{Xm zSc2%h=W5Bkw+{>;&oXyEYegOl*;aEgPisaJr4h5DoVIz_NSxyMj zzA*C{WyWK3!3S~DA|tJ_yuUg4BRav9|76k-KnjE;vWNf)r13-toNFtb6}xwI096E? zm<8ov?LdNPoD$GLkcp2#4bFg1iXy2Hh*i81G9XITvVO7?#BHV^A7qxFSrE61JO>Z2 z62ncxm)somc&rD+DaQ7wu0Nt2VO7|Oj54w*1@s{Z5$MhK2pJ;b%0=+pkDn8XfouW1 z{Qb8+sk*5nc4y)CFnOYnio(_4@}xqg`MH+JUKE#qW|^11`BLJ z!jSLPS;zSBe4-&xLAP?y27@BVgKPd4q@N7?8?JBpVSNIq%$hq-d}tDhy83{4gmV?W z#w&m}2r>hy@?T0WLUrJP@aBxP8{88Clgi?Q;G}I6ymzmvf7T|9Nm>vH;RCN=`+SoG zoe5b{zW_JUS0_@tZXDufh=8&T&ee5W>zpG5#aqCM36Y6e|GXcBAjkqL68YL(KXahD0U! zC&+kM^_%|y(0+5%%Z_|T`jwG$(FVa`3f4HkGG`uWY*z(BfL z_1ulS{256Sa%(ZV{W#= zxs8!n|H^?-rT>vFm~ryLc(0p!Ibfx$gm@4=*pU`fQf5=g2osrLS_TNHk9rI+rNY4t zei}hiBq2R9`&o?u=mg;Zc>eD{c>eoOTn@-rIRUY04aBD<1;;|dgnv*P7}0{FgccRf z4C}VDPGC?oJ}QM3VR-FVM}XK))C7jH@{JhCWK;qR`y7=ZR{3VK3okSI!~lri zrqBvtMNJ7ZY<$*;)G#6HopQsX4QmL3GgsOPlL`|?|MQd z84z4TjJB!1zOf{Lp#Y?fu+sRq1wR3`6pU~URB{k2I2y)aR!W1pjdB|G018qpfU(C8 z?G~(v9V;K$=VBPN(gI+kgr;Hcrgfln1mI7xTUG(w1QW~`NJ9DfP5b6A|FYVa8UwWW z7oT(ApSPcT{{MgDay7nc0j!|I-T0sSUeSsPyOaM;$lvN>G`%ibBP}|^D?__1H3>CB zS3MlJQw;T|6zCgqO;m@&yPK)BC1x}j5|B%jtJT+yysdrSG4c(u}m2%hD%-)fyxbv<2|DWb5FS z1UMI!b=c3e1YpGqQxyh&PK|NyX8m^w0U!(xegH?8#nNbC>FpbgC0RA4xj`l0c9U4B znot~69VtvK^8YIZy&~=-I1wjAJmD6dfJ#^&Paal}Ih*q!8Py4gBA=fS1NM1Vz64F{ zf;|PwH{v+Qpl*Z8nz(Qt6+0$O5n2Nx0CJavQTEx%^I3zcyHKJQV|^ybjhM{L2`wGM z^tZ&@zy#D8LbdYzfGja9ZW?iNY>~zCmdel1=mda` z0oLvR%_Wq-;x=4u4a5S~V2QznmViSq|CkD}JB_56_v8xEp^f0)UEG$YIUwyjuu2aN zgdq26FkmDEOZz|_6etv2um|tYf;fb}Wea%`_m9B-v{#=KooQ{XIz!$?!vM&K?m~;- zJMx1jH?7j2eGGD`6GHYOj*-1ds%$b+7((>PQo>XG6P!32V*c-a+>yBxGA@dccT^XPQSo_-H(U|t4Nq#FOn@wzisw~_Vu4{lL1Eb`Ljoa<+e_A zgrxL-Z>jnalDB!Wz@)Ev8d#q=2vz0gbA1pVpR`qVS^StbL5kr^5+Dz%FXVxbfDoYR>^8l zmc)px68Un##XyNw*&uHH1Bi4KCR17G$1&9>3C$zF?^BeJB0JXaE9L+i{FhjU~ z2O$T~dk?*h+ak|nLG@_~&Rftg$OE3k=q&WB+GJw7ET$RpsMX&6OZSgxZF;mZ2;A{^6fYNWELtx#?xZP+@`tU?gAXXz@NkZ1V z^G*s$kMJ&Vm2;f&&(k1y!z=0F{E&ROYuX=Ul?6UkDvt3IyMK0#04xG({LA*c{?o5| z0RQR|3D`|Itor=w-)!c4CwrC_%`E;j{jT|s5dXPLvyZF|sJe>@3-V}ikvG1ew!XbO z_i?Z2nakce!k2wYur)Cu@PcySAS`;O_9jhokRw1wxiU1~qfcmsgG_()sUkg&qxmX;aor_1Z=;7UJy_Tw6< z_nqJ8#DOov=aWeIt_-q{B%_SlrKDqqKv-PItifI**1mKaz_ipbGslYP%zqW=+jyoC zGH1T0qPH2aQ1TE-fQ1;XGj3vlmGdOky&0DhHUZ5CO2j&h5fTy|j@z+*{bV+@5F5(4 zp)dRoNOIstyb5gXa(J-xS7JaE7FQnjA@~bSut^9TO%hmxp$)iSnUIF#gac19@4c5+ zfJ}{h5D%2{6{AaSmCyEc2EW7X)&JKe!nGv<9tl9$f4n`WRaMX6J6~4_TSK7D{~yrs z4YflKhWdPi)>9%lBAEq=U2+lp&q|+pIap2Bn9XYt{z6Won7L0RiU^__O)}OhWZfnk ztojfz1i(y;5GEwxW?cCc73o;;XZMz~b7#6I@1#vgbp=pTI4bMjg0$)w&A@@FAH6+5 zt&oWZKr{a#N!kQrAX9ThuY?F9zGOx<|C=UKENo7g9eh-hXd~4|C%GCx|5;MFBoZ<} zGqyDeZ0&C$DLf%amE;2jZP0iIH~^O3<5^kmaQ#b(LjV+?#doOGF&T@=^ZLtxZq@{Y z3Zs9D(MBVmQ7|?_V9=&0{sjiX@X2~APeeP;$&VD!!-UEo1pQ#v$1u-d9L%PGketJo zW}qC1$;dFN=ieOvcHRKNl8L{$P5=}Mus&!%{ru9b=ibKZN|4vcMT?V6rZCot_< z8Cq57Gg0});8iU}I*cgYrNtfFo>+0?4o|CsLOH0d$hpj2F!#NOFA^kR*=Jrr6W9?g zsmxtiIcLux2_d$&1?MMVJabjmmo^I1K0uxb(Wq#s2$=8Cz#i*ox)DeradwsjAVDRp zSIW()Z&BrAHHjR8<}4`O{eoG*CPGdV6GG7$Td4B|;Vv+p<#E@<52AB`oQWAc5y;Ma zI0{%ufoVn`k0n>ACD>+ufVhrmv7E(~N(9DhUu?^W)~e_1=Eb@6QO3uo9+n zrQWOe=Fm77&_1>y1|i0c4N)#$V)jo19B%&`|90CiHqopisC;FT`IytFEX z1v*3IZiH-w`~XzgoIL=!>pW~G$HjqUN08T@LJI+5Y9v-RjR7Q&?HvB_y%2zzu+S7n zJh=VF8*z;!7%>lGz60)L+|Y!70*+?FG3I+5brNRchP3fO5zJfid1YN-=OSw0FZ+*c z+$0s)Wb(MCo%`A>JyEG^tp{9lolDN<1wW7SO%qcLg5j_1Z<#JZc;zF|;6#!XDm$E^ zZZg`2qi->ZGhyC;Oad@DAn-bK&0}tef!RDH7!;KtEshqr1~&j|3BwztOp_dh>1XE# z&}v%gv0B^W>6R5dN5SUR0cqU}wz0=0W z%!=oMFIoj{{4A^OLG@WG_Jqm|v95XEMuz-}65lcJZ>rCY?>_3&T{?5hEFC-bZXcMT{#Qx-Y_Y9Sv zV^XQw$dxw}_{s3U2dN&dP7L6Jiwp69alumkm>8v+d=kJ&Kwv^a$Nh&)de$pvu6PZB z0A7GI4~;PCyJ8~IpsaM#3s+neHqDU7eNkj6Bc_M|4K!C$lMw9I1h#*+@>RVQ$Dj%@ zeUMfXsyY^8A|N!G12JcPALFZp@dVXE69#S7=Z8^T)z^>I@@jqzK{Pm;k38$$3$1^N z3K*XRP-BBQ9(WSagNzG8%K*pTW|Ts)j-HT!kJWOJ;Fx=z=ra&vJh71oMV~oHMSW)7 zgxg?D8vu3rvs`cxd*I`D#4eX|AxN{7B^-_Rt?Y$|NgZy_ZIwe0^1Y?p*2D)d*_OOl%~Rn zzi0@9YN;@xgF)WCR*HqOKPt0)LSSkM!D7_-obyNN-{4|Dk=h`+CD{yq@?*_Z{uU5_ z))QDM6SD1S5qBG!4kGOfgU(h`WgZS_GhIP5FO^D%BVAzb5>kUGN4L z_Q6a-H=t@aQi*5sS*|SS6xE*#Q8m!mF9JA(jMEHy7?O~Mg}@}ph|v%dGaVs(0!_aJ zLADP;#s@>RHBkRxHA)z8>=b4mR+Pf17QhoMRFLr(v5_aTzAIe;L_1aWe|-LRh5xqM zYs@wsWjrAO8#q4xHGA@(wEy|}pFd3B>x8>%;_^F)h-ek zunQB~p~2pW7D2Ao!FraO2Z3--fJ&f=5x>S|Ang(4B9sXONHGgCFu+-m1o$k}t9@4q>vFx{lZP%5Tf8@ob!_3I>X--AHX`gP zg7?2}-~0uVW%MbHYb}6#8{mg8YXGd^db|9c>*^nqfqAb^4(GpvF!~jM>YtJlAQ}K2 z`uCRL$TA!5)da=SS{3$<%Gp9yHH;@pd9l6WHi9$#2WcG*=a#%|3PM@hHlCBZUqko% z(ZhaOx0cZ+X z{U8grY9GeX31H z#}AdMr~Z9AEVKuHd-3?br$}Z(4p6jlYhUc)<2*I1pefW#f^n(VlwQ2)b^=Q5wxoi6 zCXdkt&+AwpU+E5Hxo3(vAe(+xOLe8EU!K)8<8LssIEAVq3;1V{AIUM1K>iNsz_CA= z2;fXV)h2*-X=1Vv&=w)rg{c6x3LcZhsFNi~lH}I6&;m%$P`l@YBmw#RCmSKVSlO#J z=LlMJt}D_Y#6CJ2haLhs_+_+!m;q4|*h0>iRo#vixpV$NSzur+&;(g%qII)sDX8fL z_1Z{8I!xM7&NrX8TRopB##9nG=l%M~Q2ty93oQtk)OnC-4;WJ;6pn($MCiN@|Gf_6 zP7nawSLN}q+qe8D?3bQj`;(Vh{=4=-EIz-g_@~(4gaG6P>Xi#XA9O;b2PU;`|V(h?#BDdO{2g%le<;yNr`ko;nTV{XzXKa10h60 z6q(O}w5D_tj6v~)d{#t)f=?(EeSUvyj|?E?;pBjK!=Id_4HzlT$p9j@Gqny_f>FqP z1lA9M@?dg;vc@d|BQ2%U5{WJlM2UbC(2D$wvV>J|0n8SofeMyKc;A5VhwT6<_iMFs zos*CTS^wPl?0rEhkSG@b`#V7KlW-5BX0+H|$SQ{lAz*|#YvV0}Y1!u-&XerlVJ zh+ndA{_o$~2Y)sNu=|;>nZ=)68UyW;2{p$bCWU=g6~7Uql>v+XNT}B>Rr|+?pp(E} zs_5DGg)#ow7*&Kg?&t}&8}#=L5C`XwDvky^>DNc}`xfu*#LKOcp;CKMj~>I4BW6PCeEOvd=wSy*?z zYu*CU1200}%#zqKu;+8Z^x;#Lt4wK{@*esISSRDOBmiLjbDv;8bJ7$-ezS-Ug>eHQ zGdlKXEO8iJwUBbf1o5y`;y3{Sq_5_0pjtCzX4-_Taj?KF?18gK!THckg1V6Fr~0lR z_wjP|-w}XaE8t@S@E^&_f3=O(XdqVh#w+{woy034+m&KPo^=2sl35Hv#vHgS1Co{j zL1!3;*$4DN%il=sGhn9YR)yF1ANLB%y$&SeSrq~bGRhZV8kEsYC-?baP%sgvDZ2b% zE)&*O>}adQ?mcr8-UUe9&et*rVGOV)yaiA%Kuu_5bfD!lgr~%AZKlnSzbkD3Ldcv@ zsh7vyd!P=1j-YxUH4h?u07l+q+=!zsaVAqy$*g#C3c?YB7#OGvkSp+{+u!E^XJ_MD6KFsoc7526nM z!u!M5#y>3JOWWd1K(r*PM8+RooIBcLF)<-Qo_E#Dm+FA%oJvr`=I*&_3AC{}(Jx<- zma)PMAr92G3A6u77PzdPFrJ1!1FdZkoqJ%S7mw2kjpd6T2Uh#wlk~y!Lq3ft`hhqQ zjtHXp5h5HrG{8(C18-w`o;(8(;6+Lr_&AQFtbr7)Z6Vy%Sp_y6Ge z&(EwA0EZU9yv6}|{q+yolYh;A{Q32N?_rYO3fA8t0Jdl&SVv=!c=RD9*tc9wBj3XK z_6(tZ0@+5Ylq|HvpF0Fm>xyWtQjZ?^v7gTr|5O_Q1G*U(_(DQho>VvjNxtkEeaNu8 z6I4Q$!;matl|BI0KFM@|2KlOe!4&*Z4tg3DH9|XKmR!i>19M*R0dUv83<$GN(w~=) zy;pnbCKDX|n$PH3{cRcm*0~UYKQ=Hwl>iS>@mog*x?>AxJL{q zLOJvK8ew3hxLghADFWFxFza@-BTW&6uq0-qlexb{s)SszW>OqV^M**8jPNVi2xita zz##g1lR~QmSFBuxV+#d|p{)h*v^H8&NC8$?IO^D}n+GB7I7tXlf)oif{@>X*f9~lq z1mKDkaDDA-EdjV93%vS!CAzfY2H2Wl*kE@sV>;}B#|l<{TYQ@LNOtujN_MDQpACr^ z)X((f2Ug%pCk*HOr8=MW!~#e58tg0l8v==agg;cSk6_)iHXD)(QPjs^ zY_qikW}mgs;y_L~H@R|e?s(Q69}Vuo>76@JjHsac_r#6DDUsGT5W++b2<~u#S}frE za*r{z1=?baml3RjH?Hs(4E+t@$%K*zHVE<|)bB*qh5;6x|Cp*E69-amfwX;)0}Ffx z6Bd;~9B2R%<$}5Xk)C{m9kD9M?;`AY0|Fv00@SULAO)>5RXzo*O%VbjhiQfk1r}qV zbVoNAvm>c8qgnusivX}avS5u#0t=Mn{LIa`5z%vO-u|#DfJX#*ru`4k|NYMMd;j>Q z`F{rQUmg8>H0Wo-Q2O`Tw-2I=FRJ~4^1rhxu2~{5A^YA}9T7ksS_daMjBAU$Xb1T_ z1V^PKkLRH+&NvYRNrV&IVI#bQApK<|`!N*8vAt(~jIK>ODC4P&Rz~;aV(3jfUmLDTJ$Bs{pi(?4M6^ z-!VQw*nKQ;TiKKGwYVTDj6^{r{df%QMyk?0PB^^_3*`jiwX!8igVOt-iiBpo+HPow z_elc`w#dSlW<&upzGhUDpIx68ObIm1Dh3*7ViO=U^f$ViTrdYfAo9hiCm5wpTLp;6 zNc=1E#Kirc{?7A%eqtTGM+>IcbpVdH{G0ZV{J(uo0{$%u;n{_Xeb+kJB?k*h;Nu?s zkP`y7z>_36`v>G1{#@zjdvK6_EYmWX9LM+r#z|keDDE8mie+vP`PT*HOO0pX+0DF$ zTwJ5u-!Z~igpyK{Fq|9Q9Q%Ke#PY~lQODcp@%!SngV3iG?rOZe_eBoqDE*6C0Epr=(ztO#p!@oqWBa#pn}To`*k`af3R3+W z84ENk+8oXVfE=>$VA8=5fGMZI2!W3n--M8;EtP5>kIU6*So4W*-*emm#IiR-%SWQ1 z1b>1M2PLCjz=b$Zf3n%VP6;rfCKTHVV(h~-1cFq@LB{t?u=ZmBtcs`sCSBNH1OcMV zDUhBAD!Cv$jn*4Q({6yw-G9Qq@xT4fbp&7~1-#7y?5~~)0IRO%h7@RT@q7T|I0!+6z?-;WBIOQksP`_X3Uy{f<+)$w2?+^C60t6GJe@bG?(8{{uW z`CM@ptg06V^sjRfFp)?Iu7DXC7!CwXtJ(_a^p~ei!fT7c_cWxMh%z1pCQigx1If9V z3>!ldX7aB%(6}JH72)Q{$$Ty(X%j?UKC=)EVv2lC`3jlpYCB%r1vomGgMRB@N7o8C zlb6xqe+)v5pD>vLjghfMTcaqgQih61Iy0t3$Wi2nRq#%Z4OImp`+VK@?AittCGy^* zW(tR`mrvNzjXyxnfvc8+5*|9Y^H(2(|3f4I*V+Its(`=y{O6BI8(*aWxJnM#=Psy+ zwZ-A^0)U=wo>69_TeK32-d149fmxR~&BVC!z8oBY2jGKPsX|aTtB!{eeRMA;u1aYT zPx&O11O+)^vatlkVybqfVzu7eR}j5NZP{h`noSa1F;#Lv0$8se5MdB2$B`xqa9ZIUyMumrtC~mE=%yMCK>TY$JcCz`1^r=Felqo; z&VAFm;vnmfCa!>NJ&E4GtsQu*gKmW`n(%%(Z z;FSPS*jimM^$j!BdCTK;0b2b4mt*fZPhhlM3#$5XLi{AR(95lL%ay&=>h2^zVXzN) z-MQ!cShctW)ZcEiTn!V2&*r9QpxPJQx4w+R9gkDMC>mimW>d5#OS?!MIL=SB0gxy- zYh~Yb1PEf0w{fibhtpyX;F$<&MwtK6D}3oy0Oy!#1mt^sGD0kjTC(Rr4lM-_%&v%g zD{^6wqybO_7rc06QH&Z~ z&fTN+r;$qC#OyzCBigYN6Lo~nvmG&V1j2-f6GEDk<5n$sFaxI$f;#6#CTjrXzWai` zuNGK$A&RsT8}k0$Wx8W)NBlq9H~z+N?c;x+n0S~2kbZvj`Tc)p8@zW_yy6EeM*ZY= z3`l%d#{wG+pBVmTgi#DY3(nmeQRYL0Cy)9u#lZrZ!Xh4|LdtM;F(9&j&*JzUQ$tl4 zFjKiKv;V|bKG5iez{5@gnFp`F*|_AHEGZp-2U z^yrRq1)iNoEqY&m@dOa=%X@DCe|nPZ#r-_zuZd-D0D9oQPHNl)!=DLGiQc=DZDDJx z+}1$`T8R1`69rmvG>!#)TJ^L){@k2vemJ=JQSJqi{KbG@y2P85Z7r+5}M>GKXw~j$_!6^vB`bW}U4yGCHpnd?OR+%12*hh`} zv5FnE^uyPEdQ=N*0T6rT4w?hAw{$Y||CG}uJ;A_+?Gw?_chW1IfIh&28&Mg`4LPty zwM(F56A5UBpX;Lfo##J4_E-XN|M{?V!dkTf{vG>{|H$(fhyB^hWIlBsuCxg*m<74l zS$HSj@kcMxqr1|T;FR*P7JPAmowap^L3dzf9@T^Wv>%RM#mOj>5DTU&;N;pzXjz5x zQHaW8YJ(ZVs=CeJi~FesA%;9aHQPo6gL4Wrlg+3ypAr3rQ2Y~L2>S@11g7opOu0|F z0`jnQS3?Yq{9x;L2=gBkJP_oGHT6O=T@u7mun0zlX$=XOP;uhO$&w1jCn)HVEf9cG zAx95POtZg@{h3jzVKq946@D0(9j32EZy~f+0_n>$FM|w^&-C8Ol9idrfhedVpZ%O%yGLQ?Wh1~Vi=8Am1<+JOQ)2rDZzptY}{%Jn4C?Lyx^>-Fb0fkDOB#B4~E%g}Yq z+T$KXeGd(pT4ye6;g4j`Yqgbu@OUOa;{9Dl0F4;@7`f&UUnaor!)t2<6#@%+)cR;5 zb30t>71(0r5BLG4-IIzDg(l@O+93CJzBZH#5|CiSbIuY1mRKC|!|D12L>_d#DLDb~D7odNS`u?qo5Cpy^Y~;PgfW$zb&|`KG z%4k*Ak31f@9TI@|aR6T4|3U=(&F8=W$%kP3s~mt`5+I0K4$s-41u&xP|DP4+tTXG& zk*`^M**DX0ZRy36d*g({J&6xN&lg$!mMZkpZ#PZ6a^+tEVl9lomlx=qR3PaLU(e*m zGS){bbWis-Lj1WZeSO>qSNqFuewHEqs1ASmn2Qo1^!FW5hg_|XgxI=s-Taw30RqNX z$jBt>e#5yiQAPxYt>v7}7QPU+(A-!1Vtrh&Bwot)7rrZ^2h%p0APB}S*(PIxv=zXJ zZ_HH3LH?5aLDU0iv-#FJ6UF8? zv-#DrSZDADD)t3|>eM)$6Jj1qFfzLfM_#-pOXog5kzhU(B68~$y~ET4^B8cpD8l}? z_RV=Pzxn*BL=zwiHz{gvl`{y8~KFgF!FvB-T-0^vY78>EWPf;MOM z1cb0$xgpGHOjO5+1a%S$!hJ9Tqk5RM8V+P*I}n_JNJqN|&RN-(di=wPgm)~)#PIw% z5W#|Q=wMm^gG!#b5R`^9v2gfh3AWi<{He=oqpIVK+Cphl0(c%n$l8vCgy3V9Mg$#g zhf`93Gym*ynM5*pf__9hXGCWK69p&S7G{%Ou3JE2l3Xf}P;<5d;CY|$j5KLruY8k; z&Or%bAardk&>*l!pUp3hf{+-X-@K((_Av(7jDl|&=dAfme!+KLotg2m1RD<<~ejX>o_D(!sIlYK5F1SH)mI_20Lj^}k?WfBxq`FWUeZ+|_rW@eS*-*@8O| zP`yUicF%$KMnH(n%Ffci=fcFuR4!nDLQF%bJBx<3(XY^qtx)>*W>UTt?>8sw6&u?+ z12r(@70Hz)3~GWQ2R&aU5O|S*#h)v02W^FdoH&8J4aO49Gy&5p!Hq%undg8U14RCq z^snPK#O@UT15mMSVoAPW@e`gv69Hn6Uhwf&M2hk>N#mp7h-#iBTWN~0c7rNn=0+_r zw*pWSSi#^S_)Ihbjcyy<9_ZXI2x9#!h z`^HcH?E4acEClp`9q=#FDI9N|Fvxf= zGjS(^wH0lV5T)IiggfKbM+;jkfVDNP>jC8P!q(Gg?0wM^$kjaaP|4(9^b5eY01f^) z#=!dBNN|%Zeye6V8Jd{c@CRD}(dB$5G}5?LuUXgUM+G@d@H4dSLXERs=CCT&>-;&( z)i=WVI2t$5ks15^yA(Fn4x(Wjejw^j(<+38TAtzpCg^9K~M`+5k$v7?4={ufGHeW4ay2} zE8GMVCPhdz#Mi7==h9M>jP4b#1zs0epn?RpY^@wqv?Ji4Q4hXDy4+3sw5fAa!xr~nRf+F%7Kl5Kd|M{WIz5sI( z&~38~@>=-aH4JCi9Wwq&WLI-lH>1k|pNmkQA)ln8g$g7)2A0J+b3~MA5LES+Nmb6* zmK+e846oI~rYOHzABRJso<`6kqt4AFC_o5@oF4C#0xnQK;gA*Y8;BMW+1o#Mp`6oEhf^yLMXNi&lkAs|-h&8IFc(^PP#pD&GfdgAb zw;ys-PpegvqH#1y^m0dP6=K+z(Y-f<~-JDL9tBG>B*)@Sp>1A0nM)Yx(|Fu;(d zq~+<@S_9VhVn9ZczhD~vf5G1M(>V#BhXe5O_+H$I{@(Lv|CxuNeG{z#yYO!^R&=v! z`h%t?8SXt~e$T}yB@mtnTDhH&)$*{Hp7X?9(VKNAz1P>D$ox0ypQDv|fn7<_fPGWE zmx8#*8S)pb!u*afbk+%Q@C0B9!W6JO;|O>{US{ISObxA{Rqzv#?cms0SzE>eUC7?D z)&W-JpqFwR+8+5#L|i|9O_dKHXGq*~0+L-Kojm(1*F?94EqV0zit-{T0XPJgfCPDl z2(XIY2v=aWW;kfONPU1wdNAPx8qU>z3T6`*^61Kg!Ga)D6!h*C*a=}ZX*mPXfc=9$ zb8|oR+D<4YIk_s%B0NVBH6Z1IIFUICoP#6|Mn?0jSJn|7;%AV0kUl$gA@n=fq46t> zpbt+AvR?py0F8B{#d0P*MSi|5b%`Lc~^l{`6B zR!sg(*hYwLO#ubIaa>+retHv?I`7x8swF<(76(gS6-gdD;5!Qs#Na(qzpE{PZ^v*> z=R}7`Uw{8(#c#vYdC7^zOTf$DAF%E750InS>*aVzl@*h}FMsD``+ng6}(z zE#Nq<&f_2do@~EhJNfuu-apg1no`3%esA8Vv**sPLq>1YK3Y0=bJk-TWJ$Ifd8`woQw_a^}L&xa+n{&*YUB?0(Pp8tH1P~A%0=SuHi z5Ccp9&lAHxDyhl?#;>3f%`?kP5XjJ9U{)B>3HOA-@J&-a2($XAz^C?&xtcjch;9p-;b$SR%7Oh*WH@*xmL#fJPvUK9(1XbPDLvASAx& z9sQVuXgI(XaKMCAK3{YU5Hu`8v>}=XKmf@Ppm`}q#@asC=jyR-=gVoVo zW+qfT1$He)!3{FB07nFXjT$!tAZ7*>!oWxLcY}@gn2-l16(|zwEa~)5sOAe3)Wp;R zu_wj~I2e3xki0}C8Zy#ees2->N!2n+8}lAGshl$u%BBj8_z4*76S)1cdE~4Qklh14 zt1J>f1&c*H1WiY*Ya4w0c?tY~-roJwwF^EQ0`Phm|DpZr^Jl--g85uj`T5Ti{9El8 z*$!4Y{+8(v>lI3FTSctDCs!<3Ipg~&&&N2rRgATwR)uA~NUvc9&$fwwN*89apZLDK zUs%1B7P8u>XuM1x)t}FEeDTq|1w@TiivK{vzK5?#p+*!JraAxow$NOj%OwfQ3 z?>}C5c-UUghVC^v*>Ur4Su)kSC18WPM&W- zGBfMdnkAcHoOvEFH9+l3g^HaUw{1nUAnd=QzW;gIo$wME@yk0Q%g6m3OhUq2iov)h zr#0anG8Q~s@C1Mh5TvUZ)=?Q3Jlxzb$C>!d1(<}C*QhCI_>%(iagI;o-st<5&;4{U z8N0F=2J@t>U4e7|tfn%-<3O@zkjmeX(*pN!xu<70Rch*#3+F!rvTbR#F*LyL1xcG$ z2&NQ>;y`Q~z>LqaM<*0EF&*b49!+5o34)C6j$}XFH&Myw{&Z2#0|TMg_dQvg7$`w} zSRaqWUi|1im-2oi(*xcYu;tTCdhX~xSbV1k(e1Z>eg1#;-UV3O_9_dz*0aB$1}iF^ zI@7Th+v1F+l%XgQ5uu0@(I~+Z!4{YVXe|f@WQZhGIhaF3%TWRqA_q$q5(UDsDE1f{3D7c$O$tbKdM=c1CVxMMQ_(=^`UOLiy>vUE9Ed5W|uTcn!iBRo#*O-a+mFso`IDmHDI;Y71 zkX``DMUd73V{)NNKxy1BJ#V0R46vhQMFFfb)*YIZ#t=Ib{iLowLuz6~te6o6^9hI+ z1cbFPRBxWFeYyYagM#TnVwdLqIZ5K_gUvc}$?jUEjo^W(sBD3)CQ9HcNS)M%#&@*| zxK?|TMV~(C$+B6HDSKHKWDtcv+Wo&6}(I*bI3tR8E&DVA0JWJEd2R2MZzH0paz5-?oY z1dHpHrGVA=Ba_$WH_AQ=*X<6 zq+=uLq1y1Kq{b%Bf0iMsWZ>f&&Q5m2$O~qApHcS_lWd9@6A|l_^swyvb2V;#vW4!% z?Pm86C+C8q>a{ul%?ybk5D7|7wu^J&MeJ+v=6~XkF8){bw;Qd_{-e3wlm`CN_5OS1 zNj`9ER>8+82@3nx!@_uGxguZ0BJZ9@0XLv(RM?(HauAUmtJS(Kc>y%4;ufom?IOm} zq+fsuCqQq`ZR;nyiiU~n=fj(^K0eOE26>QrU^~L)72>xJ`IxA1mhrwp0exxgy(vi= zJSN@q&w`U5w+R+jIaH@VwR~bgA41NT$#xL>3f|Z-qmldtwDpI&roE}w zISDN$KsyOQAtibZ@DLsVE|P;ZNz}F>8PXTRL7nb>aR+1sfu0*@-`z#~fGTiVIYsRO z{v4L2<-{Z^*bNHaq-jtX`Yb8T>Qjq`x*fv#P6z`j_rWysxKz2!a$E+Tm@#1xeSGC{ zQ2O~07@*ZF#RL)1DY3Zu_kXkg^UZkFH!GWXTSsp$&tqs5%lrL&?9$gTyj+2LfJml{Smhj%}Czf@qlXrq@!LB&etcz zIp2`~K>M$Z+4A2<9$}3{M78aOw4BzTFe{&01XkzOA;#j;nA?&oaz`@`M+8A%GAS^P z@J+~vkvL~dZU@YWf2PI{7b4zWZBu;xWmRmI%((FcKw1QmV8_o{1nKask{%1^p(EUh zc1e*l7DU9X^g_=))31{RJi#H5+j#kY--I{6&k#Nba0_7sOz0QRdaDysFvmro}gQG@>Y9kiIASqERENtV>8AjE-)s{f4a+zBI zRUM=94|qh-t(t(aKS3+vvt*=UJBcC(fvh*9r-2L8qV33!%=&Ov-ZM?0J7la%WG4v@ zd9X|73kUhT#5rS00OSSxERUi(1M01K6@FbK-z&5O~$uf07%x%Sg<+|K@gdh0L1 zFWK|)PuE|crlUy{469Ns5>xwu(!_ng-5BKMm|3ZH0X{Ii$Efqaj0b;AbAh)H0Y(7Vg7CI0P!9 zSLer4EQI69Cy0YCwlR|F()ipU$e#M$E_48*#y|(eVF(vOCI>2%0JKn!+!l$IOd>yCzV@Hu&EN4$cP0T) zWw;n?$9KQj5#_`IV+Fs>U)WT>#;gDFt4`fa!& zF)89>o-D6<+z;9g+eE^ZM1NwkDL!L656QK2uu+$C_CvJ-9>LAi6(0a(3WiFwEo&15 zDj;ovl@N`K(Lm+}us=L9@yzX+=n(y~uXwQuI$+oRSqs;_p+fuab?j2mdP=~G2}EhZ zEDf=il9~n7N+5gR81P&{<>(D~RBjqcd$E#dO?`zf8M*kSb#OF}h`@A&Gr11ZbJY7J zmDY*%FOw9Lv!RQmGNKg7BYvqJz{r8u)`1*>g>Gj^V&817AzS10cj+Rig1E8{hJg0t z>&Vc@O|%)p>&K+MkbLBw-29ei)||G-uB!PmBFN*6p|Vf2x#X6281DhIG=+glYKaKa zB~aB7)kVNQ17?C$=pxbZh9pRY!A`S3E&fJH6FWS%Y9;*9593Y$=PU1g0&u(g|M>tu z|1tryQ3>nM3vriEmX!sCJjb=oM9_OVmyW$pec@)X9m=550e?>XamJ6eg0B zTf!>!5P-+X@2RR_2n0gP^wQ(4`yX;O8|7wI-+2hx-UxlmfFqK>6x37#e;}1v|5>0) zmEq|jP!`#S0r3F5A_lSj;j~W={waqbn8yBSRz3zzn`~35a0P){xeKZJ{#gAksQM1W z``4@UTS{SjGrH|rl&iBUhSvnFb>m}WwIR4Q6C_~j7MP%N)h|OM70kW=W>jrvKMY|n z%)M*x`qC;%SVG1`@0VuqLlQ6sRVxd%7(%~%oL4l#1sdQ(a~+H-ezut`0#0Er-xbu4 zzE!~0oJ0w_WWqX}O|&&ySo8U}KOoy(B(9M)RDm94m!34d1>ECe#Lye--5AJa z!eN?&5Chg2dCotc$R&`E>T(Fy1~>%hCU+>JbAQaTKEui4F+z1L{~1@|G6M(YB9_n|{uI74Yz zaJ$Sa{O^q*m<`eXF=Y2o*R8g6T{;nBUGV`-RNy3TQ!0NUC#)0%o#(5&v_OrgN^u~!s-Crt`+4RIbgci>+m99TsBrqA-j)j} z%Z2to_g6aIeV&gW9Ax9dM5BX6&mSmkcUQ?mOh`sh+Z(j4jf6579q^oNxN9zk4DJnc zsZ1oyT>aw9S>`t+J|!@0xBqv&zvRxq|9NG|N`QB!1pxosc$a+%o_i?{bgRL>n*b1{ z5EFs83XQ?~(DyGvD>AHE!qrJws+lF0lD*JlQc2!bB@t)>ypSdDApbZ9hz7>*dZD+{ zLP(IaXh9^Rp%W>Nl8eiPfU4^)Nl=J@U$0k2$pJ&yJbnq((N12UqlbX9$c;opvk9lc z>geWzX8vCE@R@JXs+g)43U5ybq%{hrhy6B2GgaWI+61T$h*1Y%GtdG-{bbSuab2Wf z&Vio3q_HC?U5lnT0Wm4`2`7Lnc8K8bA{iJ~A^P@plqe)Ny$Ec~GuQ+!80L=<$&!_j zidtZ-zR#}%daC5(3U*E{k+AJjT@aa5P>_lsHO!f&B>6$~+69TvUGxqW0Z_s9BZx1# zAeRVEMo9unLi0Sl>094*XA*!+mbuZo@!j?;0N=L$`|S1phtc%B+dx0_3J_2s1aZi$ zRsh?ijFFFd=u0mnS2o*JfHh-))Z6w3eJTv|&Lt1p+0d_w1oNIkGNV?sL0+)b-D~f? zI3JQG!YpAaqiF(NQSF-&{8hy&l8#vYtNH$z6bv$jI$YZararqp9?pX^=3qdN?J@6* z34jXmG&*0LrVf}V5WNXv(6x{}EU1o_G8wd<;K$>83(cYOzAk22SBcpfNEE0M_v+-D z#|TLSBLg%Fm|%S9R)UeLV3Pjs&;W0$EEdmEh(Lr@V3h;l-1}bY9RK#3CfJNvfx8%` z3*&83nJ@;>WDK;RC{wxR3Y^ClH0a9#uBF_cRReI zBb`jKx`U)#wS#gYd-d7$H)@9(KT;`zYS0;wUezClEY)v=RwHL zvUD?_D>11V6UkoC@iDK*jU|Nn{EV{IEIT|#y&lG>(z#NSIh+TZ{Bd-L9r=3~$>wk_ zxQpZK^;}32#4JC|$D@-U4pPlZF4)ES7Dwa9>>BebZOtfO4K8v0B4XS?mi|pX_u=uu zft;&wmJhaS?vvR2Bs1*7gWgF#n7!+r-fs)QLS{H76iK#sx&Ew!l<^h_>GL4A;qY^N zAB4x3uw9LeuMEt{s)QEyv03vTLLRwD2cwsTPR~`3*>mxxZ+_REO91lc-oV&_;%_es z@U83p0tI-V5mTuihb?D{oqWebqgA9BAr1>6YPtx7s2}GQ$U^J2E+Oh^jud6oi#H>lh4buOt?~tNg$2(qi1iHIi2@ zNr!x>wdL?0aa%X910xw=Tvzt;IErbKq(RZm|Bw87-ah6D6lcK>F^#Q&Ax;+VK^BI* z=(Q)Zp9!I%xWNaOK8Uzyq@=fyx|9i&A@r?_xX%(){TQAP&BoyrykTo-%qp$ zQsp1{42r=IA_DHg`)YNy>wR5<5JSAy_pJ*D0L`dik}3YhJ6HV!S_*gvLcOi=|4Z;r z``ec&z~7e>07@5NkD%zX8&CyzTm=8f%tnCwY4LGr_I?yVQE(*~w#PL&QTflu5kn{r zfnPgN^OTPgY)LRUS?tOlJ?bbJEbGd1MQLirf8-!F1TP@vZiAHd2KCwnvn?eZR$F}D zX!*zBu1s^{C|?vPO47N|`#Qn%B>q!^Cu{PXCITjrAcm@i!Xo2|R+Q zM19l+>^7N{@(i=bgv3OWqFnQos~`8?K}kc=2B%~qj{f-|@*K}QDtNx(3YciEa3W&C zd)uk;DfRoiz^-J0RkKwzSG^-EI<*GQ4lvX6@TM2N^UlTpI@xgJPL2RJD8RR@_b zJDYIfOlo_gM{lBHbkvq#;y-vL))jf{SlwB+r1h8xG$e-7vv0@`N*~`)pOer6t3k=) zNTpRG^+IcW8pHEkeMf|%_(>r1S4yBkPN>*M*x6>;^ZORzomo;03ivd&;%JV26BuX7 z66-;5(jCvC_*hA-!*%JRHOS{mOdRAWV}mg8`<#V~4|q{{g|3Nz4B#_$J-|5b)BRC%f!X!VtG5jU;xH2td^z2w5!8 z45x_#f`HSAkV*=;;y>#X3!uI~UwL-X`#<}ZI~#nhh`ZeAJ+JRj{Qok%)1HmLv;O*R zQ%HYro7C$Vwu|atq;BS665s&K!t0_L^EI|1Wms5N3m`KRqtj=I1T6;pL7L>&&wwlQ z$pb+uE!V21=wbY<%&L@?3s`IjLm; zpF-x>JcAD2w>&O}S-T)@zu_HsJOQ9zZ(stI-ND;1owL0!?-)#Fyt z0vFYZc|!pq^3^ux1v30u|90-V9|>zJ1##+hV}LW3KU^;X-i%p6_3up*%VjlfJgs0g zF^Iu`cy;kha<&j+Ov;MZs{m39{5Z8zI9V6Sfm0$7TM@;An5F)?6+sZpg=Ti&Sq_&>bbJUKUwSWo4;KbJGCAP>7&{GG?e+4&F89DpsSS45 zNlC%qxc=?i@tE9L3iz=N{5L=5IcxF#g_mT&?`f-gU=D#bj~fxatU-#|f|nPoDBlXM zzIhA@q$!~0kRhaexvRuxLq=5&`W65ht@Q~~UO5uPQ(7RtFZHFR3~!^B$LgIt0$a$0 z4Kk#32we2_xnhq_e1=@L8|k=gQ(4HSN9l8{(YQxWs<`*BGy+o4vfgf*xdy78z!JgH z?Ql_oMX3CcX!w(O1KwK<8=b2K{fr}dsO>o?#EgsKWCcv99%z!JB;?*Wa*i7C6Y}CU zSxNh92G6S`fP=cU6=oUXMiop=O3A)Cnl=Hq8=_1vL2vQBw}9J>zqwNKJImuDag1+k zm&B1eLnX?As(T_V8d(#UgaBPx)cy7ch#VN4b5@^e?%MX1y-uU-k`gm0T*Fr##0}62USzox` zAC`f?GX`pU|6t<}A(v}ZeVc&S7t^kP$l8s*4KR8?#Per(^#WkYVB_ty z>d8>ZlQcPzT2q8Xa7#eh3(J;>7^!3gWW>pZX5@{hq&s*&1$EIfm9O_6u{!+i99ak` z5$>#3?H*kpL2Ws{9*MJ490iqdP(&X>`;a{WRI;Gg(W`?j*H@)y&+URGFA_2Ulay{5 z@o0Pl;`6-;`Ui85bd-fsUxI8HiJ+c8L*Yd-cnK;@%QZuIyXej-d2sPru@5;&f8o3d zh!B;(=fs0yQvS?Bda<4ubMy9vk6pm+yZ|1j1i+tkn)`jOikH2v*(3RsKFJ`E4r+MGMSKby%l347XnoO8xhZoaKa)m1` z2d*4Zb^5{kYL*-m632m9=|6OW_+05H;vWU~*yrDjjYQ9$d*~5fUxeO&_Z}$`5xNr` zu;V!A5VpSsjrBf(vhEUd!3kk7)HQNosK^t%Iz>iZ4JEFlOo2d2cv9_TgC*h?B=Nn5 zb0oPbu7khvB#|-;+J1__AQ6F>F^Ybo)BB8g(NW)@1HFsKAbS3onFmqX9|9RWJ^-S>H_yW%UZiFz)O z7I&f8XH{SfUbRAuAzxHhJ5Sm~sbns24#Ya6N>bXi9;#WdYKD?e0IHxts(;9m&%-Oj zJ|Yi?K!3Jma^XVbl4(58bZci36?G9pw9MmJFgHkz=UFlza>1Q&X@**u{7NKh<}#Sh ztsuoayng30c?Rt^*!AVNsT|sVHA2`S11@+CMQDh(eHR4~J-@`<7tne%w~2Paj*1|Z z_lgq23=z>tpwnm_RQ1pOrHCr6@0{pLd+xnS*7e{5%=#7G! z`!=pKvN2HVS9{I$O9el!Cjl6$WOo%|$ z9R#AV8MvHR6PxXOP=)S8RE>nb@Kll97yMlsq+tl;rJy*tJ{Fy!46m6^NK-Pd0afq~ znqjC_G$=?#qyQlEAqb-<&^nIl5bK0$u^|vT)19$z@*}SsuLe~78Ke2!(I7wDIYq`= zqd`Iv66RzunMu#u?Xj{kkXVt^xE>)Dv4Z0%hV+$%j4+Go6b=oGU4>+L zcm>GD5MBVtDu&>`I_cBP+{Cf!kQyfNan#~cGE)GeM< zacU-N11LG>$m-`cg^7gBVJKbz&Fc%VWp<9TbujY*R4>190{FWSWKzs~7-@h|vAcSF zM72L;ks&7|pJ=fNs`?Mlu|fUxK|S*!%#xTF;FV0(+7c5Pa@z)iAvYdD+%C194S}eP zCDOEjq5kdW?fFb8U+)OE6= zCJu~~P|s~2Ml}cYh-~Kpu}2m)1=ph)cWuu1q&wl6csD2!Np{W%&YqOuyEuo9Pz@eq zWHO@i24Jm#x<-*Suo?wS3lq(o@?+G-C7#v?W(V&d|D& zNfyaL(RN;9qYBx-J|+QIBZ5(mSE-XFgA!}{qR=U=sjMG6E`i(MJ{NEJ^51@ZfwhP zsqhmz6yA$XxROr4AR&(08NNnUAdsO^HUE{0ViW~ZBc&w{Ko=pgZMY>lRuc$7s0GHp zy7Cc-8F-Nr1|<)T6m9A4N5+36MO@Zanl?yEeO&#LvsOu1oZ1PVgSnHkpV4O~`QLc) zSP1+^+Z1hyy(Sq&fGAEGET{%EOo%DYD-8y=-M$ZR_+P&N@hkqL@50S1^l?)HoJ8)= zUjO~5_2=&_+TC{K_Az{qDiO@jQxGW72%Lm~Y^D>a^m;LA;tZR6Q}HR?e(36_#j>NA zi`CWvL`B9XLn+|X*NG!uCGfFvP&al5OuY-xK)TVj&QRI-oI~2_`Mb;z2BTe0Xe8?Wu_aC7;_SlIP(K(NG{VMIa8kZB^X; zM*c1-k?<_!m2rw|4&a>8$4|M~34H@NbJzg&$0b`bmH5&yp$zp#G(m+x*@M*5mWOdcDG%q7P2E=YuOybSsGK^6WXAPhBXNZTK`Cywi>u||wA$~VM>!)#x= z7;ohL$7HtAAWZSt(Q~-Jtw!d`@f!{4)HP|ejESn(qCQ8B0dD+;Fa|n?CKU3q?8X9z zAbUn<)<}Gv5)_;7&m`5uXDGKwNwghDtXmgBa9AbhUA_;I-^zHQ2?swSqB#d!BJ&-H zZ3lwh00po(wnt<;7Qiqf9x77YCiH_arpIG^Aktn#&NKB|9`B3aZ$VWcT&^X4ZJ}|) zrEYr}?PH=~+14+|8=n6QcSQmazQi=_3n74@uTcv-BOx`QTH>BGimEZsp zeK8HemVUQu4#KiD=OeWRpuizA_hGa^!Sb8X4>GKus#VLCeHqOYmH85;iH2o}pWBdW zEDjYBIH(oImd&8{#bSML-cef?M;S-SUY0m!&?ZR<(Gqss@!T4n{4Q1Q!$=Isb32V? zf}PJl?}egFa0CkEkGlU%p^9BAAhJ}QV62WGP}Vu&f|sOy#kHpt{E{(IbO=<07c%I9 zQuHH|oa*lzp$`RTV#IuZ0BoD~wRpt-20nBB_50=a4d^KVLU7z?Up;_X&0^J{_X*-FkF(zW2*J$jLeM$) zDO+F;-e{QG3&F8Qdh}C!SHXJTeCLfE=4f_b!pL5k|Ak*+l_&{h6Q0sSp~s{gaOlh( zg3?(?gchrWwDcXU(U`Yf86H$wgT;Dwd~(KYQ0m2UaCJ(cnIp%^b~?|3`Inq+(i4jP5Ve9{%5wxY80JqKh47~n1kKA1ez|Gr@$6E=oO#=QN ze!xBx-?{$!G%b}kK?S_e&A2LDpf74r-(BkIyO6jQJy%2Zxo?^NO@U=%F=dW{5&}vU zW7nq_|5rWSg|s?=>Z%9o<#W|vk{Hw9KZyjP>dL2lTU7R?FHgce5fM2PPJ2bXCL#hX z5F`wLI?Li&QqY;dE##e$IMwV;5ccWtoB+vf(A`~K-8RVs1d36dGwnL@J#2TZ!Xue| z88rb_`~Ye46JB?jlo9+s#5G^WY1x%?=rP7iNocP!$WBN=@d=Qq7n%K(zzqugtg$bu z-WZUXWZ_+q=EqzJ7KIZq{Vhxbl(UFdC#U4X86i;OL(v1^oE9%m79a$PfI5cANJZ== zFT?@htkRIQN%yYLH6^DCI&IghFm*uvUbNR>vTOAgTQk%Hq+!Hku8Q@R$iLe-{Z@Aw@QXN+xO^ zUNe;U!yrpwfjUGDeX~jcROcw574ypVH$w74Jc`A1k`c+23$=_kCO@@J(j*-QIm zIdO0PFM^(RNSjlvb8&7u?F(}FOnL#TP(y7CN)ALL+@l;lBbp%XuLqs8F4}fVD%b@q z?m~;2AMf}AU4d7=^OD(8v?djId%`-kkF%$E1R)b$n>L8-ze zl0$()^o_wx{Hym^8aK8w%Ei{CaS?&|>HXLu)ND5z$QhdB)o~0|g~JadcRvAztJ(P| z>f?h(_-F%T4?iXaMx%Ezz*s{UNtH5CbOjPoQlJ705fY))3oQr1qF8tJVB|dvB`y;x zYSrgw6~IO^pgQuQ8F@V>_(#ovuH_+66$pNX90#;2<7}e(cM9^6lK`>=?(DNwQegZ# z*F1puy5b(lt;ufu+J$&Ts|8?U$1#DzG;F9Y^g^_w1v6InPUJrJAt(gDki{3j0vA2{ zFnU*f3YGxR3im%ywV%|&TnCF*n;=4!XV)%r;8DjQ5OVWNynMZX!`&53kKq8^Wl4a4 z{aS4O4|vA<>(8wB-!V;C1oUje1ZEL@01HTuAEbQ+iLcDvQ(}8q3>K~mP?HSB2k5v6 zo6PXl*F1AQa~;2T5)~4lmU|7zYPtYQZ$t%>L8HwxgBaI9B-^=0x!Y**+cD-H)zTMD z0x(FiZxFg*f-~YBx_GsnXG?zWLdXtR;Vx7U)x3Tr9WInG9X_W^;1;V$@|K{XnVuky zhMG)B@I504xHkJaeC!#jhJm@zoguW!MUUP?J0TXn$>)Ozg2iTdq9E!#JuoT?;Ndz{ zlH8>g(oGh$h^BBID>?z73e<#5A++LO8VwX^!*(F9g56L~XrwHdcmUJ}FcuM_+2%wv z4840>vVlHtowBKG|^J#ef7md3w0mx_TU8e&e zlYsvnKfC_;QikQ^eaHOcu-G~){|)ilkrPPnT}oTvhOz7dj*UI!_?j^qBbh*;@_>@< zRc!3N&epQqq8cr7&xJt`@*KaDb!`l4Y{|=W^YOXyy`$}M+-5ne?qrXAUY`@^)Q8Xp zaMFZNv*sX!A!VLJ@5P2dgY10M-#yhB*1X2@#i{5U|f7I{5*PKnB2hv7Rt-^loRaUe{ zqk$?}uxbKSta=wz*FRxmliCs%Y=wCq9yyDSYjG-s&b%Ff7yqYKvflG3nfR0^Z;R> zix$a&Ty}n4p+_s;NV*+^I(rdjHeGc^!kxR2=oaw&aG~fYj<+c|yF+rwPB^wamPVf3 z1!5Cxj)?9qg42~ut57FVB1mS@2HTn*I4Tivp=le5E_vmeBr+jYz_Ha4=l5NVudNXr7;bn&(BxxYu2AH zz+HJ$Er2O0Y%qzIw2z_D# zP{#vc)1Iwg%=6M#`;CB}bDkw4V9_4a0FffK>) zN>P>P`k5SPneV|~^oy|k<~GRiCP}iKU>RM5ai6oBA2Z$+cZG#^6x!=#{KFms!^k8E zf?vk#wriKbw$0O=liYhZ(`|s5n5u-Uj0n;-?>w&Q4f$4$6AHZoZV$L9E)@HJYb|m@ z;+fCW`UQaD|0Xf;uD5U0cOL@gM}A*GNC$gOOl0EVBqT&VdlnmtX>VfwbHHa&p|cRk zIWZ7csL^L31#Z?7h@vfLRm3DhV5tHc?^s{^biD2h-gS4z|B4}Q#dv7$YEmWywE?|5*76^j#kueu1NZ;9$J_NSJEg+fT z2!lI8;TE4OVDXfY7(w|a7)UG+Os`iEJj)4hBUhbFaDnDSdtect)5sBT&xhCtSMPVT z_3)fE_`d;n^UdCf zC9~Ez2^t9xNm*e*epSfRz?HnVYS|$}?QyKG@Yh_AwiX0{%!Sl64OjTahIU6t1h))SycL&cZ+;QV>o&b>X|3!G0Jp-S# z{`%T=d!IOMVT`&MD8yjvvr~1%PJf-a2Q#FjZoHZ$=aJZjrcyj=2?*HVhn^QVP83Fs z2;7r*l{AS37g5B0Z{vc`s`y=F(U5sgL>ngcEj+hb%90_cm&SN2)qS7Ls)q($@$N2 zPHCTkq;pLmVIZNOMiN*BeNH%~U{KiSTf*TLmRCFl9|X2ZWZvA~HFn z1z62hUH*Jz{rO4wna_LIJ&^!}i*;!sr@J``_y_p8b$g##f<8|Hh?BWB6IXbR{2dXXxue}amWV;nTJxdk3|9TL3XIqkVuoAMFmZ%3Z;NvwUJv?81= zxjpYYQ}8_P4+TTV0LYMZe7>5xQ18tSkRIAD-h8hyFm;nd*8&jJ2FhWN&(~;23-8?- z(ZJM_e9qGqBTpd2_JL}fZQrMH)oiCT)Z>%uG_{nVA$g!e`D!1z&*A)}gi9`wWm_SR z<*3_`5{yyTEk|943Y*>)^v?`=2$;W_5-7n@Oa-J?0P6ckXxt}Ee~a(4=r%%n-|!q0 z?uN%q78HlTCCmsf!TnFQ1R&Z7#ewjd_?gfBxw}7d)_r!P_p-g~TLBm9fKOTPe{M( zu+SmM21B8RPTwe~lO|SBjg8phF>a%a)`Ak3D`;3ePA*UY%g6~&rNqCR)oQ)axBMD` zSWq<|ihE{!UeuBpwH;F=j$Jzs0$N2AvAyAS^2S^ngaOUo zP$CIH2N{Od&O3-&81Jw-O$@e*;{bNcb0js+B-!hlFW`-9ope`3Z6Bt#hm!0S$ttVy z4X>__@p*1~M2yw-+_lh7gu6{8KTS_w^|C>U&=&A$Qp6iV!l3a#S6cVb6DUz$x0pns zcX}+xaMTunH?0BHc8H^Z=1pf$v;<0W;W=^H#x@lV!E4Zu>qRFWocI7J|>1BgDIs~$gyG{__s1tT}Gy-%c6xA&m-K{^m3(cstF>^X69izj!0 zu~<}yoeIms%RGb2+yA$I#xwBKpK*^w&10p2@0BFrTk%%=EW#TPlpB7G~APHWl>IASPs*6StYi|5Vadf-fq{P!t#`2qq4Q zWvH6vb``p^5kV>0h@K4+;ewWi-YV)5oB*9TSWP07JRDGyUp4!0U7}9<1l;3q6*=kJ zN!kz;s1Gq^cy$&uQQfObU!C*rI>C}6LC)kqhjh>@*;5nXJTRJMU_B;2EOq=3MIXQE z^+UKCo|BOeyZ+J0VYyfr%I;`XuvGlY2Ow5Z3#FUB2LHeQR@_s!jHND8P46DM`M2SF z)@^=aX>4|mU&uXq9z8&xJ_fxp%1)27x-V zRJaZhV`v~{K2)7^sU(NGZ9|M#O~>?LVQTZ3l zff!!BBS6Kq(xeQB;(pe$?LvpSOZHQ1}g_@N)CEFjrtSJe;99 z5@~#LXf-~iFo?+rqvQZh&P{jC0Z@D$bX*}^?_n`G}6Wpy+JAj zN+Mkd&a(f%)E>U-@iUhpZ5KaxK~WEs1?f$U!{{CI7tMoIecV}^hB9f# z4ugxzof-PEZIL_$cnSj<>gUnV*tb9TYF`Xttqk%_NCr*NDvtcwi81fmLRY~l1^5j~ zz|sj{ArE66CI$}9el99#uD;n#P*vPqp^sqWEYDFVuL5zMM6)4Q^lXtvW%AVd5p+`l zE@pz+KcKS_V={}IZZD`FdQsZ*#>*QTj_Ve*G>2?;0--|<3LeE!y^G|cf&S%W!BQ6` z*z4EV{mpwXOja!K2KEFf?gbR!AL8xy-{P;Xzh0;WK5?4RbVeKuw?mBkHH*suEwZRR zAObp2N50hJP>iZX@b2LJ7zDcZhqFDaP`OLvT49Ce8EK=yT!@#fz9?EVP>?gJ3R-7( zY}IvdTj*nA65wqM0eV3WvTRE{2n93~Br*oq2+C)O+5|egB^5#=Ktb^CdHYS6z7?Be z1Og=UJBTv^lIUH;d8FWTw?3f3bpWAUYnDEKg z!zN6*Tqp~cHHh1l+zyfmfY;VQ47M(Oy~~bb;tln=yixu0WdiMkcD!k3Wx8M&;ej5F zgV^Bb9C=JH9=HhY2Y0U4!OyAB4@|}hFGl`80YCj|Z@;$_fQYg9y&L?ubL!jimi5Q~ z)N&d|Z4aoPzET0(dZyvD`q2;c{61R2KQN}^-2!5X-l#gY_Q43P1_iTAWxfXL zhiZGUp%JuFpfn#sZ4aSq{}H#r1uvkiW2s74BYzC%V=|hD3YYr?_TRE$I|6R+Ao(zg zSK0wf1Nkc>9`zdW*=D+RpxP%q4p?4`&&F#%^)0xkZ#)KgkBI?Il7MfrzjoOQ_~bSb zP}w$%=a?RYGEr3nq!@@=QAZd(sszUA0#_i7_ge5NaGS6%g>EOXmX=V>mJD@+Kn`7x zJF0DOHQ0sF(I5n6tNg;~V1%wq6-NX;H?8dG!nW9jjj?Q1Fy!9s&jMQuLm!5gH#4L# z#>ZX>ak~g5Hi{^%$Dyk`-P2s45u9a%|;X!is0nqJ=8_LC|@!`xVJ@ zNjw>K9@m0HO5radBoWwhElLE`iJ_u2=>0Xpw)G~~0lh{m@}FuEbR%)3GT1J#yff`M zVj$b)w(!1cA6$_EJ0v4j{eJaaBQv2<M-QQ}=vashZJMl$Bo`ql21umm@5`_G1{pl#Z3bD(8+}~>w)Ok#~mZI0W7u*oXCqKFRF>8wZtUgE$jRJ^?SSUuQs7t z3GiM<0savlS-1b=*ZXfue#6jn_z6~po)rd95h&%c4l+cAD$$tr6vLs>Oa{d2fiw?% zN9pVkWE6bbO0bfenDvb`DV$DkCUv z-SAYx@O@y)_ofqs_>9`82{IpbN`Ws8ZqW>}Uoe5tO{irKpSM9|O9KrgPuqKb9@-W` zwsDLZ~tuf^OlVYe7JB8am$rJ(vkn5LE2}Y86m ze2ssMpR>P;PhEd~aan1_0>~t#1^r`ke+dzxSUEsc@sT-hT{NKoD^D$DKMVzVqY=Jo zV16h2RsP&a`YTof))WQ=;cq?&43&=zuXjjRY7+$gd|qdmP?gl;g;~->*lFpcT^0#&+*TO5Wx%ba7(`(j<+{}TC()*<1@0kW zBWole1oT)x9*~4E_ptVUG!3FX|?+aDctLxaQh2i>KEq1|O8$js; zC=+I7%+prhsuDK+-tE*8Q&>BV|4BO_^_N(@hntrQY2e4>!42L5Gu1%g8FjH5w=sD@4l`dLxs*>vv1lBfS z#NdD=j0Qz-2utt5O$<6)rY z>UkX0Cfkh0M?hH-tL+^-Hn*%FfXS>o+Y2iqBcCHJaubs((lYQ!>>EORK%Damjlyi^ zKDO;VGyRK=cY{u)sve@!SMj$)X+RnXN{<1;GeHoQIZ^T1TJ56xccbT-MhIM#7pbo$ z2!r0t)=$ZSjlHjRq(3{t@>jfvEytq+HvQXI;WeM|;`^gCuHW;%L;%9=^Vje6U#>qt zviojjbcabfoqE@d&_jK|q-FfT(BV9Hj2lK@F~#9bf+684C?<;nab>vKN;{tAitT(~hk`R;WT;5%_{ACFI1ztgMM|Gtl^ z&edY8j8ha^p!D92;Q4m79hG1O==Wu_73&ss*HX6((VHN2?G_WBcn%%Jkq&+n3F>czfG$mZ zP>q3PLPChxV*MI2T|>PQ+v*Y&)wr z1^p=6afaeGkTd`Wt=JKQx%`}8SpWWsc+JP3-+#ehO#$4mNx+NoHv4bzvFootxqh!F zGVCt3{k<+g%Gf9M^n@tSMZiow;RYdyK|l$|ELZ2YksgMLKpNT8i{-?9UV0}QG9?-I4B14k8 z#sm?Z`A`Xt?5=16N>3aD(H1KH9>R9G4ctvIPsOZ47EpaXEMCv;qPL?4=ps@(bZ_2w zB7E0^Fia+}cshWQ2K4=S<^dE#3G@j~at-99B8mq2BkSLvidTQ^+wR}$KXL%}wC?Zq zB;X}@-TIxLarw<$zrx8Kqo;%QZ*aJx%CG|Onu85hX?yfceQx6}#4?xn$ zS6@x^DvP)ZdT(?<^zjuei+P_~y-#V62%#Gj-s7(8d`}xSz zIfIV(Q0Ep+0d-7&c^;#(^j-LTK!^v$gc;sU89?w6==qFFmIdE;{;x!KTucUZJ$-B5 zL(c3|iW+$eDYM<{*&o^8x0#?&^SE86w@+V#|Lg9%hWL~Na32Q#6tDjYzQaBaAFzJM zFN6vP1eGu7o%Afg_yuJfS+>?pR57YY*totyya%@h3X7Z)l}J!i`u=7@h10SeuurN1 z#~FP(IOlGrPR2S=o0j2HMaG6%0rA%JF37AJNHFFr(t=PKB52-ns@RH zfH%zhk+*}-pY%6MhT=5D5?T<^;uwq?i0U&g84)-wtr_Jy>Gk!7-U-kN;V|eV)leN8 zBS&DHyEmOUscY_lHY{wn>_YCDQ1iq4ozr^ZrV9os>Lup=6)JF?Qy>R>X7eKxA5^7W zv;fec20I9WWYsIpYO7WKQMY3}0%9A1xd83$`FQnHzVrT!f=fAIH|}v0pj*rW@a*;9 zKeFEc3dH!C48XY7`|k{U)Wzte0GF(GCt@wKhg!TSYEVe;JM*LO#u!CKE!!PxZFAAD z_iARN%#Gr6H@jg*A$cLT+^!kCu8>|ogP;z97JAnDPk|)18K{4q_O&4K4H~CRN`UpN zrbL60lGqY3+CI2e8|2D4CJ^1YAbTssB!J)d{Oa0RP)UoCILP+ZeBCfd!2}y0B6((Q z|J2j>Lc6&P^ikl;qILwXjeXQLlah6>guFtQ75lYI-c9WjLC7tigBdTtnt%=+h$Y0S z6uPd0X{)ApY(dJJ{i82jrRWbE7A+(YB>wN`Ac3Fw-Y;4I@a+3G{+Sh?$c?E|-u)K; zziT~ZpNdajzuzBR|NErU|29oTD#z z%2aHzre8Xf5j1GGLWpY0nmK9LTm&|BSu8W|K?;EfMBy+u&IpX7R{NZNrM|k=!bU2? ziGoM9O%rYL5k3(95+Y#2*4nCx6DM|M8o|k}P$Ua1;?ofUT^#S(dcew*HeI)^ zk`7!jl*tUg0?uAb@5XPjB z51(v1^!FJ<^Fg=|uX^L7Q7}yEg!FTW<1hrrAyuX2hJ{rQp2iPZf5fm0Fphf)``c9{|F92!!)=YJq|?U}VnIX#uE_lqn}5Y>%1PJ~kX$E@HvQt-=41`x^qU z21z%t+e^9+lYsBWTkS97W7c0^ahMD+*A;)#K_3%))Mi9Sju_&a&C9d)s>&+DTcd&8 zp?L=A`mZ|$yh=P$^)cwXO&I*jqBd<`=&Lglk;d=HGCa+f6C&UO5iQ5d^mAFoZ;V>m zDyignycj`9J_(+c1AS60F~i@{w3os$QOLn)&e1g9r&=a73%@k`xga{nRD#MMgDUki zq4F1x(Wd1=t?Xz*6u)Csek{nH6WbouCfF71-?adgDrU1Ec%Rx4$U-^o9P=w%sb2vN z^WLizWveDZh(JLeX7RtM)q6+an zno6BfA!%FNsQ)RZzGUO?AVRK^Cz?uevA;&Aw~v0h0b-vHZ44|jAoKXOb;le2TCb(wtc*wmx>>{(oYD1@(!Ti%eld$b+OV^ z=m4PvWP~#_IKt#f1>DiK11MRP{`M>b9W+zg z_7WNs9wB*Lj`gG^l1!G^v`8g+B)vOCf&%E|M;Rz}t*SD%*IZ-L+=mUxppylK#Cf;A zV5cSEYHaV=fQ@DpunRUu@V!DE#LstI&KJkN=Ckmsk9_Ha6Y;nOvfTedKzq0Gz4-d& zDfmO{cm7-I0)v1=O~G#gQZ`cKw60y?jRK$Rk;4sU9~p{I?SXEbFkW7!$wn^7i6Cu# z7UNNZL}vQ`W;)oV?R8?GcY-Hjlx`uEvKS$S?O~QZlLA@f$0LL+B#6uBB3GGlY>it> zNyNu;t07HqvGg!9QLmh@!U@*IcaP`H5*<;|6IOYNFstsc-MAmeB*~0dqkEJTrSCCm zfgPax$ohvM5hilqVZV;+V%WTnLW0bS;CT|ElLPMBa>W+d#9AZvGa93-2{w*ui>t={ zb_985pVS%>dBd8O&Bv<)b%aS!Ju4gE_4MTD7-e1;REF~wq+$UR)cjPE5c|FJqV}5hf<8yQnx1m`)qw<~!=050L8Dbxr2%_9yOPUYJOny#& z`FxQ>Z4J6s92rTWGI0cG+J--OX<8?4;(GS#&RG&7^uJHN9b8L{m%jXTjD_}0Hz80S z>s0;3QP~aBw>TY8-rpM4Kz#Fhe-<9Jn;Zjt0i`)w#?XWR#i{ju@Z~Sy)7M|$d%gc9 z<-)}mpV8z1FT7IrS1k7EAVmt65R0Aq*^Uq%K9@8+l$>y}|Bbf5(d>roeKGmYN*f?q z#99alR)hLNHnXJ)MK)SGmGv^ z;n*a(XHGhZ{F)4ithVvY=IubWMznQyGcDATl7TwTCvAi&N#Kl5Ro4l^;jz3&S+M#f z2`IDB0{Y_DnZ0z~zE8(1|I`B*>C_ltH%juLB>~@mNg4gbbssz|udKL_hEA3g*+~6= ztFCD=kYhh+$VJfo#nPi}xCZXfoC5LHh8S<`JlG)%Iz&P}Z4++oU_6uJLM6G7@4tkq02S?l^=TkU`Dyh znONFj_h2Rg7jvK=yMF)I@ct;;0OG=g@O%yEL`WTO(HBkcJ z$dJC$q90-+fk}Pc)IjkBpmTgsGLnMRXxRRsYXgA<$O#9s!XHnT93}yhCqg_Y8scTz z_f0@`F`1yqh@7}bJ^{MKK#+wcNfnhqiH028B?L0?t65bj9e&irpbO4UkGUe_;SMGN zDoIg<#^u%P&!^%QAMywu*xOR}&vFX>lmz?$eq;Ud=hyqsg2(on3$q)oEy1{{&Rs01 zXpB}AyWWK$5X7RtJ!gpa!;IZLdNy~e=$fEjzN72JA;Qc&cE{Tk;bdM&3wxXPDOdc4 z8ss|HHziC1A#{_fU)F?xP@IeVderaRCL6-Qr<{}O92t`lHm@i`IVW7B1~KX4q;Bkt zFV4nI;r+fp$`mW?(*ZHz(j)^@0ihWR!02961o+-{-k^NGhZG9&t9rp%BN#+v7PqG2 zZzc)kI9~I(-C`1;(22e20JEQ6|M?ia;z_^pU`Mwqv~=PCs3hPA@wVm3__OP;Ke-0~ z|EQn=n9HrQ)GGG?ne}f$;3u}&4BE0dGJs>xTXA|6RNj(}Fqaf9Ex-wPpfts)2VAzt zY7aX0{+aRQMDR-zxuNfTVAvn}sS9ebDDtbnLaOjG|z53WK?=hrgeZ_d(=^0J-0PaO0bCVkF`PAq91PtY;Eo>fw7Oi53$p2%d%T<6va2@J0k7KefNRW9xZZpqziq(~u{GHJ;~+ zp(ifHZi5OGVfyi7PFPo0*3vLf%RBti`o}+uSNyTJog$kd%gqg82YkBST)ch=zr0>R zAG!YeR&J_ItxZ{(t;5DWD3}3KM22VmJLqqC9}A{Rq26vWlkSCA zdtn9Pn-F#4WMoYEB^{!`($y>wgaEIPJ#i$6%3gd=B^XrN6p_EsRQee8^9OWW!PWKW z)$j;HM!1|$9ZvxLzLzH^&%gSt+D222Pg(-4JaALN+cES0T|1z>{+HMLpTWyN=$B6s z4X8p*OK1T+pkhF?+xh>6pI?9cnf3l_nCNoz?o(u?Ss#(Bfyg;1;vn7^1>Qi_GI3}U zjxKmR8uF2VI880?^j)}jtNNyxB@WC5XwVL>x|DFvA#f#chvs?R=<%K+TujtP66LD zF)Dz&Nx-kI_m9HMKk(;Ikt`ew%k3Dg1b9jSViNFk>;Cwt_5QYQ;ZFeBpk6Eb?A=rV znLHoVx8$CzBp9M$DT@R|G3GlJJFZAujgJZP;Ef9!psVjyV zD8?Aq02EJA3UOK4k@)j z6SK?oIv0Vw$Oo$=p!*!F@9Re;0dHI1V-5Zv@N=gKCK9Z$zm|HooNgrv_+h+h{qa%j z{a>r}yuhN~Xj(L+CQP7W2;w03Ylly&W$#-N$0QAEA%ga8-3FWh`iRC)14vO~I`=6}qn2yB3+6rkS;Em6yS^!>iB$ znGgW2YhaNCmP~w+1aJ$%Ya794ErOB+{Q70(|C8_krc>nVlDs$uc&hr3O9K8GUcdhM z=ygB+x=aEl(B5o>r|bROgDR46rl_Lx}mHfs{MNybjU zeso>{)C%y%7zPujwn{|OqO1}P)u$EP3SnBbB??DcJI_pwFd6v+Rq&W3g55|ephp`E zQi2JSpA!m#^vgLx20`lFA7UZ{N3;N(2#sTwxbksD!?O`ciz*1F?Yky9=-#hI1(aRp zR%uh9ZrS&YRNBt-h_TC~hjzr+V4nnB1phyWpM1a9pCVS50A#D=r5A}f-9!@bT3m1f z4())>xW?Gb!XUBx3U=k#o(Y~l75ZiV`TxZS+7z9EEjaosVBr|~{HMS?`{JnY3UeL!av;u6# z2k2Ixrlnx&HNI2v?<4`06Y#d1mH!`o?I}`qw*@>3xJWqYbnBCVe~#C!KR#mJPrs%r z^F>_(tQjEow@)O2G3f=vb<)utR#lzH&l-DGzW2Ro$1_bJ5I3MqjO+zX0c|U&KOqNuRUmZmQ|gP()q6w=MuzgGIv}bF zAKL_{qzF1-4xjK+818S9#~~amA>0SsDHTl45!cCKd9AqSR6>x}2lUQ@<@h$jTu_LD zaB7?{_13fmRABD@A6o}x@f*Zbo5ZaiG@YM+NFva!bD{+s5h{1*D{FvskPMg z?DNh^!%RUnzU;SkDk3Wit70U!v5o!oF!v6sYiGS+A4 z8Rv|k3=*2^R32xdKaz8Sxtz1&1aR_q@$31drPGm9y+Mjv#E34O(Md6TGfm(c%14K0 zpqF;;WVPF_nh+?qKAPtM(z8F>YZYRfI8- zBz5ww)ngy_dZknAfD%4VzHYD{L3{7_;CEhfBLhhh;&TxUq>;Q z(+J?BN&+rsOh3#nWP`;8sru~l;`848r85(Pfb@aB+VA`W(7^Nzf6Kn&Y$`w`Ktn9_ zqmlV#soYj>b>?$`S_w;nn$Hu7C!sE{NG;vt^{40n$Sccz{Ga6Ku)35GW$OmyZ|4bmaG6lg~IL=f+elN>e;qW2wGQ$X*dsh+!*!!bARD~`$OaBR?V%vt%RN{@f*AYYL% zaW$q$V{z%~sxy@cc`yhX#NilR9mmSM88K7)(I;@Wi&n%SL|{y`{gs+tH|n@6x&tl1 zl+dXT+Q)>45qSk@2$QoQ=*h{ zAJa4L;Q!r80abOX|99ho=#qeSt+hXmAODa4)hPnK8!do?Hu{DDoC5!&y1nb6OMQR~ z+2DuUi3*<({CpC;T_6gmi@WS;LXiES2~0|+cq)sF{h4{GZ02Z9d?JG!WX3ZF>R6vJ z3u3V#^<1_1?lP&pR{W#0XKt|9F*Ch^7Bd1t=#uj=8LfkI_7jQZKvjWP zI`AvRd2BZG-$?_DmVgZSK&4b)e#XP=KOc!7|3fEj|91}pNXX7|w21lXHctW`#&53s z?ZQRydu3%Vf(beV029%du#hq7Yc#afksoxVmxZwVzzL*&uv~iDc^3y+JK^4n?>OC-N6L4!~hFpQ|jvFLw zG)X5{&)lc)CI?3I{sX<7GfV$6H(zIKVPrsXdrt(4*thZE1*mKcgF$MvaeoX zqAE8#LK~n-3T#&01HE$K#oB5ggCGBczj=x{j{*TGEdboYS#rAdNx;kS_Vve8*ZVgr zB*1+>=I6>7NG-5C2?Nk46=TBIFUbjnR{{FT02TO|K&2{kVBbu~0-~s7V@K0_Rr^|! z(uncj&22|y(?!C;Xpc|E4?%5{P80}@ZLX6|a3Uy<>VE(n;S(@X>l=HX2ooFR&bqiY zi&9<+Xp+yFLS)eJA4;F0LEfakey7}rUiR0ULST2Y0{fZ&Htb5fNy^iaFOu;_c#7DE*z-7_;PPIfnI9c zN4sEaEK@Pj8!&ngap`d@VS9|MR&A23@MjNUIUHYmSSt?CTdbM&x4NwxVg=$*&~|U@ z20a%hw#}-sX+%plk{`$_I0S36>)^6jurvKkt+f_9Ag>A94d1hqCgvBf+ zb%$-|?~1t3gJty)&tFVetkK@rC=uICN=LTwJ=pC=*=2+T&^%iL$Hxv!U9|YMc%H^< zOueYt)1tUjp=IMtj$GpYBim0#qA-M*(*|1GdNC1zSE9 zKlUG-g8%Kfm3_PjFMw0Ppi|@)Kvdjgrk}vZKg!WjBt)~m{mkVLM4)?j z0zL-_7vp$pn^Mml{kSH)ho;rU!06`s|w{mAy8GyeDd%t|BUR$ z>U(Ti?43V=_-+ajnH@)o9tgw*&3I;qf{jM{cQpD55c(d`P)H>X9PrWEWlso$dx*g< zigyd>Uls3}H4aRR0ohC?@M5jB55teX_Ya*S$ZbvlDA`)tRpE5Iok_rjM)e1;_gBpl ztOWTs5U~v8*mrDGi#h22?;!J{7YOP(Q+`s?ozw0e%SLBPNDe=+Pi005r9X z7Vx|``$PukN#))hXJ9T~)QLJB6Xa(>T_%UO5bXWv39%jazS+D*kYD$z_4!Z2kN&=s zuK#VnEhPz9PF4S7lmz@Zeib+U170$_*!ZH`f*Os9sYS3Q_m^@FSW0 z;yH#sc$W8W_yn!oD&{P2>%Q|WKtm@fWAuIm*bDBcA~iA}p9$)qcVp}l6WIsHzN(%Q z??J<(0CxBUbdIq?BNW=h=LmX{o$2|C-da~(FRTtYRR7=mb3GbFjav-zGQr*vX_EW^ ztF4gEh)PPCa|Um>$_JQ<0d0SK$>kdPvfukFr-<>`Bmqks9Zt79kp%p^b>BX1{q=ci zFVh9BTm<(Ze}26;V~FuZ)|kL_$%sr48mM*_$l`)(&|_x2q%b5e;bVnA&ImkNs2*D!edX(f1y%jMF zM|FhDAz*r?0yyP(G=@q=djP#LkK*ujgg(~9z>W+uyx02gl2Dn5G|9Dw7;c3RAg=1` zHh^EPiBH4J-s?2xcSlX6pQdm;e~$HWubet+Y-k%>LLOU6uOnZOB`$8EnBeg}ek?AsW)<>3jN`?2N*NpE zB*QYi#7_sURg6s zWCdb8RGb@pPV=^na6B=a)feaaHV$ZYpB+YA52Kf7&{g{qx1+hn()r-}oLcC5J-_-) zc>UgKL=hS}>#oyd#`*THYhZqSbWrhTg|LvBbmM~fx9k5u4KMrM|M(Og9;*c4`u~eO z+NUJou}B2IW8Kdmy8il$*jXHvJS@<#!H=xuvC|sZgC;v!(zdVI5Cc^;;ydkwt@>(Y zfR9nPRD{A=zh1qrxXUq&?V{BItp-?*F+Dz9AR?Hnz3q)!wk&)`by)6q^0`AG66oCj z8p~sOjt9tpG-I-gn8%_}=K_TpzH_`g$UjaZD4A+b2vL1DipUWfspAe+JFrSr{MpXB>=hovP_Tq>2_z6fSgJAf;gaUka-Mmc+ceKz{ewh!{@XAgL>_tYSH+6%v=IR|2zgYgYKip zM5Rr1j-AbStw0Bnz$CLE@tu6qf@u8`n7^RfNvIOaMW(meheapAYhQgd*GwxZE!rt) z+5n2%&@GR0J0|)71HA$ugiin(KFTgi-y|EiG7fmOPk>GM0F?;%BbRI6!%uwODH1$Z z@xQ7rp2h$l|0LkTNa)FH_5YP!2H-meBzuQ%4<%3)qpX^JE+p#&j|h&2p2!vrWrmXz;7pT!X6iCK2GADFMxQ@rd3OXiul;T7kQ~?2(XDp^ z`mwtr2;{xFj@n=4yqb6zbOb##Q!@kF!7^6%>H9^|pq&IsU3+a3>Dm}bp$^;rw4SHn zVw<@Jzm=(g{Y2mhKEQPn@Kx*2C*$GY^{-D6;4wx3EK4lkc=xcL)9p4V0T*h4pZA0h z!OPd5-?ZL;&+J&p03X_Jsz=;%2E^l_g>Tlkjn()fk^oSDpKWxb+;KO`cj&h+(1}Lu z6&o$6i#6ZLYOfxXeH2yx9M=-HvPXYd>!>1Mta1_}oISdI7~b!2Jfd{(LAKV!^ABGt zel7w^k^1D~oQeC*L;#)1(@y)#)EZ`K>TbWHNm@8rAWnAr(v+(x?d4I9>b4*GW zR9*_p4^Q_C1^1=7wiIx#B!UaCqw^UFZ*FnTI9%seug&<_j5pLXL$p<5j=7~TusK?GS!LHccsBrxZ74b>i1`zEK1;LB90MH9UaqFh5 zXcf~O&|`;EJc?UCo{zClS_Ct3s+o2}+~0gI6~#mB%V>`y&chi`V;otHtL+(+?b?;w zUJjaS^a~w9O(ZhIdqJAfa5|(UE(J!lZ-!7*#79I3>(3{x!T(>JR`|Oi z0RZ|jz!MU1HzWa%tUsRqgg=3wSbu)?dViwQ3fLXhBd0Pm?E=UZvxF2W&?%D|AiMl6 zqN20_>s!-Sum;BE?Oe!Y%)D&QD<98XJN6*Z9 z5Y+@koqG2NVrVp9gFaU*1gr&__-ItX9ro|=I*R9A(rFsr$7JNR9S)l$wP~MNX5%kb zd+R*L7uzX3iZ_~bJGpftk5=;U5uR7CNn@+L|EVPg8pk|lzhLM3xz+b!AYVPNCIOev zSl2QC96WUPji*)pZfXHsHw~9l@V|=_fmdAe0sh!}e|>kDcqU&#-X8jWiV%efq&|b% zTSJp(tPjUl9~1RBRkf?i4LcQmN@kSWAaXU023k*oLW((##@!IT_zzqsV$l~sPDkt^ z9Gu{Jt?WV2fBanEfx$uK)iCJt_J*5~|8ffccO?R_;G_d^ZzKV4S&zp>OW^C! zw6f)YCQS3uEC7401GS);$oqM$O(qeIUcTI(CJ~siulWm(340w)Aa1u|M_sQ}jmQF{!F5nIEGJG_D*!a3KWQ_# zIH86xXyW5Hnij!cuslY~-**yZIjur(?vu>>RpjNx&=d_VxZZ)?c5x-hW+P(X$|`h;^7$BeH6lRN}mNCA=#yF<}_!ITjGtXB*HbHq*l=Nr&$f z7cn_su5Q16xkf#N&s^_sKdsz%Ap&4~Ism8Oe>W!r-?RSsfc5^vXacMqQC)PK#rEd)SUy6SrCwqI4QO5Lwhw2S-A zc9`$AgRIk~ZZ6D%&i#d*2c%Hq+rYQ)ekH z0v{BbgIc+F?Wtp=deI~nYFjZu7f0;tp76n!62jkxo7w>yn)7SX6NuozjAIV8v)8DkBC_GVNeUP} zSgcn3Mk~#Mooyr>WBk5Ww^OmY+4QDNPoUWW$l^%R8MS4y^M1AKfEA5@Fx!S^n-o!S z5t}_T6-vj}8r3+@(+bM4N$zV9BR)K7c3gUhNa#t>-!57I;k^!108w*z z&YxUP!T%l${+S4P;}rbw`6S>%M)*sf@WJ@5_2)lW?;mu`aa6Fny&H$W$k4{h#d4LU z=8~hdDsNW_oXA$f2dGkRlQ)%~?bz8iRZ^_Rl3lB+$Wbe7kJYJV8Q$*mxf1Ov>@?1Z zDmHTf{}3z47%*(UA5;Ri*h~^Ozi!a76RYfGn@dPaobw-yp!oGU=AN}K;&(e#|6_uD z9ggQYeXpV)fcm~h-*X`pX9$#Sd%j&u>>8l}opyhR$a(lr)H`rTqxG$?v+gqk67<^j zeV=~gp*os;y4_pV|7i^H{z?QcF33Nz-k-JJ-`-{VdzJs*-$zrebkO$z9(h%qCrg-nD=acrH$fFtL_m5##1QW%q2`ex0c~Q@uMiNeobH=&F2jCQx0Ger3u;5kGXr50`4YM_Jn=LBuU?$Wa-b-SFHCB z{2dSDdGGP?CE|Fx-OC7o?b!jJZufQ~@Kbop`r}Wn_s?AK|7I3fJFoP;{%yB7+a#A9 zejHPJ?=}l6rmDQ|d1&lZQJwF6qN_jkR=}9cn1JPOdsz7^jJ4YQ2+zYrWS%I=77N4iM2gjgvQ?sZeuALcgrFrge z;O4Z^hyKooFV~dQ?Ve8nauRSd1G;~cfU(|Rxc>T|ulEh#L7Q|-0zHZ)6o>_`;PM)c%ZAtepN>t&`AAT>5;=n0Za)I|mGjd-=0BS$81o8G7RB7f;7^ibQEXj%p*xgM2=n}1~HCK%dDnp}l zasERjGsidq-ioMCc<#lT@kgKdFuv}0J=}IPPPcn90k}>APE!E)e-}l#uYX~^ z|KjX8+obYgb-&1T-(#H50_SXjDt+uuYk#i_MR8{M0 zTy7?f%m~B~lE8b$N&|#QurLmqmWJR749o?DW~k3>bh9&s~KdqP#Vgf+pM5o(* zmjpbr-oI%5_5Igl{nA+=>8gizfo~k++A0S7v&_~c7HJW54Qm^Yb1`Hz0$uQE*wu!e z3Kw%y7Q2f%OP zSfNi!W~9&Ac{kHL=#Z0=#u))z5o0%i9i>GTQis=PDNQ!d+mwuCMMojnzwQ~$I4mFS$B!cuAt3q2geqH z*mpD4MHZQv1?rgpnmUdv=yp!jB_TuKVN2v zwYrAJEO9;J9NTfyytwv1=J&r0f9dzVtX~ICxBDgjs{~+DO`LA`c_Q#?{19%o1wJ3m zw7{NFkT*fR8(->vH&NNo7JBO{{fW7af}cA?qpQGxx#3cI{Vi0SYE>VK-9Ga(*b9)e zNe?xFfH_iuNig(D5XBMy#}S|1z3+f-2MzR!eglLWU~WDn69W&!w;TT!Ur`8xY7YIKL!5>I0<;idjH?nU*8v(KHhrHw_8|> zQXcw|v;UvHtBbLuD#BI0LqvD^QT`MV5LtHl5e!^`#Te88f~OKyty9WC71k0x^@7`3WB8`P8Rz^Fd8$vT?kBI-rK(vo{vb2b{&fG0Pl0a-c!a> zP=fCGs#&w7Yv-psE;FicN}#rRx2t!2?a`azyGLzi(uW%@XXzh+fC37%C;+1x2<#%R z@&=qqd=EgJ<&Im91WTxyEAyl|^Yl*n%5l~MF%%HuIc0=&h?H8v5BO;V2@J7gxo)qW z7SCS_$Hxgp={mF4kqqZU>3CSLdI)k2aN4eK1p%g*kmDTZEVSm9|}-0q%X`7;N&7lAdFKoRwgcEA~4CO zw6ws{Ie?zt89-0J%>qIGdT+1Z<>RK|>SL!ZnU4VgkI`xY7|B3j)0_Q4rNJxdQQ48Q z1aR}@h4eUanrem+XEk$9`&B>+_d!NMiv&m~DFTqQ!bP$Dz*n@BLe4ZZyUVB+sA`su znHpj8^oJT|>15E6``Jj{>vF_TP|kD+gdo+$VUwx*Qvws($J}cWK!j#J(VqLV+xxMD z_{tNeVZ-s$u*tq?Mo}W5Rv4fOe|AXqjefDol(7Vf23 zCfD)zMFJI^rr+8xSE4xqFxdYTs}lDt`hcPgr%qhjA`?7gPZ3{7eD;aca;UG-N(TY8 z=r2t88>Q%tK9t0F&|`BN{k)O*X;dDs+>C9}c1qEqR89coG$|z(TKcGCqOS%}LLk6~ zcL|m7IKhB9$F0QTvr`L-AQHXgGd~Sn96Kl2OUCS7I=MapxH$1nSh+AfP%eFS3F$ch zFq>YP2y4|e^(gdEv6iWzEyLPVW$+{ZGA*t@c^Yni-?XLgHIh`dVOw}%fF}Hn67+f> z-Xy-67EdMq67f?>p^!PYQ#AP;uXZPZ08N`yj{$--UmVwvY5IsN-la%2xCf{ zcl@<+sp%Kj*5uV#{SNoJgzjGO9KgtVfW!hyPB!UFSpog=O2=DWq`7(s?gt2=mD(JC zlrSjn^HlRLu3I|;KYahRdG;Gg@*g7*P?+#H8UYCGeXS3-R39CBjBcRz9~{1vECQM~ zq@_#6Ol=&5{Ye?*aJ9;id;SZ2E^#v{u3?)EnH63>Y{a&|j25vcZ#st4n zh6MnXL_nP(xzUOy0s(tp?Zd6KxSsf0;zt)Uo-{{{z?a4aMIhv8lTnIULEuT*4?(b6 zbje64IwT4pA@VqPdE-Kc6@_(nlmL$qb|?WCEF~#ej^)LIeWdBEFLr z&%rvq@FvM3kR!0WrteK{hl~~qz?ZF+oYf?uBhKF{bMlggh$-!pu?!%{g?&=MpcJl0 zr+X!o`sD;91Xi7>l46Dyt$~FS(#5OF6nKNSsXeqp_|}enF3(>g2h?bpCmX8Xsp{Q2 z=d2mH^UN7OVV99n9tfyK|5Uc^$;c={d-{VXr%EsUz?vSM4|R&+tB6-gKmZeh6oa;a zw#FU;fkC2{W?hlcs<|}B1@f-}LOMr5I7Lx_J~bc-XYBYYI^kOSiE9ws*9x)QKF||< z1<0Y1Vv;Bq92c{5-vN{z6NBf%J8=vjn&7&W-m`wQ)iTW^UkI&>?{$>H0=f4K5ErwC;g--6hA$5DYp-n7aU^&6Ra_4O;a_bWa~=d57@O73>DsguR>y_vMM|iXkI@A}-=)q&NekTdl?{{O%&p5Q z6D(ar&DS7N&Nnp;0vYEjh2cr(-MUAaRsKq0r)8!iYI5$M-T_y?3!flf|KTn00~7v6 zld}M{q4<;uf1^bP0@V}uV8!q3!Le9R4ODzwju7)7q8 zQ!U{$Al-lNmPdIJh6R35_eFsKv<*%C!>JsH@o^|qTF+5a<B@flLe2kcTAEe^e_H?WjmL3}+uo}Z-lAStIn_`47Uyl)ovmtC+9F&gBeCjHO^-!k8$ z-k&w?d)L2^{sY)c?ful}Hfry?4KO>{Xp$)(DP;V{hr$lwh$Y_!1zx~07@ZO4# z=)uv{UI%r8q4gkWcbsDxtD!^e!Jf*s4~pi@;b_T<(9-$@?K~9)!R=FvCUDHjwVdKY ziv{K#OUf#PlD6&S|As$W-uj;7+(0AG@W$7oCs43DNM)q`W2&yrKF|3x_1sDOrr%G{ z@@>@q=<==bAA94C_N(?k_tHB)3=jspn9&CWR2+?PWj~Hb<2fBIL zImV>j2ujU#FV!mu=l2UwsBvL=bD?9{{k$}DHRt)C6viF))fFf}hwUA-oh|*8zJSa* zVJx`A@k)ptAtnC{zg#qC+^@{z2)bA? zq)wtAe^K!2Y-(F6zQBpA|C-=grr zALaz$1UO<^tCgl4xET83z6ywA_x(X}L=D`5?3~uRJ}y4axf}=*SU}z(Pbyv%_62A= z1g7HbMbPD92hI?H_<16v$Pdi09(dclPU|;Pdl&J&*K9Knexs!<{M%o=DHHxi2NFHs zAADe|w8MA4rw4~X?KJqA#(8jc4)#rf07<|U0T5t?I`Ud~m>{T2LK!w}5g985riG%rThj-|eu_%h<7QXoJRhMhtRL045dbG8#K0-^Ol z+)ZevXC$l31ViJL#GI92Zaub5_L2Y&HS$6Ma9z$N66oGd&@vk~w2RK+m$dS3x;MYN zejB`IZ=lhE2>|BbhA9*NM$3Vo>kmH2Kd<=sI;`+~tV0X0p!NgWtAGqX4uz}B(3D$( zN>LwEvF6-5r)avNwLr@aFb6OcCr2$uKx=A15YCd7AS|Qnd-zL!2OL0hDR87*E0C-k z{FKgP#P`wSeRQs~H*UAEJ)`9X0&G+(xV(9q)c@4z#9$qsi=Tq=43agOM z8zJQbXr@lo!V*F}r;n7Vh82)rSgv2?u)ur(5DM&2&;fHy7--sa33!|*WL;eAKpUH_ zp8$MT!Q2`GVqzg^gBd@mQ)q%(;Hf)5;~#;==0_f(}gX{|(|tsQ)J78Cu@@%~^QG-U6eQ0|1}}0;a4Ku+d7U zo&A9T*w`?vt(^(aBfh9vmJKo%mtm%~kuYS##bqTB+oz63qu@%7nj>(L6m_I}>hhctGoWu$G$hJw6K> zD?thvxQ!v~5!kb{!l5tL8+kfx#Kno<@8o)&-3Y8O6I_`%helBW?%X=lQe$!~FdPT;+MF z3hC@1{tNM+i619^2AVVi(O8(b55M}KU&)Vg6l9mfzTvXgkKHb#U+5Q7NPU#?Q0d#H z-zd*wr)5mP(|HyyPJjumbLoC;pAq`G@VFe`*Gc;c`{W!?+;8fD6aHNV;BpcEW6~%g z`^6c(ACBqRa01r=C$yjD&OTjnl^re^^S#8U5TD$%r+B{)g#(P-c(2>b#5;-q?(t4n zx6k4P_bufE;OYcKR+7vDPR_*@7dZu#K%|G1L6~!=lDmYGD>PY$=$DmZri4S00*9O(s-vq0H*IeR}D+-lSrUFb`YSzs1ZTH)I^29 z8`IZhMB%?6?SCl=@dcXxC#C&QQV^$vey;g{u>iyZ=tZoB;Ev(32~!sbdtl(m02~Z? zO~dbi+DY-y2ILq9W5Y2Dg4d9H%qOn4IRY^%kq$WyE@AohfO# zcVOw$m{0AGINj|p0^#PQVQ$D?t9h$4`35+5L(`$iV(EY@D5r3^{4C6oTq%LxbH&Fe zhx>=@3kE5e_2GV@s6BFW?G+On^;uVvtq3$JA}_ZgcYLp>-!E$kNqEwXR=d>Fm2X|_ zSzoM%wggDlD!O+JPrbYcs>lm*#ny>nRfPpc4P}Gu>z9N5Xm6Nwv%E3KklAp0$6S8A z2WK<6qFK}w)Jg};AUy-ZqYgW->2jw2xZtg|8SR4kUURM7PIikz4qe`OQRf^yzoc4w9PzU$m}kNHpE=$I5H_~s1k^+s)>r!(CPaZ7p1~}>GTp;| zS_{O_F8uQhxsV%WWT>PX;Ja3x-}_OHo1UdK9Wt~^sL;XqZ5_}Bvaf9a9^!~mw}=qP zc)vv0KdiTQ`sms8-t~jdUA_k-a2;G+OoF0YgIegK#Xv*0X>iTmzb1I9YQ;niaFmvg zUtzg~_#Xx~>(pwFPyKX=@o22)e#qUe(C|yO@Go=yB#ln5ZjADhU>mu0!_{7bl` za@MxfpoX{6!D)&-bJ7{FFCPrRDLmh+TP;h+t597KKka$5oU!%4CiuzyNhgITfuOz) znv^tKe8ICGrLyzah|=Nfhje4U8EIWf9O-(W$>f=4wdL&Qjj?-TB$^`=*AEJJ6ZKa= z;!L4Ix3_CE1-3%aEAM12Yk`xhtob0;zE-Sy5Ca&Wn#zOia zMp9;9H>n%V=Y~C1QCyj1dkio+z?z@f+29zzx-mlSYAwoF2;c8v>|?lke3x}T2%{Ms z6l1_pa?&8<9D*@yVdnRhAc>g(sI@W%2HUtr{;~Va~ z+~ZkYSE*j5JFaNzGoCHZTO!r)E4WV}x?}QBoGfR@xk?8jUrS4;bga&*1G2iyV&C-g z`_xZPT>_fHJmbj=H8JlH=K)=!?fu`(7i-R=9Usr&G~!H`XO? zh;*yh-Tg~tcBR=lhVFV1@BRA$VNw)n(Lv!=hniRZPs=OS05~o+0fv+*W=BZ z0pd6Rn)pGd>m4O9MiUFQO6ggD2#ajF z2(N<18gB1*gfhGFi!&Wr#K~Q+ie~nK3p1I$&BL}A06K;kmBf_~kuEVlHkv{K+b<^a zX}XO5mAx;*eLjlb`>kx>N?fu(d5Xa2Vm%=U<|M}V?zH*W*D0-;;8quUTO69WQc6_D=>H3~`jdAwcF5q83s$0yEbh0lb z_VnWA7uZ&vs4`VhXj4w2qEE0|L!(&?YSp*jZYsE`<+!L(@Um}XL$GhaP`og7%2Bi4 ze~3Y34ZFfo8&S!G=!{foQAOw+)h`EE9G4@ukc^&f1}i2Bc_;$IXUl_wnCOWSIx|fY z5bXv#mnGSjej#hD%x^0fuOpH5;NEaZ1pkJg?(@pZsprx6Czgh*Kk|C(zpV{xAK@z) z$^RC5h5A-b;yEwteQqPIUXCt8TAKXmx-zj6O~Sr-_+>Zr{o2mVde#-{Ls&6m6b$<7 zypK?y#ts~{d16`r{1F=iy^H^b<0|iD)VZ8@*jG zf5k5Zctqq5%F|AXX%<6UbtE*panUZiA^Koq8AX*N< z9CFDXzhl9Ss@COtQ>fo2(mx01-|n;e&QQXpgYOovK$D-ZzYik#cD5+acd;HWQDcO9 zu+yhLAx^mrss(&)i9HNaZ{LaFdvFx3c`?X7Ww2l#r28NlR3;WvLD@KypZPq|vP#! zgb#M26n&$Q;QOU`e;F05zZJY@kfZI*Aa>t$91Rg$o>g5wWR0oSy)001jB$dANp!c1XFvcjiUtmE+H|{a6)f7vNngYvUMf*>(W)9WXdYb zzp?JT8Tn(~*_frytJ({B+Rj^Z-XfE48ZW}1OI784pOnD*{R6xSn4SryTJSyCPrDo% z7&%nlv)`s=_S`tF%<-?IY~y#Qju8`ccwTsXG}wxV&qjAh=LNI( zX~F{2`|*1&o6PbxxqG2&<>4qj zxVc&_-TV5pWPsQ=m8p%J?rF%c5^0(1R<0+*oa=2^2c98|>+P0%A64O~E#lRkxV*!b zO>o`b%EN%AEIME^BV(hdZq~yGDc>Z#{*VO|#>$HB2PABT>cDM-Cl0br)JH5N zH1BxB+KQz=OjZgefE&K=dbTqJPB%#)w1p8(e-)K9yOtK&qn_BQT$^S(yqVnzh|k{& zYGTRiZqH|wbGUZ8^>JL~LmAQ0btxG^x}mhz-r5S{gg*(`?NO6=kn7yo;MS;#mTy~6 zT3gPGuc6z`Pt`-V?Og5ZRBEtw8PVS$R#}f=LH)iT%xVf?Hm%g%lJNjfpxXn>?o3ak zzp@qKm6fOeX!SLI`gc;oJHJ1)S%js{n@{RE)|q@i@8ioCQA5XGR;{3&Ef-){;aadXUh~qYI%+PW8c{V?lZLx`*+!blDig3f$Jlg1i9y;BB) z=mtbPWNIq>*$KQYkJ;#*54bJAtO7VZg>ozsM=IwU3hUvy@IzX4e)gqg=N(DM zcdZ5 zpHZo+^%^A5R002<;c=Hf>W1nbb#>Nvn*@$WW^avDNS5(KwQ;waLU)$R&7d|DXqJU! z=yoC_ram4HnAVeXU1-o?aLEa5P*cSs|1>Gp{p@-|Fs0Ez|h>sun(I7fKzO= z;7|Ai^6z1lncAEy%}4i=G=*VLQ#QULj`w=o0;PZO6~I$g4b1GwJs$0FbF8j-M>z%b zu?g7h*ehKg@)_LN`sFq#MQlBjZHe?5x`=DaS%u3)Rhqw??R;EFY>C|{Z1@Yc4&Vjd zK9BB7fhtH@{_Ev;$X^_s;KoM-9J6OJBDB?y{kmvhe~KA&O1x>an=?b4Avy^|wp1Zi z^|*oN<^``A&G2`xYrpQz$>NS3B#ktt?@pm4TVlh`H@)r720Z2W{%^GLSIeVAL*1wG zwT8imW6}lFy{y~ISTC1gsHX&xrRJ1!0=TUUiVpVpqEokz?1Dt0+Q zzXkN|`-9BXz55pxJ7vGRHoDch?UB79yN6cY!FJEjzUG{bz{o`ZA47Dn3SSh~}qNp$3c?;Vd)jeHa z)A!hVVSXK2%Q_wTZ@l#je*xxjob(ayp(>I561b^8y4h0+WVco(lq~*r;LHa4H90i< z?N)IX@>qllH^U;K4b5hmO{Ra&ls4aTOyziAYQ1~jpdg^RW z;e4Q{#@N{JT47bJOpt3|1*5RaE7G``ufsfl)LenIR;Lt=)EM7EA*wH^)+E?t#3QvV z^}WiNMY{A9i?Ew??MbIc>jtkkeh^jwH?pz^le+Q zc=35HKA-mP-mX3`zg})w8U{@jN1FF#p?3^qM!(uN&z}I%`iMOTB*S69him3EH)adY##&+K zWYKqj(*t@J=nI+-Cudie4AD!{-NKNAxy_!ro=V$_Xgt&TP&3Bopqx@uu~Jhk2|V7I zvOODXM^LAv09BKcVZ&GLiZWsq%e3I)h%(&@>m4%Koo_}nkR%~MUHN%CnO1`V7g733 zRA~LszK0z<;;Erb)kKQp?79gF>7UB+8MfucG(}Q>gboXYMMB3B| z2Z%Yz0Pj%Id{{mk-Mdd3Rf053`bb#wW`)(nGi$f}C29obPb8KnaJoNesyNiFHB4Z? z$Vqd>)`@y%jG*H`S%+T}-Y>n78B1T`hdme(Om~2?5>`|qQO<`nUFap364~ zz1WL4?$9#uS{hWD&lKkU=K!){K#`?L05g?~F! z4dhM=gvG%Zuw*7=T9XW#c{`mD_;@F)!H3ji3TWcRY6g@!s?eA zO~aNBx<2L;gu4Y$8z)QFp(xH~i!OT^{)WD}nYe2xh}aDcqYf-hNm#>b!Gx0aqlVXh zS4X}%|5`6I&&UpRd+%rCk)*6{;E|&1I-pZ;`OnpX+cI`zxhp{UOkO!V_Okq+=^P6O zs#N#I0^*4MNpe!_g~6P3nz`}BW~AO_p3SBi9G5xaGhW5?U>jUo z@Y#=y$){zLXY-vncK&@;11PvGNwO21ybg!_-X3bI^4{l-@B; zV50+EsrlfKVJ!t*qf{Y@Ix;)@VYtD0V86cTeumhA6dT$g?=bMt@a3*8Fh7`AvU%Bq zGfDNYC&MaOOYV)~@QK%keuEpPui;0&c1pFufO)=0fOEH%QFVUJ7xv4bho|qhhN=$p zRoFj1Y~a?NKKoMsJC1rf1pnXEATn~lM&x1?ofx`(+5-pXC-r~22wi!0BVnV=QTc^L z7V~X8pQ6oC-K<{S{*T+nlbH!WLaunfB|pALhILwd8($%93CGagmo!oFZ!6-Sn{wV` zL2}fWRdZI4uJ6Kq*=}c)!|@5BP3129tS9#{BuRh9bpBiy1BeO6s*gF^PVN3kMsFv# zyyhs#=h~Ts=7=$FG@5?pMju5~;d8)KD8|MhFvF6-N?b~#c1*`W-cHM#;#A9X!scFg zsA(;6SPSrZ7yU=eeI@XxWa&h141f;Ee7YazxYQw$!Vc8K>`hC1}RQxgJc_ftx&_(u}PS*I<<$}%= z?zFk+tCSnTvL|9M=J)v|Ab}hqWYdjv>uW~z_RecRdLL`V3TC}W&WWvLIr(H_TcwKN zzA1Pmyd#Xr`qAOiOcu}Ef79!`J=RN&vdRltCFF%K(I8>BSnYzVEhB6L*=z#+wb#o= zpoBeYJx3`QwPu%;LEubu`EDNi;XA`3cM*a!*Mx+jKJJ4bDX8$ObCT46s=>}{KUO~n zhV@BVQGGS4WI718{TeBD}_iqrvb<6heQg^M#$6Z0m@u3hCt zqHm5rYn&^x4!k9`Oe^g7ixrFKJKH_rF11;>U{V+qZW}!P{2WN~68kpK^8^{X@*ucm zwDnDrSe=0_Eu;4-WhPSZZAl?mpTwZpHcQ8fkgl!)vR7BVQX<&#S*f%0S-qQ9(2&`I z7Jt^b4xPhJ<;+p(&>I)_HTR=sV6eUWZHAD8fe~-GxB^ik8#r#b`Qkw8WKou0I*iYo zU*@7{rCOlMe5{Lu@Ds_kpu8s6ylQvXsh*(=Uazl}JfayH>G-!=V{)MR^TphT2#EAs zoZ9H)GC$*t;tN2yWQ%Wib<~^7Bzx)$^%j>XhoXZQqNH!ipWE+G1($zP9ih1_Zety9 zk)HYbdEfP^EeY20tF$gl5-P!mrFS1H{i^sdGtR#Oz8$Wg_lg^RG*Gn;*}_q$>*??# zv)YmlFdUo(ALEtfJZbt|KKDd(!7VqhV!!*#!GwZ_7P!;bVu3EBspK%V^wsz2?x|8O z^`HmahUJxYrRHXt<2fp_^ft<-f?ZHy9Yf^pF}Ys&Gi2kLX|0FvwC+0pr&H_RZm*Ht z6Rn1RTjtNtbGG4hjBL@%pyQ+Ihc4u+PC_!W!+>U z{Guhwq7*ghkIIgh)mKT5W*igBa&CH$4?HC@;n*(^{BkN+T3zVG|Gv6V|`C_!*zfmlT&#Op0>Q@oB$RIW9*ynb=<6b5b$vGVb zLzr`C@xV{z2H~dME;)0Cg%YOA+76l{(cv-8!HB`q4 zQ^@V!{P=U!c;1U|+-eCU8j0oCTEqF?c8`X@vHt>LC62mazdOAWLlbHQG8^#t9j?8^ z=TT02WluLs$I4^Y9xR^RqZg5JdaK&rcHjuR?n1dEKBvDbn`_pqy%N zv$s{Biom?U+QK31zj-W#uoABBs2j6C#srU(9-{bA`sdj_r#Xb{pxXRyL$b%*iJ?Um z;F|iujs=VHH(~l|U#EN1+%H?2QxkG*r0s-;RVZ~`{m+2(_3G7_(!Iiy0HzK%>P|o> zmL8sKfhv7zP%7Xgm&`?ar*tQ0mb(b>gg%Y?%`lYFv(7X2{cyD%HTz4ZvQXKt{fQF?(`PtD@*>UhwVr!WF zL!agVpTpm?jKJXKe{vjtpQ+xtJ?H|Jz$x<#<2u-`0tNX7A76BHjT3Ryb6rX!X;dZ- zltuY;wp?I(_^&iwO?LojC>sAQ&Ch<@njQQ7+IQ!r4zWvM%c>XOrJv}ec(!H$tq$%q z@PwL%Pvla9-pXjIPrIWC_av_Zqv~Yl?;G+r7WM1(<~_+telVHo^8Rhy$bAhiSOul= zmdn-XzL>BRDmKDsC%hTVNtydATe}zd#Y=Ce&kV6M{nb5VF?2&lh<gDouH(mK_X zP!k7@8pR_z{k|)5LJm>bPGo{l-dp5-LK@w82`e#k4XHp2uy)TA7z4`Yb@sV_CMGcF zQT91+aE^IJow$ONGTS+6cle zaT21>4>zu=y#*)XH)FM7IISr*de_r$a~1D$l$G8+Pw~;l1^5mDg2Ykztn?A3bZBxr zXM_5t#TT!@DWYQ7CKdb{(?Qzy`>nmLJ2{k&#l!hXNUZ6yyduk7IITe!5U*$VKEtiG zMnT1qn{Z2oY;UV;T1~$xe8C8q*qcdm!hC&sA(dJA?wfak+TVn(HNS9ocleTh1-9Xf zCg?d`IV@NCUPAPiI7OCD76y#HsNQ6`_)LCzA9uwdnJ>RWFjTMOyU_R1viz{u$C3YD zgm2GilJka|I}5pcMgKb}R-b2zWm+haMhBPX)-zZGkj7aZ zFm7jaZZOY*AIxcA?2^q3sIJgDXqcS&e-+k1FK^^$uT7fiY^=0)Orw8l@6$*nOtW%s zm=8YD??=um;9|1hEtivV@%j7Yqd%Re{{)X;E=l}upq3exc--<4#^DqvHpI})5*C@# zXD<15z)MuFg{=}$>Mfu6?ix|C(7>W5`(lSU`Sy7KY6)+b&%8*8pU(?7NBnpmh+8Gs z!qv9L<~NsM7A=o3aB$leT{0~Leia>i+$vP0JA3)j3mGA3ai{wrF_9MgPNY4SFVN@e z!A9Y)v(YeKh)9;h!6tA;x$UMTElTBZ|1iP&ci`6JCS>>vtVRf}b@NdGA_I;?1#iZ4 z6EZTu;X{%Q{?p;R3OGk_y*dxnV|%t^MOKp4MTPFyK*M&s{1Nmt>bG5c(IkwWcI)~$ zKV9FNoeJzy=>J-%BG_Ar|l&AxmNlJP@jPqn-o#f6va1>IhHkNeG_SiE4HegYY~ zm8AZwSXxCiZu~CdL)uqhtoWyLc^q`9XXJi3-r|ysuY4A{UC0NjYy&r!8Pz~#A}-V{ z#;pXc-${B_>dG$jD3coXu#VkXZ|xfO@YP+9<2$?Q;r4S2QHe?#_Xvvnh_2|HPIdLZ z{*D$pPn>Djl+BDEdQ=f&9s$bWH23mbSa5o#*7% zKTOy8)Nth6yT62^{QS3tYfGX$l=!E2wkW*-N(&ncVQreR+RadtM{&ZrKP%)8c8Kgh zd;+CwP<9R__n#U$MXZ4A>i6-}%H3yU}P(X_m02)pW`)C+p)E>(`ji!}R?XDqZK54qQ?r zs2YOFvT6^kwLL^VCKr0P@(}wx4Zzf3U0Uq%{7~;ywRDmFz3$_GYFghsKXdDYlk-*3 zgW=7^VmQdN9_}Phi z1pfa%G`MV0?%FG;@HTVvPV_pNO$Xa^!a*1?2XUB_ZZXnRrmMbK2> z=lplQRv>=Y$RY6B&Mk>^jJ_=V3GL#1@gUbdtNVA@TkSTVH#(m12+_)2FQnb> zba?D^wZGxJ(*yDqr+u||>K8=R9H+}qsIC1v5EJ3C1m4#8?gen32}R?7CuMqvzqxPS zL+|6s=j^8)jTz56yjB^*?kjk%&uo_62rsUpbcvQUS$BW?jeAWF^`oq7kjrb~rPB;X zUN|!u!ofpWc+W#z=UF0?q{+9J1_sq`;m3k|+Y(DL7WqiU{D^p%W}x53Q=h0bWLHtq z%*Br)ynCLy-ngHdE$&`dB+XaEJB^WH0o{X=^=nO@B#;06L}S*TZ6Ajb>Gdj^8TGAV zG7@+-0&h&z3fB2bBKpM|P6 zP(G?Dnfj=4IR@#`6PiV#-{u;`4T*@jln^(a)J0uN9CN9hp|@c-^`JxB)knwJ3~v6G zd7br^+0NOCjVJ&7YkuGW{p}Yy_eh5vu34Zy+Izi)(Y^wj*ixoW=goNb({!^NeYlH zqW$wqjh1X#CkPNH{CyW^7H5sMEv9QlLDRx(hrb7!+ZPx43%XUpuKOgkd^WEqto){- zT3KITbQHFX&D&_pF~}nGY1Oo(0f!1(mv=F&Ij`@Q2=R)vjqNR?@^(vm5;AU>{2$S4 zMzgc7w)2~5NwPP=>xVs!9{S%Y{c|K0cjtFR%}m!7Y14`D_jkmqx?|n6Y16$ylTJ=ypZ$f#8aH1A(v+1&RbYYJ@;(3(IJoww30fJ}aCF%2s)e5T8 zDU7!w>TsSW8A2_uBIaP$X2Izh95;KztX{@C^?t+TjFRB+vkp4H(zg`UBdPg0c#g0x z<@cE^GcYQFI+TZ9vFdGq(neC*2-fw>i$c!ZwjR+;jix;;6yOlD;srX;;gN*7h3kINw zpKLuIMJ&8o807Gk4KQ)tsxJ-6CyqP@D`#r4__`yd_ON|F}r6 zw92m_?Y}f?b!RqIf7cHZ|C97k(PAffk-NlnSoYdKTS-lo`xmP;uDMt3QCHm$_npJG zr(qx(+7IEo-8Qg$q{$xphP3xMhB0|p@uNaUtg;^HK+biLgsn}=gCXY=nF6m5O_hBh z6R%U&+Q(ya&g+X*ooA{;Lajy{Ro$#=ppzqHV$&jBHOL`9wDgOmw(fH4Ghs^lJ$ti? z@QozwkXZiydk+vQ>VJ<^qFGP~o z?Q-qwfjRcCTCQo;SCpNGLwxt7^A2b8^I8r`I7G8<{rYnqX+Gk zDShTnyGK-Id}}>m-i|xt=iK(kOjt_eE!yV`%W85pfzdYcjJ1=Uh;t=ACddZG1bUai zkG`&iB)RZdx1y1wlvy!q*^NWEIW@UgmT|)depMAW)G9@oP8*V3fxA>4dQo~;tTMVs zWXns}4*~J{a+Ip+LnGon;axT6NAX;I#bvRnmLo7+BY1wZ9;(5qheVbueNRP6m#AbZ5sen7)cquES3HsJU>$rpm zTmW6;9mdN?w?XUk&0gMjfyf;@s4cHT{&Yh{WfwOs#7rBnZf+2xQ_2Xh+eQ2 zH530MQjy9y>#FlvqduzR_>-$B-Q>$LS=B^7-UpfO^S`%BGpFzF^gdR90Cz%{bk}0v z*&VeRaW5;tr+KTLqwh&f58hpzh#OH7ejSm>bO?{ia*5pWv3-^S%W~@Ysf%jkBP%;p z44*ml*G@_e_T^lkDrOIdVjWR)eVjEVUDgJE@74yX=Nipxxg=-rSxTd$gCa2xA}Rd1 z#ZTtj%bCc3`JKNa5@I%VJhIF5>?g>fMx$P~{u`a7$J7M1mYc~g`2*p(F`8VSnaimK zT(G?`lgSkm%_+}E7oje{HHi#c3u}tO%YYT#cDcf^KxyO);MbLP{r!f3l2v;@J0+q` z$EfvQYl3vT`awRw>vs?1(Zx!>_jwx9^4G)5uE7u;k$M)8&C;lZ=5t}WIU1>16+LEo zFkW@g-h5tEe(+F>tvC?K$L`R-7CtgWYP%Y<+q{`tI~_i>iYI!bmofiWWyuQn>5FWw zji+@(wjFZkm9&};?#w+q>u3G@R7tnKsf(B;+R3(PRN0w;=n>nN9o2Mgn;S{i5S7i) zP8mHd&$5xcwNjT=1%pn>oa{E#hhqS8W*EvCEhvUd@&ztxh}9gW%JtOK{M>w~8dszb zXbP(_#!#0X)N9O?5ayapJtGp}xbo}uGxS3H)`{`enb*=Fmm0k`GSu^Ob2v4Bq?i9j z($dOPJCTpSI(lrimUJLLcPb4`KIQQSKo)UJQ zO7MOP)CXyvH~gh$>?d)R-nKZC*rGLj{8=(;E%ZObv`_F~Z-bE%L9o_J!|F6cXeA>* z^kaVP*XMJsNx^7>GDb&rG*o7^JWy1JnECf@P!CQeqXc=gcF(mev@|w@&?Z%~9~-4m zs;u(UV0~()x6bwB$*+={o@v>C`-EkZ5K(1O|BNv6KcJeD`ddg?I7{n2{S;t6c($fy zMXv55i4}Z7`gj5Fa?jH)(Dqunnli6|;J@Knu5aF1x{*dp7 zu~tLDN3RN#z<-l$l?inVp-xlpS2;{vC%R(O`5&m)9{gK6XS&v zKsUeQQ`b0G9hC>`vae5DfBbV{G~^1#k=qQ~GFPd$ve4xC;pv13{d2lS!F-xtdwa+L zLX54Rpc4D+EXE}&hmb1XZpy=4y6qz$M!05k)UTJcY}EKY9z*9thAChMghBL;eZD^a zxInj=AJV0!KZ2|}-N2Vsjw~T{NVg=bNpgE7K;PJJ=C1LYa)Ue1#kT|n$W#*w=^~EC44{p(lzz^DiEm*ZiqcFmt zMre1D`PS`>n4^SW`HeRPKC_e#$A?r41YN@-s~z2TE$#BDpG~#-yEO|#6g;jH@I)JN zb0r(=4()@j_%k3$!*k_jXZt>E<3CJ#@;qH33*4xny;i!1wE#5*q&ydQg`U5;I=s{^ zBC2$|bMDRe*~q=C|2@1VRKtu8lD~amQas@D^IFaP{fva8tG2bPi*1$U2?)%pr4rbO z0#7Va3?4q-T97v=I&S`nf+Ji4qGt#7>_`)11j&iDLKlw14hT1g5l z?_SXO?ijVkthQ@L5M}dPOy4!G#Kxu`GvFt$Tb-yNd!W)X^7p(IuJZ4}e{VXv?(!~J zb-n$;dn>wLPi=}O^uyw2ruxQ2A>Ss=Gj_J`>E!R%jX_U0X38%KA-^z*J+Iyc1dTRo z`>ikCG+=H6!Nd9_p-Wy*jq95a&wdK@?Vn7n*Y}(hEzr{Pqkpw6dZwG|+B}#@%?4)W zHDl8^+D`=;zmqWye?8W|h1-u@)MiytQb!<@iUOGVZ(8W-X$YF}joA#@$%7_%U2d&7 zH@CIg%* z%0>vUkq-`pFCNLsBgmtt>@!s-f0UoW?GdZQq#|&4URWe$GDfr5s4?8+Kia9Oox(g znh$DrCJo-wq`t4ifK9hd!f*n@xo{Bd7K-PbsGK2p5zlR(n&7*>f}?y%7tLqva{P4u zti@*?dtWVQ0P>P40c_g4^`-M9>FId&o5FNCYsH%RrsDXGpow4g1rTpUpFt- z%i^NM)WGhHFI$>SM_wy*iF(E(uZ=1$>O2356RIcTeIhn$G7UT5HlfBfX>7cZ#Ilhi zXQitQt@m880RIq*TH!B;KN-^W9x7JzPxWGo31NCqhaKMi6aVFiguI%)khT?eV&GX(?99qfuQF$MeO( z+NXS?(E+M^IJZ)cVI2VSpy9i4PCi{WH#qVm^x+_4=W;>tGr<3U9aOz0#d6EKGE@ZP zgTJw&2LGOBWKp9{e~zk^nf_B9k$E1JZBSGCBy9gxYt!eo_zTsCp|_u(j8NOnygm5< zeLHCcV*lNwY+j8>UG8`DaY`?BdER%3?yoim8RDbIQg?v z{lE3BT9@}lnZKAK|A?W(vc??$y4AiQ-tH(sw4p@E^EaFf^=znlTQTna?Fl4x`e0`( z#v_I{s-%l^@WEUc3aA^G_nBFKdn74KmYl;cS_p!asw=~cac)fwGgo)*j1C@i*Kbr? zw_XuKH~(}t-QUO#;d}4widJ@{+RHlr4b@3#Kkt#JA`|`2YbJ4QC2o6Ptl`F0yk!zd z{qR+*hwtCEhF=D==U0(~W^fD)l|AV4n6P-&`|Iu2B*<-h;)NUFnqv|_ zXeJ7IF`Jj@y2H+pRJOTgX2%?PHK`3h;bTSv7t)eQts*aM`)atDPddHnhib6bj*1rvH-2Rs|>Jm8l z4i2tR3745-p?Moi3HiG>xVgFU+`1bPWDf2pCX;hHYtKRHXdNEz8$wvY)G@3$?g7`a zNvq(Fc&ftQ74e+&rumIICsF7KL`Kcr?}voAGKbs(<+fez7r$>p{J$#GzgljMB^?Jy zyOn6!nmxGAC(YsK!e^q)U1c+^otE$=(kg%bxCsbX9hnBr^P1`*+v>{yHRIlQi<66| z5l&&I?|z;Sl2@m-2d&@H+}W#3|C7lU@lfY6qc3MiM3`$SugwHB-#KBy6x+)4te-zo z-SDeQD~gEd{FGpxK~h;w{&&*oEFU=i_@0tEDtf%nKFHwUgbL}HyoO8X zm1gRr;;Ki>B53RdSEy{!L72-oEribKS*xROZ@z70ne>RZRtmspB^Mz~Acj_<^Z&R^ zdZjXuk%LkQw>i7sI~}%l)Q!LooCkZ`SK*|JHyKz`_8!9&6I){qERL zJ`inpfAjddEC_sW{EJ|P3Y15h)jt&YY-Cx(@trrX&AZlm*x4@g#UN6oJn_rK9R%C4 zk;rp)O;qg91?gLfE&-q74jGFFuP=0|N^CERP3i=1XS4SVLc?I2I9&E zCTV@^AvnYKkJK1+wNH0CRJ-j4_~VvmYyFuCrYA^x<<+w2C?@OL3#82 zKqcJYWhi_j-58U}8MZJB@6b)gd*ZThx37UvdsqTLI}yIG#J(-!HO`*V$zcyyN&U4RXPXxcgPh<;#R5p)(Nr4;C5@Fs=vGKB4M3maSQp`>vZ%{}(BbN2y*KCkZiZ5EcpK1aN z;Z2}5EyU^}gujIXwcH-B*WJq%er{iZKiiOZCr|brPPI^^zgZ&JEBH0{Sf?0y;L6OF zgn$9?ld^4;=k+A|xoSDCi^yuOAkF_)YNQE3aa4x{#u3~R?J$=(AX>d<>@O>%xm7$_ zIV+7LG3?8_^|M1h+EudKHr@lp?c1dK2v_2`g_+SU4QE64qt}nRItIS2?gLtAX|uIQ z4DjvcjAWEac*u2$@%dyO)W*R+Ej_E_;E2AubHsS$hiLjbDPBAL#YaONNq?AH5#}A# zEZI9G)nY(yve6O(!{gOjO-u;EQ|-U9lT^y*bskM!hnn}uwIJ>i#~m%w zii`Hez)fW4f)X~rQiTDah*FX@HP+i=Ji35bLpoJD|ASIP+~><0WSVYus7l@n z-pc8A-pVOSCG}M#O{=$9u|E-0vO0MZ2r#;_Jk$BvZgIrzc4NaEZrdiTR^|9u)Frr)%>S@7KW-ZQFJgbk{Ki z^P|LK$~~dQf`tKOL%HTv8|$b7l#p$@ZENg*W70ZT}QX?(&BCd(Y zpI_7kX!EzSMc_nVQKu|Fh^ zN&z6v>x)#6;8n?v0KPw8=f|U;Cu!BqfkzREn(D4DK0V#ZQw>U*ZNgHu_+HGhypEh~ z42`H0HZ95O8HzW{VQmON*lvfkw18%m==IzWvk^ud0=J_^6RUX-M*&cbI5P0uUIWaX zt8KEAGb>s_wrU0Z@r zkS7JXU@P_~K^Eo}MW06f7F>B&vaC7+xkSA*VopYF?8$WG*M4YK|1uvM%gkn{gHXW| zX_u^>><#@$c#T)3@bH)5Y-<(ggTm8B2*$pQT<4YzjE(&52=g&6wWFI=R@(7q#k;;l zRITh!1sG%rJ#VM-SdY!L7k@L61OIAIz`Q)JMw1WeuBP*0_|!%{;77?&)C^`HVy}yE z4(IG1%^ESjzU@)cpjvQb3RhiAFw0xhNH)y}O|^qMI_&3l04isnhwUfN1jh26M6-``F)_jBe)Ux5yQm2tuPXC$T`1n5;5#H9KR_Sk0*3n3^$2v-7jwgi z?~35jYSH-tkC$|1f9aDH2T*^{iw;%3(HqQnI=>S6xO_f9(Rb(-%j3zEeTb?|r@B$E z=zIS8gzBe7-_~2lAgzoOgM|c52J07ba3(5yY2wbEdhAR68zJdE$1O9(-~euyiC>X* z^=G=9qSrxSufy!D8qxqr&*yQu#~4`NxjL>I6MaL)Td?3#&5;Hva4&>4ifnG0E#go1 zIf}^;@__0bR&cc0LFGzD!ZiA-(W7a0{WNNav__r^W%wR$bXD#_#Rama0k!V-@DlPw zUD+u^rHkt{HQz9sE%WhPQ=Nj{?=(IZWs7B$5mUtBj<a!|fvfdGQ-mF(8eq(;*k) zkxOO-PXz?itn6(`=2{7Tw}G)e7Grl_-*3MmR|Q7?mhNBBSXdIEkxS5(ejMeOE8u){ z?e-`YEa*kF0=kwWM`G3eH#z!h?)F7{sbPHD(P+~kn`HA_%-~#xQkWX=_JyEq-I9ya z9v^6*`Y5kl6&a|4=w1motkuBBB8&pyinV1WWdw3~l_LtW`)>1h!vWPwrB^vqLTlBNu7g zG-?zbT690V9roYhF_T-asb{=DNJa%tjFRsJTZAY_eR9#cm8-OE&TGc=eAkQO>%B*} zze9IV%Mlhnx^O?SJu*`mXPNBh8p;QxH-%T;q7NfcSbgMUt#d^RqteSFM2B!kK?Kk! z%lLmB+=n|9j^n`bN>QIUAsh}#C7EZQofOGRvbPi2&N=gt$Ova=Qz2v?*?ZmD+u8GQ zHiyHRzvuh=H{Q?ldiR`(+X^lORX)aWF1nk^C_6Abnv%FjG*LQ*7zrX%v>;CyyS0mp zlti9wu_j4xNh|*)rn>~Iaw4$bi=^>C!}ng$PYl_FRaqb&9Dfby`4(Mlf#ls#lTnoY zLMxOheNnBIdD<@|RC%Z4NvYD1Z-1 zQ1sGvi-sfx{a}l$ZQT)-Sl|D)Q`fU2)r2H_l#^wv9YxWGp`>hQ--sT_b3ZeR9U~S0 zn2Bx>hb{%M>a4(68WV8q9(?qbpXK|(I@H;_aOKk620;WhTde_upIsh6;cc{bBW)KU z(x*(qJr*rKUn3{49k2b~Up3}wrb~nB_)I5Q8Q!X_KJh)*&6?Xx!J&Po?d<;dfjJv9 zdE1?0W8o0QTeq`RPLHNW2Ows}!9_vMv`e7rK)cL@pqVjOoq;q4$@|$*CSA5H@7Tze z{Lq>It^Cr(R#%7g)sPn4e0fy&rTZe?IIC4E=}Mm>Kq@nXC-iXd@?#{ zUt%o9)HBs0)zl_?Mim2a>;(NIF9F~q)j@7UXb&F@xAVc_;SUw{>HPsZ|CQ|9-&S71 z1r^qg6m6Y0AFVgat1L?gVVaJCCKY3nP_Y@vrkTQLR;?@tMM^#)zc-xZg(5MmPf1aA zyQ?cD%IlKgJ0-+o87IU@vsZI;joh>HcAM!OU)^i`S98%EuHvTD@Nzz!VRa$0#Oj3b z2`F^if3tvULNfy00i1y+49?~k?mpe=s70;yC0}}dTPmX!Nf-)?^6|=j{KJ7RwW-_W z(afvvynNeawEt_CY47sbj3+Mbw?j27VLp_J3X_k%2lu1hfa1KBsh8n<-s1PYN^<-D+=v z%d98S{9Q5%kAk=_jvds-Z{y6ix4Ur<<#)jR~k-sXPv7|63pd6*gyM(eKE?x}a zKE%VUEMhl3UxlB=XRqu`NZ>5%b8Ti_V-c0(IZc6m85698Ir`IFy{w1ZmTSjMHN=*4 z!IAcAq)hfAcl_ky+x78AFXuwjd1yd@yT|j3Wnf`+TPx34wvXXQ=zJp&H=xG zeWyChv*M?7)iJUt@2>2U-)qZ^71QMw6RhPs1x9kyGgPv2MkN;a<9#{KEPnl1$EF(5jKs_mTSp%=nX2>1xylToh`Nb1hh6Q8tX@$5IOOmZrzOOm z#x+uec|S2K?ab9sR5EwlpTozQx|CP>xqyB$tYq?dasokw@FWX~B<6`b6rJ+{nrZR? z;ml$Z3L9wy`d+)U;StTnT1^uA#GU;oc~c3M5=X0wALaf#Jf0L@P~gYyoy`_z8Dx6z ze7_DD7rJM8$3;eNe=9oca+r!k{`A_>f0fobwBHk3*nY{?OjWu$%(58A>Q@}3CFJRA z%5>q+dep)Yp?}|^-5KO@sD0~RfzK|k&P=$dy00{%WiRjdilXuZ@ayUH{6Uo)esS$} zEZir_(s!o9ye_{-p&yZ-ks1ad>xG;U$^D-)@pCSTC2GI@t+c;Xxk^*DZRY!&!T`AY zk260Y6ya#hS69LJsr5D1F*2oV(G-5<(_jCjDz@;X_QR*L!UKx7e*plMar?R$R`<`B3s8LMflXrj34OH z!HFZ5^urD>LU!#JU6U<0{SfI+K^S?Ae2=ZF*?M*j1vzA5PD<;)pJqv>jUJpmzLpL< zDWtj{$n%$I!NQs?TV43b(%!}!#++X3>$yqMwDYz}6$^?O0U`%n+%LExm&riy1Xd1OW0eUy-^ zopGM}jBHQJVj3+00h0inD2ux89jx>;w!5y*Yay2zZuqO#LHf?B=;u?f;_-y}6WRfV z>-XC@PfSq5gYyF^_gmZa`zdr!1s|%)p007@U!Ok9_a7^pkctc6ZqjyMdFe9=z|H<0 zPOgR7rwZZI;a{^@46+Yj-Nzw^MwfpA6rkVBJup9^e&La5s~w5uR|EWj^ZFmdMhtzG zDWmhugs^wNw$eAgI<=FzmDx=yAkE!&lGiAd^r%sOwh^$Hsdlhc{ON>rU4v+r$4U1B zlr=wa+G_2mAaR5&C$YZNUJdHPwY#vB|4jkAIS#!_jIv90;>bm-sa=ZxDHGB@kCq#4 zmJFN6%bfY$$%zuA60 zc++Kh_(92L;Lc6s{e~Hc-gYpw;v>OIX6@~a6tbkIfR%uF=rRA#OvGN@4|<`lark83 zKGFNMraf~&XYktqgL4bmKT;KyR^jxsBWHlEW>QDNdOYeWH)S)B?aTi0&SnQBTKxWm z&;-g0vnRBo$2n!-nvQ1==+qm4Y-wJQdRyK^_q$0shIrEOBn6x-uN*WVY?fZOSDXN@ zSNcRFY*9y#7UaLDEXbez?=aEPyd2*A3#&(yCG-rqT;Qw~rK^RF+9?ReA&d{+x=g-* zE~RdVcT%l}JWSN`Vd-#K$7orHvEsi(XOF#$bZf~EBvTZvb`tUmkw9%m_h%}_iD50EtXG?Kb8c^PXx`su(Ou^xY4O_02BCW zqGrtYS{}f$m8u5V`lg|$aT>0|?{wzqyIba?iTi2B^00i21N?eaM=$eGoH7@6MswfN z6wS^~zd5GzLImo!^XREcJI$Nxw7nNNgj1D0 zXh|0O;g-^mdAG*_%%iaUP;MeHb2K`riGC1xS1u;R?U7%J`$NjH0~Pw5=||nH@kLeW zS4*%cu+4)f{_Mi2B_b=sU~z&$^#$anJ2CwpC#^=+Ep$lB!9SZ259KSQxVqmmsU~vzCf=63yiV*q zD7~6*_Bmysy*vn@-zyF!Ew9inZfu$wst4Kd z;2i)(>0Q{!7?($pyX6uE9mCCrxYbX&5O)F{zBb}DKYAoqM>s!iF2+HJFxv%VhxjzI z&g?8EgT(1szLIL>-(Ec^0BAxGpE4;vTurx5O-xLKAK^hk?kE%^-&aTPjSkkoVoV*_ zUaX)9rO@)ACUXJ4b3fk<5?Q}a_bb@3o59eWVc{qdLJ+ixPNZ8!7V z1!*#(TDH|agn0`Uj8aPAB^^0}^_%(e>89r=eNsR!Vv^9DHDZF=** zJ2%r{mhX94L z1bq&Dq2LbkMM;FB%_XnBzC5Wf#pyDSIoaFcC%H#d;0d*IldIo25w)!kD0tHkmVRV; zEOXK2DBwX;yzfVtvd`m+rFh|#6QZceq1LuEh9nBopIPPJL=LJ0&El~XHXl%jrIG|P zg8SosP9LmKomw=U{zeWQ+%_dxN6@88RyPI5x;=OQ=_vkOTg5xO_SY}Xff-;`twq5E zVp+-rqUbW&)G_?CA3;& zBhA+L!~2`Y>}uDAAKwBYVIszJ&-{3AGWb0FNImmixQmg}z$cl?*kqG2i*SV3!w#Xv z7r^JjwE4wRu$Z-!G}nn-GYFJ@fLD1_+=C}P$a*Y z4dn1+7yH!S^kH<^9kv;(rLe5&#FU|^4P;9mfrg!_Y;#05%YFihq@>dD5SGsD5SxSN z&XvVjyYube`IPiHyv|M^lxtNylh(1SHnn+&N#yY9_*0U>J9o3~`S<2ECD|NxqL$!L zd0QP6_zYCsrKd6nbjpnH&k16~l2bSwJAMzi!QFThfT004FxMk^VcIUaUhDGsdUK;1 zUjZ?9aY<>y&$~$1AwT&pMTyR&SrzPkC*jo^VM}A$P8)C)lQ?`}m|+$F45Dko3ilM{_4aebFb^FEhzGIFIA?G<! zhYVF+C>gep!uy2Y=I~b6w*s56^>RV8L1PQzpjC)6Bnjl$gLH0)fLPx5SYp z)6WPXNBUQz3etQH8^Y1}lr)N*$W&cYYM%7p+@(5bz&d&IqiWUuT3M|#^c4nb$p8Rr|NWe4{qdYE#eE$swy1tAW1 z;|W$PIvP+z9`vv~qXMqf1bfrj0B;{DU({U(iLC!!;d|0eTs1a7yB}~mo2`G^9elb37foF@3bV!EQ@8B0t!WICY8-Kqi$x^252KBLnJ@+buugS zKxobx+hfL%jUlA(sww70mGdxB1YcDY%Hhf`!|RrK`T~9+B_4_id+VXb*e+Eg(Zz8x z)NgcnFd;KvBZ5);(qv-3K2RYyKidI8`S~rTg0RwCacOI}(Cd|Tzkb>l+L z$zyyTV=0#0pVJiIP2-$wkt(5)e_N*XFZ!RgYnnu`#b5o=vWmikKSnmcpDAN6)#$kT zqI3e{(#^xGM0%zQn6aQ)lq03BJ7*|uA{R;|7pfqJ*N5$^5ww|pYXoQ03ry)O&}FJQ zUr^A-C*r6t!2MVOxk|pjMe24+vm_5dy!Sfp(Qn;*=DXjjxEZK;Eclk$XfPu0#me2J zDnFTRMPss)KtHX9{Mb^_4+vxAVF4guzN6v6E0rRgntBC`mRyK~7%lYg2C6_sDFOm@ z0%n2Ny3C_%@{OY~;AKip0kD_JQc97wi)DMc!o;xhfUzvtb?RdlIODwT4{?{mrmw^p zJtAjb%H~njUrQ4db^po>^hh?7?oVfhymjjrVuDUv7TiFe*@_9ZRi$Z{j&y=@0X}$a z9k3Eiy8k-q^&yNVm}Z|qQ?jH9HyN_Y_Joz%ooxB!doG;?Hiy_Rn#<|mhX~@gQ65HefSW0i zZ#U)gI+nW(A_%E9fQ=6E?%S&`H5%UyOgO$rZ@e=Oxj+rREzJ@eH{|wfHA~!1V6p9! zu)|1z6`%}$VpIt7Cm!*+D~qdQo~Fw(BiYNNQ6+cS#pBxIg;eV6aoEBVl#zG9B?=^w|S0Q9fW$62^r{SNq<1$!N;wTDebI6bmWco>VqW}C zM8~nMoZmPFNu0Uq;{y8cy+~xsqE=x#$iI#!nl8KL(tb!~*XNcB75)4F?!*%io>ypvP*O1gt4wsM5 zbA36q-%TDp9qkXYuK1OwZu4pEpSn%tt1C>$x5ZzZIw|0~?{ge%+X)45nob4(^x8~% zX5)teQT3STdc2p`cd<&ZguI>PbkyT}JIRLx7VkpS*)&bB#ZCoyuZ(l;&V_sa^Rg> zrGKw!-kv?0q6?u`DaIn`c{Uo>*et(tm(;dezLcPTQdM}w)3l0RK~}c6uzCgA3p}#0 zmm8P>D*4OawK6E}@Y3F12ESaNg~TujIxV(AX?o&aW?Q9fjOn;WqbqX&A{dDQzE9HI zRH|5`H~GCn4NUw4N$zUzt-P8>P*AM8OpOsY#D8DtxJQ7^l=8L=eOG?}i{vbaFsX99 zz5LmD6mayY$fTqc*M*$KKtguIrQ~*jTZ(R%-)r@< z80Pd#^AxsT&2`Y@Wi4kDE1hY@Uolr*Y&9q3XC)W4P6}UK_;{~rl~$$g@LU%M?DKH? ztaDuyHq+I39Ekh$yYC4|@(&6Pbk5cZ__dz&&X0hj?IZrCV_sJE|JhBdo*t)-6H=*( z5ND}Jrcyl+5xz7LHd z{U#S$A28KERXWR#`-O}U?Qj=zk+B;+R%$xkOy4Td^0i90Sst|gAk|gWYR4ofmgNwP zu93Q|VNrB87^pPK4frTTXnod#{Y-uEEi6%WkAvEvyk0>&IU3=%g0}^M?GG3~_EE5& z3;#Ku72dHzWFj7F8lj1&Tv)U6ETwZ}YAK?u8m%Imw(2o%oB6vC5dLOC(M9Z$rP{4* z#El4xPh@S{+I?PHDHg3FM_15uEO?+>+O%9%!=(LvU6bS2a3$fxm)jh=w6N|rq~j2` zXRro<9oB!(95i&t#?Rf6o#KITx8b1I0E0+st%f^7r8ge$xp0QsJ5$R#Pi(HLBmUwv zP#~|wr??{8-~s(dN(x6xy}eWsGn#rc=iK7+KLiImG`?I5@cf3Lgfz&oSB;*!%I zqucV>kEJ4{8oF5H#=T^k?fm3*TAW5Rc9ki(-(|`BLY9a?n_P&fa`I3puK4i8FD9i=$5s z(fgg9hE8Jy$72GGj{Za~z%;#PveIxI)M*S@d&?mQ5LRi$gjpHtz(1GJ^LSbK&#<@Ip6S4x`)x-=NCEh_e#rn0VhzsAXFg_)J6|6ZNTZRQMllc0~QBdKPD? z6Jj%bCO7Nap<3Z=JzKtD;GPQtUbo zcYmEf2C}h9RxfkQG`mF?D4#V*eXaOG7?P&n@^)```|N-IAFgI`I0?bW)}C%Zk;>be zHfMAh(R9bw#`*4M_~CVdaN7?gR@*mHnFyXp@7>iE2_5|8^P zJ^|h;t^Fisa+vKbya~q_9zK{tf?|1)>u3y$29vrmim!-PmvO-2;i%F#VRTg%<>%`e z-RecKURuR0X<1=MHLSG18NEVO3fr?TpSSW)dQiV2zeo$mEYm3E9YlGiN3(f^cLNkN zPet=YHmOj}j!{{be#A@Tq(y~m5O7S?{dxtAE=vmw+8Cc?8nIwAy?ylS= zG@#nWr;#Od7wlM{GV-k(sMC2(ezMWUP@Y2(x(7Atq3Je>B!<)2qQiT4dpZ}&Ax)=B zw6gmx%M4kk9d3oD2Y(+XmS|oXmjwaI4;g>ZY>rGP{~_8dF&i`NSS(x2X`ks7!WuVE z3>4n!v8#|B?@U}k#L`B<-6gp+>aD|XiaDjGL7#G>#jO_Wp(E>weaTM78iYUb5D`b& zsIcX5t!6kqGu6@vC0s{MRqO&$5-ZOlwO0V5U~CJ^T1h#Rt%6VSpS_VB4z6m_w_?x>%9+uP+7`H+H|A*<*+zn zv0nJMC^Q?}dBd|0osX3Rro-tnjlM^w6$ZTSlyI((bejzc3FYwlOk9cAG8>hA`6tnK z1Il?mIedcbExq6w)}c2f9Dy$>LTOe zb*Dud>5CfYeACik$s#l>6}WmM%?ooYE!jpC(6ELvj?mWpvSF(^z@|y42C}UK`$8#G z5WKOR<>M9R21~0Hq&hmRB4 zeaZJ(nzPI2@ibI);4Hn!K&L(Ut57Q|cN_G9;xf3i{pY{v)*v3ZPL+AIDcHnFr~n_)KTx^)m@%L=alk^55(Ew zQal7F>8K-JJ7*;+*^r;5or(B?`_yr78y7pKn-ZDn&I!s9Cuh6J<@d;XP$*rrRZ(#& z>}~Vrv`XLSFE5%zkv=j2rVk`{$s=ALkc$TpraEL z)uosFxne4jWC1FLf_Rbnoc#W|15l9jz1;Kgr%I8~!`DBPlUI&NSVn6(C~Uoa>*xUm0s`mZZS3=@8h3!bb{6(BbT_;gUHs41OJG zb~)C~tL!+r?*D~hesZW?kstn#X(D*rr$n^OtK8o7RD1<4=Rw&o0T4l)8u`aU#Ig(} z@&~#1YO3BFz>@-3V_&LbUjH}_N*I?rf6~SN!!IAZ``RWVXeGj+=F7(9_ii6Z082Ji z)s&Yz&Ch+bX6qz}r%89&aFm#W^xx!o5=e<>*{dASNYRaY1Fyx?>(yDUe|9syqVQP^ z)9;gpPtw(zl@r|h;6oVJ;Mm~OF_>gxzW5yQpLj&YytT19ca%b%Nt-WKl@Z5B7vhqk0{1if0; z;H=sB{`OA*r|oWwi_>jR^IBhD@`zk7zSoxG%bWCfdA9@`4)(v9*N|s@udk+deDN6@ zJK*7+SbDkNnHK|R9TCk1_izl_`0tgFm5v1~Ct!RX#}kCblplnL#KejA)77L}D`x`Y zV(<7}B%giOYlnWMKKNAcA5mTDQJ|?)wJ$D@lQNN6@rry8nm)C%Fv~1l5xGcT{GF@b z++Y>m)KrICOvkbnAoVQ?Ryk3r2GItjMvg(}nPfq=K1sSP6)!O@5-I)Xzp1eI=6|Sd ziqo^n&m2vNZ(KymyVLg1CBvUO^Aq@~jhjvtozu8+@mI%bs_&-yW>%m;ax7Iq*mC@hf0oiR=Ai5$g_LfiIyHf>dXl=>8Z|T4R=Rn zI9gr^`Z>ERXvR9cfD77g2aT^e*Ty%@-VLkDG+{<{VhS6OAzqRG_RY`zmRgQ zj^dkwztx&LxX*+rOveOe2&qQ}7VRx*o1VW?_lwGIn&>w9ydfF7;9&2-9X&V)fWp=M z(me%C)~adQm=G}}mg5Glq?vx1Wr5Ah(6k*XB#+>&=QT;1G#nWUvH_p`+{_)_PCBhZ zygCU1QU&_gUSS$mZUQ0Pe%1iLWg5CDMdhsx=8cegu~IS3YZ0UUMjK#-qeiBUEg zy<~|Xz8U>geR}*k&@n#d(9m!>rVqw4x<4pEGXjZ2i#GJlT(rtTJ{ zAi-bR$D>o3U3MPX2g>DWBqxgn^R0(a4VYz68Md`2c|=(Hm5f0vtDrQaI2>3RQ4kS* zsDazoYsS+#lJ-6hB{~-F11u=+8sLOkRX$xwN6y=`CI6N zHyk2|wUk$1xryoA53!FV)7U~tYpBl6^*x$Uo@Uq#pE)pO@7y%$Ep^G+1gVs{^iEBq;VR7*Cd*?dNx)<_Ie zJ{;wiK|uXFf{#b_HxZ?dG~S>)nXCb4>v~qph83mQvygTTT2MBoK=c`ZrZ08OKas~A zp&iHMvoBOJLxf49$a=WJ&R7G}>X^10Lod?jk%%58(mLdq4!190hJEHT&NZ*kGJQbi zuFv1sK<;|{s3s?s6}N(WzF{8v9&pt=oCW6<{g~GFwD9BT-~EO7M$4`}efCZjxWQe9 z(5oydyJ|5bqE?~HROc}%N-Y1`SIBQp={Z+KWU;dibQ@JI8+dxrJ$`n`E+lQ0|Eo04 zeeD|aWzxr2a2q~DT8`(*^0z_zosHTGUZMi<6$&IBg*zR9{yn6Yyf|`{-IJE@>V#ig z45y-sV@NGgKG2iGq-roqYN33CIYMh2{sx8JIaL0aA^!dLR8^={!hSNW4|r*(BmK;n zgItGYRW!{DaMa3nVox_#4*vmlwc1XUXDNxl%_*sPhIh*+|2Uq|h!Ur71p*QOZeo6s zHEk_h$9bB~8uHafzchC|VC2JcOcC3StZ*s`;-f;*$QCbAo_@=GM0a*hx?TJ2Ru7v0 zu>8M79%Te48&rzux#F?HPtSu{#wULrP58+8#E~MSaW5IE-uKw-abZ;u&$f-YSu}=U zc;vTt-ny=|koLT*-wkNQQz&>HY6;1o7bh{zoCq?7F8nhDHK<)PGx}tD9aM9*TH=*- z*AvRPQU2=8s#XO&@=uX1T+wsdYo1k6PyY;Dudvo79}1g;h24vv9=!VgO|+V|g@DWt zS(+}=wl84#S}H6T*QTT*M`q_{ZdV{Ui>c3K@iO$yq$svA55Frf-PBh9D1K zRf{#nm#xi#9zf%#b^gZo9DOR`kA0fIiS2d(}r%}!J2hl_mWe(UcdkzEwlsoOdmCX*$8W z$ZzMg*b|M4CY03qe>cdc4r*V$yC4wvRXw|uFUQUh!J z$#gjTp24ABdyQ1W2nTgk%Qn~sd{kQ)L$f9JndJ+3*3`6nR0m)lQ z)NC(yXk|94=%7`ZJ)ECB}>9XpS{UNjkHtVlM7ZA+k94ot(-M=xquVj{dFX&FTj_)q$BR$Ruj>GFuM*5THIa|XEPDFYwNF>#H3 z3m@+?XCm)UIMkAbxt{PS6bz0&3%0d?eOkQXy0+NoAwEmzQZ3kVN37!;XA+&@1JfrQ z5X;fPYN6*d=z4FCHoLcJRtPT3qgo8CC^CBvDPGc0i?E7>_H1RS6*JTke&qt;-a%4s zB)WT@oA9wIFO&ODB97qwl}PWQkYMowEkT$tht5f$k7FpDBNo}GWokVK4;o1# z{~b1z_`IEEJ9ekrLqI`toabG)#(&!emZyBErhj>L-3Soe?|q8Wpl&&sW(%oi<;)tg z!;U7K8kmx|5B5iLS7$jFwAkAjv}Ea;NGT*MU)zSc#v$eyhGekz(%E0$qV76Z4C}a= zRQIc|CP5)SR{JId? z<2{V0mG`e@QOQ_Glt<7?2>$s5PaJ1j&_#A2O`AU^n3fyAQ2;LQN*;v(WYhynhbVU< zeML8wyvUD#3_6{t#sTB!FIlq3$iL^yGQ8)s2jOO$iofeEuEdx zQ_WbOib#T6>;b1-qJ3UTB9V!_A2l8mb9+V9RteMo_G&*<5C}w|L#St)>U81#i#Z#q-WS;Vk?%z#LR?R(uJwLo%rq) zfc#wI5`W{b{+q^4H6wk;?=zSCO;9w``5(?c&-xy}ZE4+9zCV~%&VVj?Y^Jf4fhcOE zJ|L1E=D{F`JJc%exPh<9RUk#($+6w;FL6wqYyoZH_Pn)6ud)S82wNX>Q-msZWq_yM7aZPd)tk4X)e zm)cp>9^6B)W!50q7cwFDxom5eUNRK%_YC}*GdRiNfdAWlfhdW89s@r3q{qS1f3bP^ zr5W#IU35bJfHTV1XtZ+l4IX!%d^Fh2M$zT3Gc;N@r^5Hpn}^$_Y`BXx`vTN|>(7eM z)=eo4jnmh5;SiT7(WdA(zkIhl`ju59ey#+QkR5ei9IZ!xNw`1gym%gJ--n4?ul|Ei=VFF;3$4H6F$ zF%h(xKlZS^>?+e?RBQ`iU*+XOx#HeH8Uac1DE~~-1s8{#$cewl)ue)vZ;BTB=se_` zQzL98uk^Bw+J9ji^ZqNylln=<;RtPm{k>l`{!2?nWlB$epK}F05Ut>TSK@ud#gjbu zfmjstEa&s(GvAtp9GVQ2!Cn z4_3gO&Fm~vUfuP91}&ffo0*XOI^)&%lfELI&A)mW(sJb#(5^jTb!;+`Fic(4Vg~V{e4ko6a@P@w;f}ebf9L zLcTK{7Ek0Hxm!4J55(+mwg0ZlF*zs%meiTMKRzH8W0ws;vo=Z;jVLZFJBTwVHG`Ac zyv;<{I?0OE*)v|bpW*++-FLS>fV{_+3c>Ga&+AJ_IVdSlxBlJrR=W+tNcm42XdeNmrVdd|4R}MVUSY76 z(N|m!=o0!@t|Wdd^=!t+SAO11(dRNUFWNlfWu&|Xw*nF=Ls@`&13qI+8l(qBXMpiv z1A7O;%quJePCMFoHIJdUBy!->%-@K2Opz=}$$eOTndu3c1ephfiw<4><`zEn!YQlP zi)EJ%TpKm7VY5XjX&E=3tG3{`f99~)dR>DcWO!2k)Uig?x!P6Ea&Tg+!b}D6)qsUYt zlpqBE@=@W4DV}o$h*y1eLyO!0AF&{0*)nYPXDL~ci=`jL{?I}G9eKShGY&f>z?<+q z&3-y(HyfKAS&dCDmtT9AGUr-Mxg(g-+zbGO$BsMFyH|Ak94cype-w;OCMYA0=~9zA zmrLq*S$FKX@v9XxRA%zBr^h5K@=JG_&{n?76o$BQ#@g({$oo{L3_S%h-^+L7lnqR* z<1mjV#64=E<5T%4A5>X|=xF-`41$FRdf6C)yMf|%e!E_4L+-5P!$%ogMM5E=M4*@M z{cwHBjqh9G8{TESflB;O9l*=bli2XWs~5izod4{%&~NphKzCq#A^M&J49vQNg~_1# zzA~=d(!tmupy+mw9PXe^;cs9v+Uf4#Kp3@53IW-+zjLbF&aOj>-BH`vb>InczBS^g zAm#r&TTq&bvhQ}*xrA~oA zMd=lGD0_5FnW|2(`V>vE@JKj%cyf71-x|fyf~=Bd{C`&{?Hm)#wmJo7)UXTaS~qg* z;AK=Zx^5pGZ5jXh;a$3>#Vv)Btn6ePp{4S)_-6(y5Xc9*(VBzS31vi+pW(5&Q`Aqt z$rlArhs@?9s|q==eUA(YQ7Af_BsqXRc=N#1vD%wD53IumM3s@HSOYBhUhoOtb(W6& zhirxCl={Bneu?najLyImRb)HR{4Dbt6QZq)uBkyraStbfp%H@g%73B9iJ*0{;yf`) zE}>zIi{d{qzcMab-8mlW%iBxYqS8nG!v@07dd!_#t)~ar5VkBlk6g9%hMYP z!5QMoZJ0_r_Y=7j{@vzl|10Ak?zhFCey%A8?#j+Pa)wFf6P1}tQ?1&pREjg+Lowij zoE>fdwb+O(o*4gLDeCPaHi!pvxK7d&rU8R722Cd6UxzC#?@ZQwg|R-FZwK%98*KG^ zWPV!l0pfpQfA}}NP#SE|E44fB9>qhaw8we0D$svSnEfDM(AmJ*vf{tg}bSLCfhAi9BU@T@-Ik&gh~PY?+Izp!LHPAJE58;Dnk261NeW5P}CV2=Q{}V;@Ojp99KGp@t5e zgUi-aLgiw**C4@_&wdQ&38Ze#Hq5J{ZXc{9WfdJ#wcnihvkx5ff+;QODZ*ewbOMPt zcJvi?td<0%?bO~+G7GEDa{-N+i!HvpO(QwyK3X>CI7qbsa?u~$dzfm8Y9TYeyARKC zKpeecU3hHG8SapIr2Tzne>NQZ;2V>@3Izqf0Zb*Qd!oU_mZZkU9aXG7g_AMEqPMNn z$p82YC5Ry-MYmrbrI~9_{VWhL)pJ(<2M-oBhFQXcIeu@nh;ce@TFsn80MK*?bd{ul z5zC;ZI(1iwornAV4V@_Z2*TYgd^hE{A3*c07*fRa@3>J{`rq^woNiQwm9Zcf$U3SX zmE+IHXKXqx3y&(8nM=5*^O7mk>ZL#?T$0~*G&S&>d|R${28P}Ea&xj{oOb2yEtTyu zYWe>zQq;1pj>r*NK4+j~kw=Qti;vG{`z3{{d$VV7?VDHq%Zn0?iS))NLihJu8=L2s zYNnd(il%j%5i@Yu8Tq@s4aSw>ql_OTl*6(M!i+b-7uBD4;tGmcGe^Bq;>g(YB2N1B&D>2c(mUFf696RZ6 zirLlAj=>>^-H?e|o0w%BXls&ab1yEdeE<2pwd+jsc3Mvtf7*m#i7-sDWh||((=lk| zSrCX8V+Ud>?0jfr|&vSqmhntM- z5AWi3K4VvygPsIZ2^OWtZ~fkUp<8IZH>tI-E^C;z{8VLf=-gGETKDZb4VPTarAXj| z_1aqP(B8AOPpkGNHDas(2MIv-zer!!<*ez?%a=AHge5kIC_0FgqL6u=(aLM2#aXEM z+Vho?xa8&w8sTT+X=_~>GZwkDflIyN;E;r;+Tdxo!wx7^VRAnE$-M*foW`^@v^aj; zflaaUxmM$FJKoN1;f~8(%V1%U6O4Ist}!ko{bc-0@2kybdvx;J8#kO>KEAR33%|TV z9{SE351zmO%xb$_-m+aD+_haSZ^6Z*3rHQCp!003@L+X%)7Tt$%mwfUv*5{W`adXep4Pk6>-h5{JH3tSX_@vq2;~2HhG=@>@$*%a>E~t&fcrXvc zHTu9gpp1IX@;ULwCh{7MnhanI`%ArEqjM>_21le-_8R$vdQu_=9fad96Gx6C@ySgG z6@OC0j&LB`Vh>QCE4baPU33L;h-#t`61vNVZ)f((&bNY=-7xlru(tk!G@l(#A?`QMl5fGy$O%`&!ot#SWnQfcWuT|#boRTr4nDA1kIl?z?WPR!{RQ!47c4)bb7HNehX~93) z`rrpYNSzzp(+@mw%l~(=ym`A_-4WeebZ`e3@P9D$&)Up!@$k#JV0>pz|4)OuJmVn) zM-D&Vc4C^!s!{)BtLF$`bA*+&UOPm7Wu)2}hmU>O(B~i)wegCOG4&?S?Q7vHPwK+p z2`$*XamRnkg^}?l<>ljo-S8TYQ*vF1I?r`HLXRiB=6kex+Av0PC(g%$;hNI>=XEhs zXKv10bsQ0GZ(D0FM)U9CGFsg_XIZlWsTO^p4) z{DLmdRh&nD$+qKJn_t?kIq7r0vA{GAzt>pVk13d60_GQc!+#P!|G5_RlEe7+#>LC8 zKeTw|;(PHYTR{QBl@N3;E~(AMOLX8srO?#A8qMKxfvEOwG_L7AG#}`-#$!qHEM z;>|Ap7E8U_+{_Z&xcEsS6tm0?Ob*(@!Rs9Ot(#@1x7!(0?Tm(CwT#i#VksH7J|^d3 zn1s!mwi73^1Z8ewcIIw5BFzIJ#2f}O%YJW>b@ob_bvl68JbVS_1sI%p z?#>VuzUG$sP#-&b=G3`%m^iTV<}l+qV28{+>gk(~DIQj{!93ZB$?Kz};@v-vTW5s0 z8ci(^&CVmJYXzUOTC{VWBR|#j%SgX{ag2j~8tC$hZ9UjTz4-TY)(0z-x(xfL&$$Q@ z4C=^{mcGL1Uhur}x=7uA=8bWrCJTe$C#Tusz{`($+znEaGRIiQ(5WXE7O@ za&$DcV{3Za%}2lHf{pskUt;9+sg`X>V=~7!aq{(?^cx4sF0AOm`qM4=CtTLi8RsQ= zy*+bs`Q)z6l{G(ud-TyqBSE`Gocg1)mlp?T;z>V@i|HUXr0^r&nHNM#TyQ!{>f)N< z>V?yIdZBi_^-^_&t9)9lvWBbe#;UYFvhy*Fd_`*LF=5PM=49m-o1Cxe_hXRl$J5U} zhOu#w4cTHK=^Pa!t@$`o@1M0Ldrb6J+qkwyjI2GwqR@qkpU9(MKH}iFCv$p!$2Lh- z9#E?X=^O^rM@pHt>OkL>EvI06Z8)r_O`7VQK}Y^5?%IwvrkjUCKotxb!Z^u}0a>?w z4TOyfcuR_s-~8!=J;Dal1_{^JWNgEy&tyL~x8^T-pwzdE;UKP_^NY>q{4>uyGd%Uw zQz*#0Gd9lEt*@VKZ;kiXXQJ3LIJQJwq+-E3cRy-`%nnQ4lQ6pstRGz1Ty*-Gj|l1t zC+s*^IgWWar?1I3*iK%Yi0bc;0qnJiiL>NUa+l+X9O&)BHeTAOp4=fTPEJ_~Xj-*H z=M&rU(leIQ8mM|~TS^N()?(Z1s@$UZAr|hCehgJFws`uSYA7#a!Es1zBGM*N@6VH- zGf>7!%o4Wwh39kbT0x4Z&6`7ddk#q<83U(3ZFD-Ukui2Y86%e6Mz1HVIb0y;B-Xj= zb~0ve-HL2}70G$Vhg7{jzhustF|8GtTn{kLY4~C%k2|;1S!)q?wi8R{AJ_-Z80JA| zZe{y`v&@|A`jU^f$Lrg-$D7;n_o)7JttI}*KIFOe!Rl_l8N4A%Jn?+|P2Znib1S>9F;i*wn6t<8E7~#g znCSt&Pm05x5FwnvvbSs;v8T}!}3EP`p~dhZ_llRhYkM6 zXAKn=>(vO9{zhYe%LC`+?97;786@=BA3Vb1C^@3h1B0VuX3!+~bukAyKZu^W3ni#| z3w}y;_A#c}x@YxSr@=K6NlR5~`=LBvvje^=628R}-xoPqow;SRI=pqU zTphaeYG13k1~v|IPM@-4Vt?&pKacU^In11I>L1%U@$g|xF^+Zr)Yovl*$@-Eo;BR0 zJzueo8{5X|2A0pYP^S*W&0`;^OXhQ<$TeBk!8J-^KnJS&<}jl{ziRidjetF`*m?Xq z3T*gefGS1|ay;4YRD`D{<_`|abFQTrl>U|vnqo}0rxH|aC;28Mg&f)O+*aFq^A)|y z$}JXz8{IM(&%q9!{=|VwjDG03nbep#;`Ar3a`?xa8nCcax~AW9x|W%_j=?Um5zf;2 zna7XC;t_8a2g~*5=H+^O^S#eMKRocl3%r4rPdxENpss~>VepJjTF}(qNBdBtYb|N6 zCVcSLi__XniCH>rtvj(W;gPq;taZ6RMry{$JEE31IT_Oo*gOvu zM=Eq`=~Jv@9Fs;#ok1}y`{8yx{c@(iu*n#1j@B8#PY%YF16x0`X;hgwn!&0mm#EaM zm^5%4)qt=)BH=s6a)J$89?VIl5zV>Uy_n=xsLi>94;#LTTaSH;ouN4lef#_q3g7g6 zT=U%fXix6)vBw@;;6HgjSqyh<7YAqhmv^xN`U|pQ`xglO*KZGE*B*b-w=-U?r8`%z zD5z&19J`IQQ#TXO+PFA1lqV^!diQAr zmX8Y<*@)xG?ei$cS0`GT&nHm2a%%0Y0Sx^qvxYI08P}Y5{Y4xomMpI{=Ow;HgcGV`T_`L=1o z#AK(@W^S_nMc&Lqyyij&4mns0zTE3C_)b~)qu|>XC)?W=|MZ`(7QgTd zxXFj5zYsS^2uy=>KG5DGg|0Ps4oBZy@`Uq_iX;`v~eS$VjqYOZ+`Bc*$kz){(_)hJ4nn&gO=W1MA**){m)x6e%LD%@0c5pCf zAImk?J34B><)e>2ik2dqvFe~P6)!zwX!GwA3V7TrO$&C0hf ziC1b#+s5~~Q}-O%ne|tGF?<-RVRnAnt*gUhpXk|@HNDzXY{)t<{qYJTpQNxIKdvEL z>s7`$)@vx1l5=20ES_lk(gUKzD_#2Z%M9uEw^$C^S<5$QwI!8>pDCUd(rz*H)INo4 zE;`R?-7u(8pGZc~!O&;_RO>ib0@V-k#fjnhN=hH3x-QwH4h)1YqSdnOzSi*p=UDjU z4^Dr_rO)`5mvLO9z=ZB@448qqTEqB|?smXcw>|aQ@AZx1S({w^6&tK>FU}ohVq7ON zWd1K&UFV-@xxW21XMzttjPC8`#c}ZGKfhc&|2%J^-Av@Az7v$AyRD7=b>8kv#n@WBfb6be5@J${+yUiJjKpgm zIE~I2{le-eN5-zkssoXA=%*?l?Tp31PxI4|i)38NifO14+e;B*4gPdot6O|KG*|aD z-z26vs?Yiux0j}Kh0Vt>_0QIu@$KXV!MD#}X%6#NiF2lYIt|MYeBcAI!LQD37Q^{r zyIT3rsA|`tCwA-;r_NW58W)ednF+qdI)0a(w}<&^KbJGj1x@wo`lRczCXVZrS7Zq) zxP9?>~?B6ic*fwpW?*v+>E73nbVlOB-p&BrMGSkMCqKM8bgQSJP;&&#v?|| zgzXE-xr!|rtF)ayc5SWhIM(#$)4cjTOFnjr`?{|gcJ~E2O!H=#b4`4s`P`A6GAG$x z8gR}bBd_OfH``@&i3i*5aPa!aKYsLDa6}eM{7J)hc@XbEm+?Llf2it>V!BCO3-=se zI7RocugAvo%wl-4PknkzIPy*GeOw29S*zK})t<&iJh#3aQ(v|1v)^(b<6IFlnXl&U z=z+D*5;S{GDNU(oJ~t*xAu}kypNoRUTkz&XeDZwBCv2}HL@4mcpSB6Auo>+kk+Drm zWTC^yiDxcJU{T zu%OWEXKal^Z`e7mu{j)Q>LX9EZ9S*V6+HfJZejRO$sK)Wcom*~ydz&6$hP+|x~n-q z*BUhPtG-K*V}-pAcW#lFIYZb>@Wt0&UoF5v*yZBgrH3gZMGe4Btd zF^XN>e%co=e&1Llx=P)4Ls}ZYr%u7PbAgz1Yb{_Py@7zz->~WD`KcSt&ZaY&qs23? za?2@pmC2*Gu&h7j^fR6~a{4MeXX>wv_&UzocCiAVd-;{a#ic8Hi;6>lu=~YUh(`&6 zxH>m@@yXNeF@vVM<(Ty(PkeC{g~pR4YfK#_LSD@k?e7A|{(iBEWbMnHA+K|GtwD38 zDt2FG z_f7d)FyPa1c+6Grwr&8&YMqL!a>_McIehK&C)ZWZxI0T<&1*i5sjunW5q6oI$J95n zLGn~tV^udoIt4Y3Hdp$!OUXatI0w~kgXq-a$QpF+kTIevUFXfxZq|IA)2q*}puUje zW~>r4)u#;FXM<|2`E$-@r`+MdxeL11jf5O|bpv+pT#c49!_%A=o2<+B)#{l$4aBg)(^taA@s_CM)e7^~(mMgx^WjYlguiB3Em!fjbjg3T zhDgL}zG==!+8gMU^X)^vsdMvYu&2;9&pCyKJ$rqOG zxZ2nJjhR7R@fOgz`8`B@i>Gco*V26Zcos9`0}kE9)NgjTQEk^rfBRZT#h43jw!>M+ z3PSRWzwN6Q$;OzAk2%JhTSo<%`V?~-Pw`V*U(_A@%$zrMpL}1EGVqTM?xnZs3HsuyjI+O_*UXo)_LrN!+&9--ByDgz?`4zipK`=@{|sXfC;qF2 zXV;DAs_M3jfA8a+Vx3=dcVbiA9M{}nUQch9L34gEXphg=uA~i#CPC0AVI*Nw+ioO5 zn++bwn21Rpk!^??vwH$3&H3i2;;uDkmd0H|uT}he>rZlb>?t8$*<`)?t&6b)es{n5 zKow)M4eXRF-gcGyxaQRy>vKHCzg6ypuZg4@q%-p7+2j^<4wDQ)vQlFE7!Q{O+vGUN zdW|QIT-Dfv2etV|6gMN~eDuxGo@q~^KBrf48trTv*FemOgEL2a3;o3bYUTt;DFMA`9(6Hfc!HE~9&M+!~pVU((HGie%^ErbwgZjA?^?0wBKH83hroPh{B%L2q zyhY|)H&XG__eHQi9G#EN@ijl@DMN&OGjr|b@g}CTI>EGccEZ<0aK+(eR)A|{TnxM* zz>{v{G-f9><3Ei@bM0^($g=n2uANVxPu!-xrpJ>vY4FwRR=%bj2mq%3Ifj_nkW`D5 zl4Kq6qF3Vd8I;HktNFT(&aT#ypDsF^pJSegaj?qZse4LZ(}=FkHRIejOQC?wP5C9I ztIdaaYevQ!!aOC0c~bkvVzOTSA=?~t46+1G8W_V%x0D?4t?)v&4i&slbEh~W0hm>BzaYMT;2s#VFpxj5~}pTu~xO|TO) zs6SyXSkh*1*v#1}KL<@3ZzrkG)aHf+DXT+5O1BTPGd;x?D<`y!^T;1FeZbOjEu_b; zS9fV3)rLODrfS!CWxoZgwsbAke#ac|xOt!LwR4ncMUxA zuLQl_jK|i#zBM-=*FNJPamAbsKC?jc25T{FHYeaX3%ZVQlUL?5x9h{kqzka~wRULJ zyeazz82iTdX@2;se_yV7Z|&?jM!t!?&v$Jc2mFkDdl)q54`vzLpeiR!(dM-yW5uRO zpCyR_#Yl)lV8EY}8e*q$4E&Hm&To#S^iL^Dj3j|P?gR?W`Sy|PNRXiKl$fhI=bO+B zWOm7?QN>Slo9zv54~_By2YXrzM1RIdl8TYQ~q0 zFXivfS$LGY`z8HdP7EJoxAlLV;GgAK#ofI+0iV6&t}AEny!&zhh-^<~(cTozrCn>O zDQ>>ju2(nCm}XqFG{w!;0$pRdU5dn-ds>~{*nTvYspx4HMMUIb4o$BE4N#CfKFR61 z8f`+5Ffr(c*Sc)s>6T5!x@wRsl~`&`Qmb$q%{i4pGpybDly^J@wQA|77`Md9r+U*eqY!ZkNZ~(f^K< z9J>D}9I$d1?hUwt;rgIEH!*xZ z5HEI%H5@?2ZhE}>mzd{cEw~fo9Q&Kj_}DM@V?lS3H+V92Q1iKdb}hlYfc29yZD8Tb zBCZXKl1l0ru;cY}6G~r4NIpi{V#S%2I&J*qZ&p>;IO+0^zd>P(1uo$#_1- zF&2t-cXGgG4k_*CgS0JX$C7%Bk+TbFd1<$}F?(dx(sR>&TxFXB{_KEQ8=Veha^kQ) zcU5t(r$0L*aNHw1onf-_GzW8QUNF{ode;`X_Gg*p&pGQlnLqh+&H;?PtkKxHvbT#> zoZaPdJf`Et0}owUeA9cbJpAy(Tl|yd?J3+xR|O=^;jY#9R%u_YxK&cyPwnC^L{}Vk zVrlPjpGv^&T~AC@BTb)mik#6hCvlOY72EJ8&mD(`O33K(r@;* zF!j&YdlNL5zcWY5w~MZ=-WLCD;9`5Sd3||&@R z_G)>K^Sh)I<_$IV<@)HyNpj3OWbwEB@JUxqK7Z3#<}y}&6^CHx6;FFchwQav?HIcej+jDKjUTzK$Pvk-&uDa}V7EH^LSqW@nE|83xQ*eGcrhS}<3@7a5DW$*F1HsN4 zW6Hqkf=G%vlw0Nsst{Al^V*<>Z*-#s;l`4j5^|K+T9&3f^MCus#6?H9R;D#H5^@B9WaTsR5e`HzTMem%e0yQxt#aUBB?5OOWIjUL_Ui zamjR#?Wu2$BBpqpCf;rbHr_8^X12f9m;oP{m-qe$E-l0|B{yF)UJ$94fWHBsHHmlX<);sUy%f|AF zC!UDV`jzEo@yf8-o^VGl%7a_#EEu#)2^<70g4iFXb8lZ|Wm8mqQ9qY%wk zV`YPCdGZSxc6N8sxt(l=<*Tv5y|TXNp3U<&-Gr5HmybR6*mk*E zUK)nQ<;5_p`2hGJQZ~jUW8QgE!=|g{6y`#c!ltVbuao44PtbH8-xxAFcUYNghMdcr zha|i#U(}so>k(EjB4;kNuQ+WrDrB@>;v_fNA>Nm0j=Qic;FvB*5p!SI!3rX z+_Bh{LU=CvlIb7&ICj>fO|5dg16>DS9u~{XcU-uzz4PXqS!wy94}A#D-KBVCzqD9K zkCT(|B1(NPN9W*(?=|fimBYO-5EdS zIpy^QNq_plbi@yXDXr(RT8!ZbW^qY@F2mBGZe~3=?5hMf{A6QzsH$XedwlkTtUEqO zd;KXv%ujWM&xV9t{Wh^t6fzbxhL?PAFhRtJUN!z;&6l*`Ns2r83&~@x}XXMeupTn=Ybe{ zq|+!qD-_ll=RtNmI0gkjGHjEI&G}5wZ6k(3Os?NLRd)@Qke^X-?fRn%_X zOp*PqYWm3fY6rdu%QZ3Pwb_@l`cC6k1A5w&nhSnChKw0YTT+j?`mZ*(z7nD@^y+IU zEx+Ur!|Hf79K1YiS1&KV{o6P9{@9P<|JhnB^Hyl?S~`u!7f$BIVVZAy z>Idy$Gyht!pjlroEN_!e;fKfUqpACMshyh{z|_@l)Sw;s#9_0o86p@ft#i?aOkW8# zB0@i`>XU$a~>SsMq~oE|=ESx-vyvj#L8_`yMOS}8?} zNww@M6wfQ2icL|-rneYpRR9VBse^h#Q1{F2TpW^&55edTAjsy#O=_{Z;0(3-3LZLb zNVz)gI66({tmpGB5z=OIS$D@jI|sMb#nJKR(>GmNd}e#Hcw@UBHo@ENNxU*7^NYOL z1M$m%%I>gEQW6)8u84Lr?Bq?@!Ung0TRr9>3uAVt=ec^lz$TCIiNn_w+PsO^ubYME zx|8`FHXLNuZu7|BbK^S)0D|Lty*7J63)^$#kk(;*Ia|OF5Bviy* zV=4*2q1U5?8VWIFuR}iJsxPr^gK`b{Qo_&kl-L>OTFf|)Eg&Ci*k`x=smENT4vPaG zTbz&dH_3Abe|;VhOqns+lNycF$MYu~n1i)=D+OgNB}SWzn2gzOn3U*7DCLt>mqMGm z{1rd$ii!l|+OM%4_7`WPPT1~}Dr+r)8~y($nKxzBq+apG7n0o=A9L8eH<5P`I%IOg zXFh$l!T7>(F!Hts7dNY;Pj6O-pMK_su20~cymDh?B$297PyB5bO0fF`^gqdDLv z%Z~`r2_bf(Gfqe9B|i1b2i-QIX?JE|;sZ3@%?GD_ICw*CIsqAH1qetvrAqdiaDp;k z@_O7piB26`iM6G52sm*KUh;ZuUXI8KiL$$cUSSs zWfsg9@#fc)4{0oL>&O{4l(3+My`dzJ1(r;_gSDbJe}bCN+Q8ZBv4fL#ckJ`Dq2whKAaf_Cxx-XMBW# zo|Q&1bV2Z<;}Bw+$Ir!xam-lx`G1fw9+GTKkx@gPCT!!>>GX~(Rp<++XXChhwR@MpVDbK z-wfAQOQpjxDimHDVt1-zDCBHU^p)iMlhrzs^<+012XmL zS3igeZh7F`02NE$EO!iy>bKr&P)$3PVCoE}4uVdg&8Bo5**WmjEIJL^(7YD14vB}7 z90#fnG${d@h>~ke0Tt0CXa_q(4I@zt_&oAJmY=lG4`LFe#15U3)6Zj2)vty2;pgTH z@;{G{KVX3526yK2rPm&K<<&RhmFLxAyM1-B-d^G>bz#0zqYFx%{?ZLqyfIT|EOVNj zjCFzSuRO(0xngP#ST*xWO`l{q(k43|l9;p`27YoJ(ry+40KBznvW)f-0XyxZ;q7^Kg90F znjNrE@Y7|2aBT7YUJy69r*C>-yxY=A9HrvmK9aRNGMBmKs~G4| zdY%2;^ZMJ*;L)6^b1v0}jptJJ!%lqj!8W&?!Yzh;=Rg`GzcKx>C@#CktUEUGQ9O3 z8{Fapi<3uQ{lv*~yLd6)o_=n*9bV!|=7;D}1QKxjnm%TIZ6on}aMQP_TqJXp-{uN7 z1{b)n$@T-2HZjE(HrL}5`I{e2U{jM9}SPzt5f*5`EXtt(lWK1at^Mlgjzsmu2B zJUYio^|_hGnmuKRiOuVVI%HuGWwk4-=NRQelJ!dgeo@!{eKNUysh78D@k1iUw{EiO zYaD*&O6oerhyHcQo?@*lj{2$dt)k+t=eLd>JN7p@VdGn#>x~${P|$qLiCQfud|D6v zafh!?mc#1x)vdRFc6r13&*uLW$Y%kSVqL@Hu7@7l4C}?kXa`@3i|8V+BQQ3LWNkvY z$TF6UQois;7fa!ao%*M?7zgRqzl#IoAbl=w-xM?DT|68q(l^CS&xP!MlUBd&Do2=N zPyOAe+MRJ2+g+Ef4twke^_aP0CUv~Y0iV}EpI3^n0sAYd-h|<2tgzQwt|u{aSc-mh zGPJF8JpjrFJnR%YkZtov2iwUVXJ^HrX7PcF9L2~uq)fHNKzrr*@r~lvLAGZZheDHh zs~_8jpx98-H)WG``^$6QVxZLZykL{sQyYG>eQ0u~d7y^3M1z1gu=q%AxN_zE!KLlF zgNrY}{4%mh+TbGf)hC~fa?2N1o8i;LX7L4eVi8HMIwsPE2u$MOtG{%>Vp0k#3>K{p zPyQxbTt0HivCp+y-L#2;Odn+g8jd-@h?zsb^17{?#aQ|?&brpCzu{o0POF)cs`WUG zd?6dY=xdrdL*jN8dv z44ACtyzZcyrhNE`=SoV8i}c)*eM2-`vSUHU0}F|t)JcdZgBlif?UT(Ib3Ld1DT7;w z#yGgzsgu#Iz%SN20QF(QC#Ow6ZRNS^*{Q|bKRcp^k%L&;!sN{~-;~#Zd;8J%~ zyD;-YHY_@~65ruc2lyrbABN={`26ko!FRoQ^u7mQeD&2=dG7Od%NIbJVC93aU>7fBoYd(>o4lWhV$=2Rt1e!~Sx z!;L-@4`Hu;5ZIWg=K4^YTEd*4T-o!_gM!usY8n7I5@6I6Odd}a@Tu&hv>@yu#^%l7quof z$+9IR0Z*KC(gZ?6Y=k)lTWIIJUyb%*;Gdflx?=@%!@=S9_19n5UY680IQ(hD+3o66 z2aDw|EZ4)Q_>~ascgfKCXI9aDZTM$Z;Ebb-;}>j&BlF9?h{GS;Bx)q4;5CnNuG!=M z3JGPyiCagA8U^Du_W9y*Lr+Go|8z`o)lys+fpI*C&MSJ0LyuP%`|u@T>tNuKSACtw zSkF7hvo-9iG3dc#e7~?EeNJsRHa-l;RcD;v&rwS2&0p+OSoOP(T+Ig#fBNcL)P;m} zj$_O*rn1hl^3YLqy<)noT;s{86+Of_UVX{&+}1ey6b#02`?*sJn||VxTW&;*C(4-2 z4Zpi5VTuk+Fz*r9%{c2ALy37%3w}gPH-#_h@^i6@1x9{-CKh>|$2ffs9}m76_T7#~*)ubHYDS z7?Y7LV?sRt8MFk^PsZqaC0jK>oI6%a(Kvb7==@} zOEvRcm6%@*+qsDY{m?D#3#?ybrIt3q{+v3^Je1>~jy7~qO zy76TF^lii97vi1muSfTDvRyCN=z`F(weAXgp#LNboUfSfo`7i`mhr|0qicZVD~hqH zH%wu^>PB4Mm~+yf(l%=*tBbQ=jIIhbx#md>=S>}jZ2lP*eHp`f4V_GXtb?2wa@z3< zRQ;UKwUT+?*@xP#X8KD^If^llcHtdt>a*CWld5xCc;2e* z&(BWf%)zivYmVEMF?BOLT^N|RbaXa>V|T?obVSBwK5?p{xge$ET7!-`)tLdSKaEm7 zW8tr?uB!dH6@07}e&SOCmo~dZ{t4GyJbn06F3Mh;U0v{YyLx%MI{QB^j?O)O=VOnZ zy!eAZIO@_+t(%%*Dd;)9eqD;vhnq>7o+)W>|Ojp&O*T=FgEIl5<_ zm@74M=CM8A70s7ST{D4cNg?sZhLojPuzkjMDZHYhih-)Nrjdj!j`R^5hvp+3y6kBj ztUO`7A;<@{!pF@fbmF9Y12QXE;-@j7&V%$kErz3Hox5R($B!gM#zfHJgDL>) zFl0=q`4|M24|?-aC?+3WBKqxs4v}_ok8wdwO{L^`u0}1-n7Upn3MH;R{uE=JxIwI# zCT0VIYK(Oti*B;sFp$Y6s~Kmg#zD3x?h)bI4SMdm=TZ}mHn_v}m6z_`UV3iWu3s3| z+gFG6=8|6t`6`&QF}k4agr=A&79Ewls<}EXUKqF?Z2BtO@2+E7r?Tc@qeXTvOu7C7c8Urgs=fbw|%o*_**I99&W7>gnzUCIL*NsD9ZSQg%$e|O5&Z5M# ze&y*Ld%4X!uIVYQlPCk{91ww-i&*oAP8pnQRz1&CB=gb^ogLqZr#N&+sqdVR)Wgqf zAv;Ry?65Kql3NID$gBxGWAIzg_zq7outUN}DP0{be8!2l!|>YTX!Wbxqvf+tKmGLR z7Yrbv8h!e?*Dl_wg{=V7o#U7i zlRt;3nV%$gky7oye-oofoStt%{D_s&&*do1LRZG)K36*J=W z1GCx=mzT?f&##Woy!yZc4{RTM>@hs!jYb>XV)gp^?Be8jwOL<^0x!qK@rIxLH_N=% zapQP1vrKM_FTTbFTjP6k@Q|tu=V(6ax}jP;W3E@(F~^(D`l{U?A27Vh6N7j#Iw4|U zCl1Vdw4um1X4gtta+^PVy^dI&m~$)V##C(Zcy9Tc9Sd*WoP9B+=1gpjr9O%qlg#r` z?8+|0T;^v??q=8X?Df~jA*I`@-Q}1Okg#sQeBJIiF?A4aIIP177aoUfJruZ)RxLhFX zG}CDF(AV5e*Q8)Q&*~J?msE4)&*vr2dB-Z3&uOt~8Ar}J7*A~aIX^QJV|#>ylKvJG zxBMdeW3rCea-=giC4DU>sjpr*KXCLZhkoWtSG~%Ka~;;G92mxcvxKkIU>v!%PB2Ho z5nFI!PbI(LyIsaR%!5}px8C;2^*#4|uKops3Dr6c!{U?il&>}?@%D7{`MBa=#Yy#r zP;ZVAmy4v`q5&E$Cgsju=*p6%+NVCL%D&-FA}ZoriLZ2O>eyX)jr)&%z4 z)Ollv4mrZg-`*xRf3owaciB2J=%FxwRM*`7?J?#W_ML;aIqs}=KkHm&4dk%CYoH!Y z#S2>jrq)oMU4PB#zBuILsXk$YT_dF*duRFQbk(T7IXii$Zu}67Iw3uOFbT48Vhn%^ zJZi!Vn@UPhY!D+G)E;9Tg8e*nhHYQ!jiTO^7{@#bgka5_+YG7_7;{_ayo?th{L4pg zEQjUmmu@_KV{>%&@+Uv}No0-)zk40gBhlmij}QIe%~v+7+heyp7mxQ5z9$X^atS>d z6JbZC4;dHS34!)W>EV0xQkCU5!88WG2UoW ziQznuVOAWa{4GD3k9p!FO?App@_Fa;m)B1B#gUs)ugU9@x+FSJW+BG=Fh<_H&T+o; zh-%m{ArlLq&japD&tqBjCRqmx_5w*^bKZOm9V_WI?O;p2R(CPH5XYCc%jL(;{+qx1 zFCD)BzKehEQ%@cHU-C7LJGdZxN$&UxzAMtn8Ia~F@nV`tW2+9DlP#}u`b;+CHKyRD z&<7a@#}m?}eI-Vs?HmP1`C&Mo;U=j=rqXz>bb+lSdT5B|(Y9P`1)Jj~H*B0rw! zjPXm(j^mtXp3a5pwE2jWuJe`tX&&t!Z~0RkeWgzLjOG2*;>Kpo7_!a}O* zr~6YEJ$#V3kZkLsi=T`!4p-{jO4A6?fJT5Vk3^{lidpXlzd!051&KnvnW00 zxP>=q2SX|re)6)zuFo`2xby}QXz;&*^`0Fp*($9GRQm)rP4A+D|5knkr6j1|G zc-6;TVH2Bm%b$1Y@dMxMwJ!y)4u55Pc;@df56(Wd`1c;W{LBwO9B)~73A66d=Z|=a z@HqtGxwEuBC0%4y#>EI(*krxQP>dnNP9Mk;)Z#@4Sze8>D&NSB;h)JTmO~gE471OE zi5SH>tu0+nO4<||lmNB!2T5I0F&u<#IO{u@9Pwl_W zl(c0g&b|@@ZiW>UH+-#8b>ia(xk@3En%|Bf0@9zcj)#)|U|};(ogl-~XTNOE#ky3) z^c!nj>Jwn9bW&O+WsJVy7J>smDMx_RC8`f(yXZQI8g38O__bmUgCxgcn%l65S(+i> zO#1(~_a;!9p4VC6zf@H(l3G%0v1QA#WNasqje{d4gsCJ7T5 z4rG7;lW{A+VPN18mYH)BGFb>A83YN7!vW%8IRqy$R-D9ETahDKo7Iw9TdP~$)m8QX z-#pKA?{mNR`|2-QvKC8n-Rk?@?YZ}TzwQ0*zf`qVNM4(IDuej_w^i@Ikyb8n53OBz z=CbAH{cDTGiOEj)1YY+(p>8ERfqL@uhDe_~7s;k$*u|^^*>)8z$CI(=iZ$a5{9tt$ zjU0@hdUScAxB2eYdb`)jJvKTX>8i;CoOr18m!5V>$I_>J%67(9)}b8xIbYTo_=o!- zCT7nXJ<}EgV2mUovll1xWTIjI*GHN}pnj02IX{np!g zWN!E=?Mn=?Nkg0^F;uu|YvulAEn^0Gwp%7n@nGf1j(TCx4sLasIQFG;AM#Y%Z+zX? zjiEeojs@!9yQ7hDN&5SkN|dknliS+T0gk$0o_BNl4DM?+zQhjgGK+^}+E_6co(JZu zX82m1t|Lm5jwxThk>cD2o-*`!`4w*A6i*C^IwO@kw)t{wFK{2ZdHIGDzj1DTvGdq;F*!c(CdYZf=&8-_rR|XH zZzMTZ_MYu8c4q}1zVFtv@*14pH%hy$8G7Cnjc?_+X4m0?qfCFB6Jw6*;4l{3&aruj zoi~i9FS&GM$`^G8`6Hc+i0<3;8{108#aXZXV)$T}ztKxd=ce&77m5}q*BG&mll5ff zGdJ1xfHTLo@>X%qiIWc7{un!UI`V__21!g?TmH)H{ff19N;25`^189_^V%aOQ5r*P z<~ztGNzXWMc*P;1$DEto!rGS@+J$RmEigUTF&g=YobSu+iN*4w@qz<8VlF|_DU;hy zt&J}+-eiV`Yj$ojU%PL5`H@erU3U2cw)#wv4l#d1sx&e{XP5=@dGjust_BYfJ32 z9m}!+HqHS^gPbqQlMuBE-=UvEwqf*imLGP{`u~hfaJO0d423H?LSQ&Pl7>*P`5Tv4nWt9 z?sP=vq1*vK6ef8-%K(i0DjMy3P5qF)BzX9MV&$EqmFR7A|{XgYJ+PUJy$O1 zKHt(IR|qJUPxf6MdSsB=!7TcGOYP)g(NKUSJ%Mh`$zLwZ*&9Dw-0LVR5A>Zd(hFSU z;T|(Je@kkB@Eu+9GGE2DyI)K}i0InwH3DSz%1c+I{70ttK&pG5OzJ;ogQWu284tG| z8+NTv>$2skq!z;R+br2zhl~l@B>@<;0d*)vIxSJsSnqS0-i}@Du51zRHQV6D5&7N~ z@3!0#uO`zy?T`Ksz5tj|Jo%)=z({R2BEa&xEcWCa_%^@~1#tvS5QCHRlJHhK9oQ=G(#}oGt}HZPc$B%-g=b>iT5! zvD_ZfkfS9}vHdtkL?g-P z`T3?^BBvvuD#95w9WNf+MbB8)R5r~4$IVlokLu7mkuIaXTee38=$u@Fv1eTKZ#?*Z z?=Nd-L}6%kKX5Dm8FuNuF7yA-h%5i(r4#T zDBOVK=+|tIvt$Zha>SQ4ZzNQ;rEMw>cJX5pWU2;OHyGyzY`)=2W7$~F0S6hTXa}$7 zy`S>>9d8{yu}a~0Q%k@80(z7_Hu#$58g4cz=DL#C%R-;hfQyyY9 z`bDy0^Z--;5biREV(D%Sqx0?&`i9Nge69Tjfqd!#=4lH2NJTd#OE~7oT`7et@;k4e zSy*aaMDLjfFB;3a_7eDZPv0XDzpjvfXKEzH;IHZCYo7)SS0@9K7>}+Cpl^P9u|uu4 z%BQP7mAZ5rN#eDneShaj8R**MN41OaqaGA=3htw40eHg{(jN-K&5ti83=(IB0(DL6 zufe)e_nZ3A5}xCr{kLISVLw?$tmrg8VX9oJ%#_f-rJMSs?cBA(^nz!AMg064Nlm7- zJ0eQAC%e1!8pfG57qsXTRH>!W(P<${BWLT0oJzOvCzOzqL(VQ?yuIVb7EOEPWL#~` zGLw$lm;G^~KdlxZJ%p1WHnTs!v_4og&cv=oSya@azv_wv+H!2gE_}7h|NOU1Bjr@2 zYvnPOt&e_mg*9aBe*5?MhRFgLk9I{kc%J`5^BBgq%p)Cec5I)$`5xt#{QPYbbY(zi zRici$5tRSclH;{)BePK!S3{lT-ubrR`+su&6peBdBP=FC(1{AU552D$%!Axwpt|NU zoxgafMW&u;sB&&bC9-?n5Gq|iV*1C_Bf~~flMxiXvVSCzJV_@wy37J-$tMJ9GC`x6 z&?6KB+>&VEv_6cv6jKxjULNxGlg&%iK2ep*ekBG*s05lI+7NiP>3wVE+dNfwP_(F_ zvfZpxP|6Tdq1|CHPZsmIdl@!{3dxE}I|^+5~9uw0`|rK?|DP0yy7rJ+4h-9>&~L&ww23`Kq_ z8s%WDt?tzQG#1j(;dA|KD!Qwzey7vzy;4!q8p(X=vd2Si@#;qS1p&diu%|#Zzt1hBVX%!IeMt^y z^g9sfPsx~ziJ!T*gqe8~v8E+#xAIG9;nFT#m0K+KPp9uh{C&FFpuSrs=MVJ+rTKZY z0O7X^V{IEo$>d|S1Cz2W-VXf+-V0*@E)VTtC7y(!$g=g*DEfuuUOq{9n8LtO9LEll z@EDnG`m895F33@maO6iDyu1$_-1x98ly)?^_j7#LCY5HtfUH2yGWcH|!{6fq{YGQjMqxvDnd;_gA8@YR77zamz=fT%WG zb+mF8-rNrGJ0aLSl#X#|QDXpbLjUr7lK^ZBh{`&;&7Z#y!yAe^KY(`KEoE|cq3TX` z_Lh(QYGG$T?9<-o`MPJ-BrjiCF`^%lN%zdI^=8tVF98es!&;NFMGVbtdPaghsiM_y@jiU-PU`gt%uDMdhJwOhT>oze~xXKvj#J9@}jA zTG5CZ0MkY;$zFCE0vuy)l%H7O^=~~E42x2TF7geFF$yaf1&tbE`Ld;#WJ0YhO*d$E zBD#x^u?Jsjug5z{k3=?f`0YQ>nq4X$5G6Cz#2WDg|F%_B`$=4mobvTL5ugaCpjZ{F zbaBp&hO%A*8Wjg-Eg9~6N^1DAt()5zZob6Mq<$rPW*d``6UN~$Ut)dselGx9C_u25w=cH~dPufL3M*_9$ zRx25Y#D*=HXiD+Pc}<{N$@y-VeY~;?D_v{7mY#$6!6-3WY_TAmr$D2PP_!6shzkAL zOXqR>*>B$Hp-6d?roF`c#LW?BUE1O4=DdXH^%8J7_r#{J%4h{~wxvq^=z|kB*MBRRBz$v4!G*_n^4j0+ABF`t?T{LL=CP z8SF*HI7QpLAn~Oh*8no@9>n@!-_(+O&V<{PI|3h*^0PIDg&3w&xWruOx=;M^>g|>u zf8k1>hOYc!+@(|Mc^hrOlMrCQYa@7k9KC>D7Fq^G4ajP9Zf4q|4EX<#I~D<{LztjFo0lXUR;kj#?X^%h0jI%$pU zfcsjfUUAD*#O<*aMU2a5SV2x#Z4fzZBeZ?hRSyhIW!s1F>zX zbD&_jj6#W;F&C*V!aLf=OVstaWMawUU+_F+c8_KT9`KfD73DUQ;~qOd*#{s`hyr^?CQ;Qo?o1N zPb`nsLXQ;px<9W!`onvF=2Fpz*N!`AfPon{+uMtg}y zL}`1O!9>xx-eCVQE6eOHsYOIqW4beJl9v< zaOf|kq5xQBRw;0c&xu>cD%jpxU!Wnts6Ij~Tn{spItEY&b89unrKi9e9?wGsv+$4W z#n$0Laox}^_ADt-loy&k@Np7jyswnlEepdYz-XLjT*5qQJmZRUx@ggzXsPEL>8lL8 zE5x*!o<)X1WC*3b(Vn|vKNRO<#IU&MU4ED8W~FlK`C4k$p-ygYVud{$lnBnP+mUz4 z!8`{mQ(P^+n4U9PO`*w39Xj$c((^&HQ!8`Ca@9dY6$T1j0D)z! zfU6Ly+GguAGME)sQ%hDp^|qQ$q0NB{S$k$Z@1}kNweMyJX{>)GTJ1vFe($6x&Umca zYMFn@tNV01=lbL~S;F?7qvaXgM`DWs#amJ=oo%U(a8JL=`213Cghw!^NxUDL1$PL{3CGT9)X~XtBK>K5 z>A9vJ+qaBHU>PXSQ)a%y!C1Lx+-Vs7Yp!kuFXqWS?2llOijvmw6R5Q}JSjsKS{jvr zfC*nrY^c&gcihIMt1mS;Tn`WwGCnbC+R?08hBiPo&V&wC`*qN&S&QuMYi@6Q0Jw4| zU(`!CM@zYG^Fu-{MZaV4mZf*5->CV44?bgQ=a2`ul;h>MefjG-17teUk#npPm$y} z(4{H|qlC*{NrJYt^cpC@&s^z3Wxz!Kp77ZdxZj5%j`G-*-EQ}9?`fvd$}s{m_4Fy- zO-SyO<#eH$+PCowZ_4bOgo{5Jww1gR>}~sFGVoqSFFEZ$EyiJbW8QfQPlF4UgUv0F zdpg;3C-rp~7jI^6*~!HK{ICMqXnLiEl8hgw{h(YyHqF9~)VY7t)p5{S?aZn5@5%M3 zrlV|vuq@{5vyLH7R_gH3(q>9Y+s+X?ZGeijNZp?M?z{Mmp#j% z9}=dk|6VUh$z*&}&Kb#jluURM?isu0D~Qxt`8D0rHI*Vh!CzY&7LJ4OMG<+YiV~oP zs#mFGaUmwpczv=XHw!^KzhijWznuXV!8Ux@SIf;J(NX5J6U+bB2dmkIX!nZB@Wncs~?AQ7J;c-1I=oKuqM_;7M2bv+!-Ya zEHl*bYYSPy((H}@xZC^nwB?Z==gcA6{(niuNdDJFZ4*L zT0rchI)(`GWSZzO&%nP>TIjY``zJktoOGw5V2-n3?@`PRZmD%6-I1w*Dd%rdQDIYZ zwumBe#m>jcdZ`EA3SayZUi$`M|%un{n$nt4vYwBgs>0m{U(xHJ9M(`>oxhZZmqSySP9bn!cHf!!!$? z=)}&z-)@G?Z}ty`-n84g%pLWWs3_(;J`)S1SAVkYTL8UV{_e3seS3b|hI%|&x?hRW zBMnVM@SqN`j)w?dj9w=t-hvq4x_~52^9Q4@8v`1w{WidV&0ZDV^rGO(1hswOrI;SK zVx-XxWs7QX({AB3+W&q4@#X3r(`iTVk$C}SJB7Cq1vHu1uAT{yZ~PXIMYvb!--J}a z`FqAj7R9|^JpA##laGCccLQS(j{7ya_>IqhH}jXIN~Hrurh?Qz%y?Z}D{Ew`JoZ<_ z3pOrIVi&^Ow9+TMkj`G#Wl~$&2KD)Rn6Iju^G|Y(h7VC#Bc*7j35mc+LK6#0_M1>@ ze$)Fcs@C7!O@Z%>8Wh0eB8HnMD{n`knA(R@!hu=a0phe=IcEbh$zD_YX-CUl$ek0Z z$AIjkNXG#2XOJFNDpM@|G?X06mu&P<;4+mmg+(G5L7{v0**!Ie`vo-8z8ppd-XXTf zU09{(Uqyt*TuNnvA!Na9*OCd_q?AJN<@%C*>+zOAOz?16k!#D5YTM0e<`&S~Ek7m@ z7jickaLb#v=vh_#w4dAacp6|~(Z>cqO|>5XMFU|T+nIM5?H%YVF2`8C?luC|G!bI} zo9p(Y=PBa0i2i()7-6Sv$>`)lMDjUbs~1t*dx|8o7IEfOqW$fU`ke+l`Zm-1fysQ2 zpI*eX629pBmL)00Fuy(2L-o)c3N>_E^H@EK37whdww_>?c>Obz+wS7K!)l3${&}d% zyRV3_a)M&cHmw zW+k!oxr3q9wPjc`Rn}RRo%2O3Pu(cXzVMXq?1$b*R1QwP=?&RTX=3s~93hna#D`J; ze)r1?=C)e)_M??6IA2kl=7M0g+ArwiHoG3?8Cmnm_4 zkxRqVu*jqJn0|ga&&%CF*}x!5LsWZLpjM8OXg!p;o)8@!z{;k4=$>yk`wA1lt>(M0jGGES^Q3PO>JC=zr3Tq{EP_WFCw z?%^Y|A^Cufoq@>{dNO5a&CO(5>NkI(G`(X$-J+b=ho>%q*FyF;%kn|{%e{`9g$Gw9 z_^-+)#Mg4N+GOx8FD``|i+^EL$9Iysr7Nfcd6y*kIrXM;bL8ta4@`Neog~|@b1ObC z-;*7w(7Z=ydccSJ>h8l*t;z+Oj$});k*r6ruZ*UbtZ-~yndtk;2iSx=p?KOq8K6^} zm=q2o1>iZFa{sK=ENT%U-I)AS9qALFo%>|l#08Vq@GXTV7Wg88j_|0AEJ}73G_RC> z1-r|ha6K>x`upjy+%9+>a>J|9)#2u{afeHG2F8P?F%v7$)MJ0JGtL0UCUVuel}Evg zfchV_uX|>erth~_#055SEWnWq{ZYz(!ZDjGEO zd8&X6^}bDky(7l4sNG)+x3&bW(W@@>+$S@Y5%OdSt6nPeA!anV&PY#9ajey`Z24XA z$)l5Eq@}wk6OM-L6s+prwz&6(uy20fb|oCA#GL;!f^8stOonSbNssJlrdg*ZdI?&RbFZk!VbTlBx=;0l}Ig*eUUCdkb}0vpVr-Sh5nWY zq>b*UI}LHKGW$ys?~XAaGDvMNamkFIxDl+|tSN7HkG$8NB4oeK>t{Cyt+l?J3 zBxmjj%jo?&`aCM2Rlmk07JBuoyUA*$6q#T0a(ryYG$yZyC2bm#$QU#&;HyCI`2ZT3 z!l^WElI7KIH0^_V>Ci2SFqcUf;Aa~O?4pa&xCrB%&z;s9<4v#O%i>|>THgxyn`w4) zR-Emarwa+rJ?MlvomnT~ez9VJ0nRRnZj_)WQ`ZI^obS;9s881GwT5c+cdEF}h%9yG z4Sj(&cV5nez`(2;HJBwGTDHiYXFt=c<&QtG!ydVFe`0!X_eIbibjlf%H zddhUcQwEV07~hsu!l$XpLytnfu4XLb6I3XIoHlo~f9rujdj60xHeC(5UQO~WrT%g#r;UX@>jp_6{>+z)9aUAUu0BnvRj{8Pmb0@L zhWothPm#8p%++ND$yo@wZ+R=&nX7qq_lSRB;!i#K#1*aQmq9j?#brIbFey9P`zLJ^ zG>34o_)7(@fbx8^YOpGD(=DY^Q(TWl<@+e}FGxc_NBx>LRdalhp5^#j))> zX5L=WOxoVds^^fak-k_cUJuT+>Cg0Dd{v%#^wzA>Qxiu?kp-=ou_^21_ELv55GD1% zxG+OKvcA*7u6P6=8a!942vXmC#jI>K?_y&&r@Q{P4zh5A`mdcKlP`ihKe`Gwzps11`J!K!VoX6ccR?gNb0!z4b~UC!Y7@SF3(n$ z-5fRuJM?ne(fF}Q`Cz}O2kNo|xJPg^dr#9D_C?ANyvb7cxgqPl6na$*J%3QU55o`*~UO-HrShz)eZ=Cvj0>XSa!yMz8UJ{5YwE|p- zvTf#)_|x)%@w@6dZmAEheD_b*n3VQXU0oBh^pe=UZBN?R7pGXmG4QJPrb=nLrcW`O ze`cPSA9Z0syUY#Y6207Ayj`Jk;9g6&{r5nLu8Nnt)r;=c!THjvHp+sqFfhH4Mts(% z-oB|v8uDo`IzZgByz18@4)v{DP4Ibw(!qW-;kK}w57R&QiR1KsMR|&iMLkRLy=y+J zpZFLXm)D|Kn}HtK<*&&)l(fO4$9(mT-Ez4xyNKOdmiZ^JCy89+#0InvSC`a@S#Z=N zbOV918{YJqds1UwXnLB*Qrm#D2Wq!TH$iQKyDX7L&;^bgiv)79EVU#J zx{o~KcN%th1wQZRl9>UyML()@ch_CJ)@o~w0sZCE9pWHL3h-_62{XU=TgY_+?d+Gu zQ=BM@vcf68$+OS&Byk04?b**g@Y2 z2%C+XFfwMd9H<_H75zqMt}e>Oj z0}e;dWu0|Fs|dZic1*3Gwf`*Uy_y_!Ql*Pg_VTwR@?-QoE~hc0Vo}jDW65*5qf?Fh z9PTPFc|~y$Xnd19RGrHV36b=4l(#=<->*#=U6CS&Zzik<_s}Z-q>9YU3U|rGy&P(? z*>lXVWM$-chF`cXKf{ZJyeiu%6v;Z7OQ%uIJaJ;@34jRwo6=}p6Wf_{!5g^3Be#W| zCbl7>Qp!7tYY6;px*QC3@q+C%rSPhA)+7cnJKSJ23crK@Qc%G;m>O~=oQnMo8Kw|! zwOJCs3Yyn##VNJLSJzdFZ z15xu{Ao_lHWNbtH_Y(?AM8cdL;;!_LgLa`nHfXA_RrQ%OL{cD*vtc9a*Ahj;kNOoVG`PXe>}=;DejiXgeAtdAD$LuZmWhy_v$iu%77+p~+wu6+Sy{ zGgOBSu@jaM3Nj^-N?M z>=sm%(Q;BHInjRQKML76Y!hGG-3xoF>l6~UO)Ej$1s$}jr$lQR-@!KY0MM#l#AoX= z@~Z8g=bj_klqy~esr1>dl~haUb&6_#O^bCwr6GsS5`|6qdY{Qx;+U#+rUZ@?@(z!A0#);G>{<4z%o~=#&U-v>5 zY5T%ChLe;LwNCKPE0z5!fGyZzaJx;gvt9_qJzgVI!}RJ>QBw!*%-s4@LKohouwmOm zK)jnPEF%`!XYY#h7v{8W-QF^x?i3t}-I)#V9<|7(fGQMb6Vdlo6wwcbqWudVSk2hK3Dl zmchbdFhkN%j)5^wq2##1zTb2j+W6{k%DIMiEIDCdVuj zpZz!%$aTdb-_+EBY_7^)(Xw2mIVqE`RAbTLH~b={%dXXe9%(PxE!Qihhd;i}E`4-l zXAY_SudOZB{N(fOlW*5$2_R93ki0C!*|3Vs=bON@@}&8~@DEGv9UPkiH>=8Rdv){4 zG7E0qaH3=D=(m8@)*l|z*o59jXO6a+dRe8!NZP$|wRrY<33l^%Whi%n?`NIX+I46W zAu1!CBp6&d;!UJ{9^iidrS>42cWM!IaeV$u6g5*LWjT8EJ!B;|%i!&^$9bxu0%#wn z$*f1^F|Y1?P;k|(+=%x(w-h=yL2XE_wu)C}5T8EJOyyUTUk!2+D~l9CAb8+Ny`)$|K_Q5kG}iU-=he;o;#TJm9ffbgP}5) zDyvh2*s}?S5;=rG3Jk5_zl7CKyR^C}RrjBf?^e}|v7gJ&wx1n=;&sZJs5WgmxAqal zg&Np*2YiCVGm9^wBn;4*u0 z3St&8%eDlx!OS$MHTU;#+rnDRGj29#hucqFm53ePi$!fmeU0p{El0k=#omuD{QNs9 znThTcn?4bb%q(vQEVybJTvW!nG?O+KFP@pg40`i1y?h%p`fjQI+>Ny-jQu_18o7XT z#&S!XOCR1a$lUub6qWtT^F$;cosx2a8cwOZM%FDIE^h>z04V4Yi9Vm2fGF z>wHTNJ2E&OYWUYG+Bt*l)C{3d2XA)-!&@eIDWQnf0 zI!ONzCAIX_ww_>o_BZi6`bFB7L(wPM6YS>$m;gzu#Oqhua)AzTL%b8o_Mn0!QV*Jz zv@iEbat8Bgw8W0=c`T;kiIJwVV4q0LWYn7_V&Edtygbc`v4<`Y zducn`ZAV?+EG8+Cl*T4coO%j`a*|AyA-p?PK+(iMPdcys zUDGmHHqbYO^_CB9WhuT03i1q9w7-8DbDM{n%dc?<+3>D~ku2K`_+!`yWUX)h0N=v} zG}T0pdXJ_|Tf_;-=UD+3q#mAvQ5-uFd>-BPWu-{hz*}WJ@}3|&Le7fUBm1br#r*>K z;ec)t)%Nd4Mm#tBW#+-zw3AAczd?OI$jiluj(z96NMR-v^|3yPw7a`HW3Bz|Qcs z?LY1`OtAB)YY0SjpBII`_Zuuu%&w~RKVPel5mwHAaYxJbN)f;CwYuJ)qds@nqSIJ7 zjsE^DDXu1q0hb6SQE@k7-LIWH(<^ zvKyBBVgOr}%4y0y;sRK7UjP#>Gr9D`e_M{SP@+c8@hAO)4wuxn&iwHr8oq16^56l= zyG~hm0unnuEW4&AV$l(wozM}tbEjn`h#*|v-eH=Qv}b&PyR>xL1)*q>D`T~ijM;CO zZg%&*H^$Fj+=@6STYv=INMNR_u)(V&h^Py%@|qQI7xrkzmN-S0;HaBz+2V+jY7O!S zTucFz|IWJmKB4fYQ0@PltmMhI?4wL0&D(s37!lF1hgfNNt5zk9;0-JzQ$$CewZAu! zhF5c;(&oM1CDvSCywhN(^|p#*1`$qmLBV&G01blPWfzDd2%GAB{m6Y;Jr zh3Jutx*+7G?q2yA9YqucW5&4Kti85KYehjff)>s9+I%Lhw8U=P9An%-BYIq|xIZM5 zLx!Vj5WX%sYTNkg69b5$ZX}c?fi@`!`A;?g*y<5a-&e_Aoa>4~^oc2p`oCN140zRp#swPtD|$yKWJrdaI6oFCb-8@e6V%|v9RuuD z`j)>3>vB1oN zHQ0JQ$MdNSevo$CV!hty+9xM+UM{*^;qs{O7S<*ZA`NbaODr{<^(q?MO#TO-J-X+ozWOD{QQ^<_|+E{k^@!_S#!=ioW0r2yHB!=i_; zidu!f2{DIfYtB8`L8h{FMODCE|9ui;EsQ2fR zgZOm1R_oIZDtI6QM$Y2K35Q6iVYI4>vA)mi{W<0uX9I$EA%T9E?n+k+txFz+wzHN^ zMX`aRLTF_51;c~ve-ko+XBYKKb&c2ACl|JUE^uONz=b4O^aKOJl6?&@omFoQSp82M zp%n0gTkW@%YRJW|Ma}NIz%7DF)?`?395cT&vJ_?G(_0}YNH(#dZ!awNjVS}lBf*gY zfQkqAmpxj57HTw}{}b5p<|hV^sEs3V#-=vtA?GCznv#b!L! z$vDfJYzCGjWRQ(6Hqg1wXx&fiw^F1dJl&poPdgs9T)b=vQb!fV`cBXue*JuLoRP&3 zE*Vhnx=+TfA%hBVQy-@V_xQ=1t>v~KvyvGh@PB`HBBe-S!>FD0pGT45F1YikHq`f! zh%qh0K146+W5OlzouoJ^_LV0|ViHPEk+YEoJYo)vliUlUuQ#t(sJadwarK)R&03Ia zg!kTDT^E5P_oLT#v2O+T|7=TAWOogp%+{KonR0xk=$GSCNQMo4!Osj#pYI9bN_>by z(@mr!TMwGnqwLhK;}-o7S(aGw59Te+s-uCz|9-_-Y=%>7N1Fr@A#Ju+!jH6aJkce_ z+w_j8@-bqXN>~tvDh0H$p9F^X>)D_82YfNXr8V@A4T3lsS-zvJ%ndVr_Y^4LTHvkb z7#nkViXMh1KtqU=X}(B8*;K7yF=_kaBW0ZVu4EhRXtQLCCZ`1vIM@v{Bd zmp2@Qo-@hOG; zU~e(jtHX9*Cm6<_Ygx!xfkZ44y8a!@%H8Why;rT&bq6o4(bCwt0Tj0AU5`?LKw3VO z)5u$ayMKV8$Bvw91RB}OJ8~}C%0+)Jk2>`gMs8&Q0M6&#l>Fbm1c}49zALPD>xXM- zP=i?54;hn7b{w7D=KyUdglg@LuVZdnm6vk1EahY8JShO=qfRy%RF`aW+dt(IiGtk>6wSXIN5KMQGjAENS58tbnw{iA z3!2x7tH<)tq-=ed2ZLQaOaj~FSG%8=Hxt>k)VROh%U zf9HP0QG~{}%K*TOY`y!xN8}Q64>7q#6~ieWx{z*W=(ZrmHuBFwX|Yw+j<_l!z{Z3I zN|?Z?S3RN=jsV5Wk)IPQJZ)VhuGjrtC;H8ley;95r7sQ4y^}&nt-1t`6`>yYPO;}W z&Lq=mAGbg)PJmtU2EVXRXHQ|glys-4cZ&N~PZf}`eBxa{m#;c?*$hSGEFblEW~$}c zSAK?SAp!t?m%U&N$tXS&_8^`q#QI`uijfa+UPK#yf3JtGGS;Sks0Hz;@Io_Um)aJh zk$5=fiZK2-Hygbjs7b=#7i&KpvoVWlau{6lc9Ts$xw9{t8a&U?c9IZGP&0kh66z=_ zKp2m!$Vy49>=4mY@N)4Dx_nIcj^jYqxS&3WtA2b?X54&8*4s!MSRCqzlh$=8Z$vQ( z?Qle`yE@LAP%9h*RXu(Kx&Nwj;()PT`|{FX%n=s)3t_*cL9$SZe;f2%sT|YSL-o1_ z3excd{YPK5(CyYcGrt(Moe&f7FI`?CYh2UQcu;cSa7{lod>?pY{9NqlKc`e|p;AZe zvpt@ET?fkx+g+VZe!htD#{0`)^C+*pD2=J=I`IK4pi&vqoW_0+^2sIf!`!KD78Lz0)re{c^s zb_?G{*g2imx0r-Nna?LLT)nX(V$gId_-kibm!gEIZ#V0v_c7ONT=HxaSn+bXJKFo5omb)jx|;;C8^b8L`C_;0+)U%j zH}>;3_7*Y43o{}l)+)hO)VrzIon~62W|;677gx5(nOa6*%+MB7cn@hfJ5*SU>5LDOCG<)FmKhV`0LC_SmV^EY%0aegN=IaI#DbiUaC5~z)SwrgIGmw z`641EP7WFo;j540&XjVxv`EsKo!iTV>)OD{*ru6(oS1*HOt}-hhyA?Gqf+7fRc8!~ z6HsB6_C%dbO^ds5iMq(^ zVNsiGTSqn$AU%e7T{8uTlVK;#Z+!}sKYTQM{e@v5GG=aQ_^96M_fUyxrR47H4fGJf&%_{Qvnr?>f*_fSMmFqvjy83dA9vEfAo9Fg!=TusAY|ISV>9~ z>HSB6dfE8O=nCbaPPY27J@6wjavGAXm58dGx&$<^Wl3P9U?Zia6v>*8to)7Hb<$Tx zI!+G5)()V_D02^sN6`Lc8mM=t|JTMrK=MX%4Ng%NUKF0 z`HIz=H5|up|6@@;ZRBR~IDR|EgM9+*PsmtuUvf@p_WE~yTF-d(HcyOryZ%H-Cg;jO znCQG2L@cidM}{^=cYA-F)oH&^fuM+cD3HW_>#h`+g_+#pagRo2Ou8 z8rG94q&q8=)0xU~KEAyv@3HfCd7R5%iR`wH9;H{^cfXJ;du;NJmG<7&{2-{yDpswZ zqU2-=&YsFK1Itu$7-GKXr;kw0t`35!4RqD3XdO0cE?onKf7jPFxIA++h$cH$2ngC$ zm^Y?=P{}*S$0Or1(9wkc!Nmi^-schIe6SsI(ls{q%&GVFH@Z5e@a13qEa3gIx;vY& zdmp$Gk9jcepZUH4rdVxwS7eOi&k;t^wtrbJpJg2bB}^rx)&YyM2sH;DrmWOjb+d_toB_#<{&;-R@pt?sVIh-VkL}IgXqYW z_`}#t26hB(-C16{JElf9{-WbW{EL4AP>nrUnL=!3&4hq1*yb$OU*gwB5{_u2B9t*5 zR`a9h?8w^NZGo<>Y;u?~Whb37#KVMN1=XNC=f}oq;Q1<44);kNLBAOmAzk{*Wo=ME z13VM(_?RtDM?+J-+!ShPT!t~azQ;p}Y*a0C|K;fP3|#{tl7j|Zs45_Jl`^0Oer09T zjDN6GB301d_JqB6+i6AM1iwDm!_QRMEg$cCH5?N#Eo0$!P_*B^wJ-$&Q3DGS!>vX5j z0is;lEd3_6%K}0Q5N~aIc!pRUn?ac8KACy=6lu&Rpftr2SpS`GGgD|OepB@1KgJyB zB;?*=i#u30as=?A@4sw`K&jCl*v-WR#DjLLI%|Ade-cJLW9+AF#El8VUBsZudeaAX z;SbGuX<MKBpv-F!J96eB)ZI~)=EaX_z z_g0t%hU7BT!Koa}KLm`v4FvqDJ1oyrQ#+&4joDWjDZg5vo z)#mefnblaONfCb6PCV4ZpsdJ)+F<6D0!S~5Txi}iX#(@y-nw4GbM5Fhws&#L&_va} zjTaCS#m?vWi&jz+d|gSP9^mluh~yi;t~4w$-YS2GXP{@S+ki9&s}L^JSs57P8t4s zguu-vDFz9NM$QBO>X_c2z}RI_q!v1oO)IS{p*=M?@qK~%(|6C@lyK;$`(^+u;$-7V z>ehn00+cr^FL2f0WqyLpQCU{G>S(ksrYv}MpS%4u3 zFR)ngPuUbF>`t>qa0y|GwO=DQF{n*><(+Gu2!6#NfWzJ9>-IWzPFUA zQ(X_nwe64o3;t-oHy%Y-u2;l;L&yy-KFe#`AAX>Aa@Uy4n&pZazQV3G`Hni~$u_HF zJGHRzE`Zj{QmJuk^5*E)w%B|+S;9p}d5ORW?I!4-ClP!wm+RgDWM|WLPSvX3;}alZ z;%!Ti5fv}@URlM%jac@qpc}cA>`%y0&I*?A;OG3Xf8{*XsXp=Z!JF^1zD0{@5Byh% zd>6|bz*fKB6Swil=U%BO)H)vK%MudOi`NVV;`Cp3Ehp!DxOlk5mE!wH_^{*48P(0R zVKj9kfW~{<3WMPxkEI+Wn5e;ID4&jzw{NZq(I~9g`8Vy%O2Rt~2TMZsOj&`SDcp_| z6RpF|R8x%*6U(|9yZE^)4Tc}499*M>xYt%s-OqbG9$gT9X2--7+)h@HCKlb{87cjT zS{=T+ei>D$-kJ%M&2sU8+hzx|p2CgApsC7=Rxb1j3wsZb$R4i7Yh8^7srbF195%S* z@yXiLH@;P@@^Eux1`L;EDkx1R0htG+>xWS;{)pMA9KI>bP*|0k(nT)jKgW#>xTvt_ zWjb3D70)Sa$?7(vz(}U2pD-)?j$NqWoiF1(?_CkYz{KBvfHnxQ@QRFUEyU6U`tnHv z8nzx{)O~GSlPFA8RXx%ug() zNua#J^^)k=yhoAUupALxyAK`Q`#+p3W7OGETv&%E-E@%m`&WT-t-t&+aM2%z021Z?wut}-2 z4m*X&@BNgdr~uYvprzlP%>YYD42_(RWD^(DNG!Sb1>_n7YB6Aqowj*E_EF+kSvR-u zJ%x;Uy3-QnynD=8BCa}_C(c;a@BOV$pDd>>2?i=~VEW}_m&bf!7*AhevUXrnCpL5X z@g3vdrEVYCcD-My+;%MQD}9!M_1IO9x@_l1E@DggyKa5@kI>40;{00<{^ZVVdg9Dm z&OH9Mzx-dHN9N0xum>-Dn!ffAoqOZO7fz=4KeF*%%fO4^78HeP-b=a*OY($+aV*^Fh5Bx1rV#y4HFjxosZ+4YD8w{;-( zAMijS#xEfUFHu3XIi71+XOeS_^?n3ylvgrxgoOeD?y;otU-YfI$)v&}m(DT9O!AY| zautqD@*=3k8|o?ye;*Wwu;!qBi)ldic?8XQqc+%zBi4?^dj0R2W-Z<5sb@@o4^CK( z5Os+G`;Gp1`HK3)g7gxQRGksd!9X$w45Vyc&ouaty0MvGFL`CNeiNf4WlLW#L-Y4e z1KW&6xUu@ZRbzzjAQ!|eE?9p1Y%yE@`hR-ek6m}&b^HZ-c5cjm+0x6mgKOx_wcoMz z+rI45%?D2}p2CgyspZb(C=UN&oSq##TMp_e#ghW8E<&6mUyQbkX@d((t%>=%@zX9Y zAl6SCtS6g&%ulInrNxr;LzQmJM*LEYvI=L1MZWM>6{ol<#X8p0-%{@kCL9n@H?S`w126=&U=Mr;co$ysX z+LVdw@rY~QflW)YYXH!4skaeet^;YjAqrN%rTS`GWH{5OIYaK;Q1_`u>Wc@o&D;5J+_P~97uPOv#LR_ByaKEj80V-S{8cxZRCQCf@fOdL#`bX@_XY#v{tFvZfNbX1 zVvPZ}A50iiI&NiBUoYdl#UX`hVkCXo0KMi%-I5CjNDm1mfPPG=hfaUq~yJAoCQn zoiXpfeeD=pIMT0aIo3-0LEb0&#c8WEATeeP8R`%>QX3k?y5`07;)r#Y;$xvX(xG1e~Ri{9#k!~(8+WeQ*&tdMS9>#F&E;Y;9rk5 zCZ%mkEk-#8^O%!)vEa4X@VBv=gBL}tpOWa@x@mV0wBxgblb>Hq7f;^t)py^0%S|`! z+!|G05rjQ_Wf9*J_=^92a@E%2{6&vHwe~yCZ_oZBcLGmzeubp(S zoLtH=R$UKbA&1z&u#W4Z^{395F}!FLYdP?Rg+=KOiGTA={)WdfcTeihh=D%KHlpqJ zNc~qyVtl^8$FXVD`;`oUm`jyD$@Gyl2k1I($^HK`5u{EKa?h>|iWUrzT6 zqOy5J`Mw$qkaD2AgCiAwgze3qii7UNOW2{J=v`FSmB|-m5-M=Mk^Y8ajP>PKyLGU5)MQMqJ}IY@9j9Z8^Rs;`fm#&9d!FJ?h4esuu0^8$)XGLy6pRe1x&x z*0JB1m@DTUa|uVpG@sOCu+UZaS+H{|Z!Z=eO6Dlndh#y4#X#CGYB5bbIQFw0ucZCL zr7zrpDIET>X4->zi%DD@$lfpFSh5*2!)~yicjdM<200Cu8*zgb(+3WGw>W=(qQ&j_ zb^Md#M7*LX9!0M}!o@dz&$m5s$re1Nx1OA~qcNW`=1McQl959}W@Qbm@>!R2; zWqc_&x~CR5%!#LL;9LJ{=D@pZjcmsqV~#NC&mFFPs~j=s6Z_~L9p;8~F8)alu$16- zF5*_tyywdra|LSv5+%khW}NddH+EagxB4Ve+DH=b-AT9t!E3L2QxG_flb`IpQyUF&V>$aEO)|oTi%{Sbzc%?3GBt1-C;WYj5 zhd(_3Q~%GqU$?QoIlbev2Yy^##-dCGW)-WICa9)zcQ*UBfqq;LVAcS4ZdH7eV^#3=2R0}d{2fppx zIJWnJIAe&XY(HToxt?k!(?=R~QIzl-(;ib&POs_UvNHnr$2P#)0`D=~vDF!YYHS=| zNp7GDpWRc_uM`o`5Jg7Q+MNyH!hwyb*|gu|I!uBOFis#ZmC-`flOV@gwTzx95e8$ zhacD7iwC)lKCHQ-BWz>vJ0{(+F`n~ka03kGn1$>27yo)aF?6wTtvtj3jZPurUsyicQ#g})}EOhTAky~580F2`ofn5<)eVc;*B zb;iHW#WAgW|H&j{Bi7;~k5n%cxj{htnCuTmj2-6~XN>f`rRvRl`oL{nGRZF!tA2ym z%}yT!@YmRx!*R@fct}@9gW*ELc;Zx#3kUR=AC6brg^`|PLl>3{4QP$Bi4*H?ir0@~ zYQxhHCH&EOjxoaz5Q3#=%^m}PY!Im2WL8Y=-Bkg!PCn#XV#BSr?ay-t>^G0JA%*_Hf(c!9qmzY~l9c(Ril@#xs+T_6EvChCzr4TgT$D7zlZaJa#?v+x zrj>}Z9(CKQ=ll=@DNc-75$d58CYfZJYj%y(?n`w&)Ir47zND=2JXYqYF3z9hg_Yf4 z#-v0Kt^R>vUig5H^o%FW%-@ftHZ@g^%Km6j+w_~yUgI(9X z1up}>QC&{A)O-H*3;MVkDCMIr37blbY2wyN)#c$riaP0s&JPSI>lf%L0jjTez;<`U zu8g{D=FkZmcYP6~um3x6S}@BW^{Q^JX!S*3=q151CSs|RYAi}Hv93oZ=~uO2c%H7; zat<954lnFF43NfHC)MIl3|L(j?PLyHb?CwIcMZl|J7R(5jjzs&{)`uvk|r^^plH6d zRRhO{zKj{OmN~UA)b$c@gaxTH1eiACVqsYuhJ0Mn0;e&xKxM4$+(3@GgX4R*<&(r5 z#$V!{EboIn#XmE?bLH0G-#D=L)NMa{_QAXFzWZhUCy$;F`8s*k(aGcQo~)n$+T|1L zI|r9L(*t;lZQ*zIr@Rw3zjY`&~$m>ToHhGbX;BVulgLtt@;=;n^RXMuQ z1x!3hHx9^lN!Fxc2{M*Mm)wjuy6UH|BuE%u?D_!_JC;ozNpql*l7~PxsBrXAf?90( zlj$>)jNzEVG~`(2Habaru*;V+eFI67o~ZQ6=6P(f6$`13i^Mnt{XIvl#gJ;Bz=MuW zG20P$*C$;I9GP}6`*_G+2c{&PnZxtc;tON{q`=-c7qt;Q~0mbLs5YHrKb$ z{mA@ZKm8rw@g2-~RZ^UVS2Z1*9Xs~^wSV-X-*nAWPtAVkY&M&0o}2z%bue9bA*KGp zFOcNw)aVNgBrg_TEq%O?K_6ftab+n7ICo|KrFeF3#MJw1knNvKBGz44y=Nv2>w;m^ z&8|sx65D>gqpe3N#>Uv{egI~Sb%g=D8&h`%a?In#7&-UCg5*z3!+oa}WV^*Uuf=c# z9wPa9vqFkv=)P{+?(&#e_42k=UHI8W@iek48(<}|3?R6Mv4|;uE)=Qfm-BW(lG4{zyfAg}htjl>7jmkb>{c5L>(_q}ia zzHfiWJJ;}Nee~qo53)nS#c(w)j3eq+aB*0wTS?jb+ds&$J-{%BxjjaH#fElZM%ZMJGnUd*TIWU1981M06KHJ3TdeUT zJL0)7W&{cguKoE!ILBzmaVKKGx{~y1Oxt_JVp5)K_}aYISL2N33SZ=5W5&M$jxOqC zl6-U~J0^ef*iN74x+=CyPszoN=Yosd)oWKTZ@=YBYjGpRW93Va@ZjraU;R7J96fYs zGX2!uhrb02-8=B?dn4NUug5c&%@3O>JYA{qNdOs%(V^f1qD~O-gRaM)oCb%^PRkg| z^?bApyRyhJ#=m|Qf7U!OJvL(fr?C?13|pMPKc}&UXt3!QjtqtKQH%i){nRw4)FZp)< zJ2A+b8zkSmi`4{v#j>e|XV{mNQfz_NikQs|-*of)rkrLE& zd1tVkoxnsszPzOSzO_Zy9skf1zj*H4x$gAo)B0_nsQ#rzzCOOx2rJlhaq{sC7w67R zy2TEEO$C<+9+7QF)H3~!Nv{|?gHe2hubhF+ZX!C0*59)I&ZW_IA-r5$%xalqfGNcp zv#!fNb?0fEnq&TIS#p~@=Bl+vd=)#Va#vvbF=JlksEK8M;sj$|+u$PM6aT3v0*4ZD(g^!o`hW0r^s0+(>@N zeM!<&CvRF@xN&Z4=YoqKLq~NS-Ogtwi|!#@E{~v_dky3!F6M*$6`8oGT8XDQE-d|e zR9-rKj z96NUF{D*$>U2k~e)a>iFw`X6q+@AkYy?o>U`2eYxszCZB4Y_~m$14&NS3Yfja6slu zJ4xLY&DTW0A;VT4^H~RmY~68v4UgyA!tidOM4n$RD+XI1R;V6>fstem$T)JJ6VgvU zE!J?1U!uwfNz%dzqTR+RnMX3FUVg^3#qCQ*(qL@M22ZLt`qMU6Of1-A=ArN3@DDRE zTr7-LjCFHgY3De!{ZhAH9%})LoEp=T07(6cR*TK?gn>_WbBr}kE@P5vr>SE;j-?M0 zuREa5x{pjJ%X=2{$)~oya_7gl4{e8&nIGbdSwQk7?`l*wE{}vXqPolR2_8=EW+@D~F-N zNyhw)*>i!D3>v>r=UU>7aj_yfM+*z2F_iR?Tu%!LsIZh=aMDNe`)iJS0Td<|D`7yk zg@NQEr1#+D!vSJ`mb0(J z1@;D9#4}t>y2sVcL1}-Libs^T7cDL(e50R|#J}V#EbNeR5`<+8x;|Wh!UZYn5{s8G zkXne!m-%2XbXp8jiWQH1{|z}WbQFqH*E}PK)GwGZC&z?k0OXFax+v(5o7)bHR0~|g zu4~Nr0|eSFo>Uku4wQXd^J*MkjRbJF6FdBi%#9NB#hN@Gc0ki2MB2MX3$l38!O zz(FT2FwPlb^(%jhVUCbo(E0^#awp##SzfsOPGaY;UHjGJ|Ho$^e&mWrzNFu4+lzet z?NdV!-}T3*ivyQT4jw+eIDP8iWwY7rbNaGm*PX(ptaS$09{=CSb>ic)%$p+ABHi@7 zFdG}Wg9Fc)Sr;)Si1m22`o?PGWY*Cl&`4uQ))fQErer)ZmX&X|ozimgS;^i@Ec_FK@6RUkDn7>SjA5?z|_C0tCJ=)C{w*vXu<>AGD>XzM^vm4!`zwp>E zoPFP4cptKuy-zLaIPTM)G(C3g!}F`Ix@h{BKk${mWw~?UHJh8$Khky6ci_Uj2v6A8 zvs-dk=Kp7*{naXVSd@O>E4sbtrqpTCW{xlz5IpYocw z>R~kt8L=9ZMM3PC^6-^xUw#tiu8cW_VF1*>+=M}y*uaNgogkQ`m(-Y-3)1a)U2!?Zly@>$te-gVVEk8S&t$?t>rvpue2iDe=h?xmPrJ(rK6O!GVoB~A`}fS^q*C_p?+r2Repo~A%|X}r^nu-#xr=l? zj!j9XkK|8}T~~2z)cvI0c5I}+^J?lfI#-FldUNgf@~@M0 zJKbMkgbSP(BrOcobFuRxjBWWjFK{4Z{NC0WtHl74#6^O$&>QxU!ZbR`i;U)? zdaRp^J6L0&4r9c$3z6f*)=Nk)TJV$nQab7+mNBqjs+J_ynAAB2V^{=ZftyZ04&Q^z zjpa|AzhLpn?Zb;RM?Z7u#Pn76*LGhD;&I-m=S0VF?Oiv$7YGbl7+1gxu zt?rtg**9sS>bkcBcqJB)HP{dUpS1XWK10r)LM`d7x>7lI$2*B`nk}-7B&|t*U_Ry$(NCgLw6qMBWx~) zS{z|(AqJBWkI2t)UuJ8Qf6kYr^ZE3XIL41mF5S8J+|hGSZl2iOe%*tw+yCmv z7eI0J_vs5J{)LkN?}z^UYYrbiJo)V1lkc4IalV}1h>Q7JEEXGR@D6ivU`NCoyk1g5 zdXZr~c-ZY}!H^v%sum_P2B7Y`#(IGYOiK7jUO2V5Qi7R7cjYqx8Z3s8UNk%gNsB@& z6WiilZ;Y*ajZVqRwGe^ZxHuOK&yO)J9EcXC>CPCd@65R_BzHlWT)?VME@B$9v4!#2 zuu1CDD0A^*)SaZT(48M2GGsh39rC!qJ&MKf(~K=IIRDq?KyF^Xb;rGb?$al)KXLtH zdQ1nKyiZ>M@m%cF^QO-p{JN{xruUwC%~!0iO{P1Wvo~QO;Q!?H`fk~M4G!`ZxJ=(h z8S}ibn3@-tjL z#Lg3^-tfR1Hj##meR@8`bFxn_9O;9IpZw7eTs~V&4xf5r=Pg@XllS6czGkwReLe3c zEMk`s=&ykAeXLrvz|?DD%DaX)pZ&(_hKAy=b9m9Jg$Nth@r~}6v8}(yhR)7V^$3*R z)S_-~FuN8a>eyhv2Qkv1ZgJH1;x)!Nm%BK4iAtF{#?q-phIUiW@2io#c#-}7S&f|w zlg4BYqSX;5#*1M68W!u3p))2u>u7$Ilj%ZryXmKK>>kBS&^y)+&VH(!Z=ZbhyYD@| z_4rnI-3PAQ!8y&0eR?4%&d@%+pmh19yXcY&&m3H=9eCZ==IougXkUxV_O~!Ln=Icx z#p~92*IlcHW6~YKVldYNhP#RvgP40C#5?SjR}rnAMVPyESWy#L+D z-p$PH_x9<9qBv9g^kR`Zx{rSJ+Yg=m^hFmRJT#p?^1y*VuE=8gKj#9+PhRyt9TyQw z-K=RRCj4O9#*+NLn%~m#etTi@UyKzPUJj;J|^ylQU0s@8oyqCd=9Nx~jX$VQhE($`>zrEbB6X zZyb2+t`ooZ^w0dmpIIKe_1J#L_99c9#eI6oh@ZM%cinZVSqcg&-*nIkY3J${ZwnavcfU| z>c88T9UPds15C=k%ge<;+~<+nm$8<{s&+ETT^^@Bjx&#oO#c!!u(iNZrvtVQ9!P(N z2vmM9LQq|wvhD6j?%dq%QTL)M3|KE`>7Ps=MV$Yk@;#IJ;?{1hyLW#1*^g{&9qfMT zZ{PL66HlD$zV%zLU*3H4%^lAF{-X90Qk=1UddcZ^lLrsK^=)gjhwr`Ut0#-uRkO+3 zwUeFcH{ecs4HgQ0;<`4)0tafpA4kbW&I_4e#`1=z^kSo!vAi2WUbKuAL-*n-9Zd2| zUoCJ=d=-W-p-KF0j2;v7&<=pKAi?29hSJ!!HmQFZ8$L@Dz)l?$`QWU7krUaO(1)*L!4?U@t_gu)L9DF$`)yv|zjzA#B_kdBLa! z4$Qb}QBO(I!U3v92CRCGZftc?@Y3(Gx$@NF5<+u{g|4ZWy6Qo17fjl12iN=HLAA$(^G+-2=b*0WD-7`N&6>`|q!P z@hQ&cKD}IM`tG-N7wkNG{z@8~r`G?FyxsJ-;x>5$ZfY%XScv#-0{y=*T!nIs*c`e1?^_^R8Z4;xA^GX5?eZTbfGd|u#; zC9J+|D{tfyOUroI)1n2v#`gPa)SZ*vUVW;{MNm0d;Do^f*I|JpW;*#G<@~_%pHgmI zw0(T`-+k`UQ|C`DKlnO*aIpU}^kqUkxBK+6q7SdVWAc>;j$SdFo;!E(m#u9~rpv9_ z<+IuJ@MLH5daP_ma06aC>$*4bZeLqW-^u@NWZuoLQFk?6ZUDn|EpBxA^5tTlZFOfL zg6Q&MXq{w?YYY}7V?sb`1sGx)Ng6M39*?vVi;`^zmqYBX7t_Q-m5Y}08Z7Mku3UC! z)$LI#uj7KIMF|VqBO$x{fH^grP3}hQaTGhXKJD)1e=|AXSl+X`^AfQf98O_XlSu3jOFfn#%pofLyoapkU+wqq!+Cg zOJ=MeJ64poD-)jikfi>-I7H!hy=IIKir|QW_c-~hi231fmydaf@W?5-N;4i0)n#b009Ses1G zQsSlg(?Dzie;#>Hp}_h1bask=3KO7Ef)wdATz^I$2DvSa#iY zcnNkHy1cirRl$w_6~y4BmVRAK3k$9QUIbc1pbXxp1Gy_(y}*sI;1)wUScpKzYP^=Y z@IaEr_54y*m>6<^sRa%yF@6ckm_C4*OtuJvyieF^cNg~canwCIpHJ?#%{4pk{=kG^?ci5?kd-^TPp>HAb+Avb0{Y#P$1lF{lIe7@IQ-h_V&lT) zbm!u3F})fK+(lSyUdNjq{6>B#xZX{!U=I8j-~w33Ww4F~;}GI#V7fz$!Fd5C;v3*O z7C5ePTHCa!fc#Q-e)542d|<*3k6%IgXaDOv^%8aS#KqSxC)?BI=K34)s_c?(y1aP4 zoV*_0(M4DwUe6T)_O--fA;7ZP9YO=B#gK((`8EIg<^2$~AO$9*W0qbFX^*j#q&7$X zSlq}9V73{<%gu+l(BUQd1G1-+6Igvu&$>B382B7stv@wi-?@LWGnpK`a{E)nOuq4R zCn>Ic`>EyKm)@;+=J;M3j!`#d`?}wyz3ND3b)R1Kr1ShON4twIxMVVY>Y>S-Xm{P> zTJ$KFU{N~)`Fbp7M`m63ddBA6>{@1Nf$Js*vDmPqV>ic_c&ayEv}%FFhB~=jtj1DU zu=6On#H?BblNVU*`UQ@1I=P?!_W>4Rb!DjW)O0d?0KM!}(|LD4G3$r6K1uodKXCZu zZMWSvIdR(u7yF-herXV|n|=DyB%OP{k9YIUH&>@e-}~SH{>d#jAJwZr^Xd7)?(Fh*wp+<>%rf%HqaAln$Q zL&5@!;?8_JJ4fHzeEuZmtlK#~pLJW41Kp_u8ylP5fyvhLbx%Ic*frNY8E^Fev3t;? z1@ZqYNOjzqk@gp}FG1R0;Pz=ux+ebF`#Y^c|MtJ!vwqEG*UU~m{M5nu_H1o?Ywh4{ z>)^%njjicyYZeRKG8Q;?dPwGXhOsV9wZM5HQ#UqWOxc}vi+TJS1_;;=2@Ab{Hl1kE zLYo*1+`K!DZgFd}-kn-5mYX|k%dPH`%`=Ssu6G{Yy#4s`$?^B!va`RC?bBXVSH(W< z)3c<(HRBF2%?Ud}lRLuCN9Y)9QR1hTCN5l*&EiGcU%>Y1S<+ Date: Fri, 15 Sep 2017 09:54:55 +1200 Subject: [PATCH 326/504] Fix history crash --- scripts/vr-edit/modules/history.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index 5fb0712db3..039f078ad0 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -90,6 +90,7 @@ History = (function () { // Limit the number of history items. if (history.length >= MAX_HISTORY_ITEMS) { history.splice(0, history.length - MAX_HISTORY_ITEMS + 1); + undoPosition = history.length - 1; } history.push({ undoData: undoData, redoData: redoData }); From c6af82dedc2def76035728d1e2fcd89b03fb2f45 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 09:55:14 +1200 Subject: [PATCH 327/504] Increase maximum number of undo levels to 1000 --- scripts/vr-edit/modules/history.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index 039f078ad0..92cd4d047f 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -45,7 +45,7 @@ History = (function () { } */ ], - MAX_HISTORY_ITEMS = 100, + MAX_HISTORY_ITEMS = 1000, undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. undoData = {}, redoData = {}; From 9da3ed0cebbb7b3daa5c01fd2b60ea02f2cdca24 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 11:24:33 +1200 Subject: [PATCH 328/504] Emissive Create palette models --- scripts/vr-edit/assets/create/cone.fbx | Bin 19056 -> 23740 bytes scripts/vr-edit/assets/create/cube.fbx | Bin 17696 -> 17564 bytes scripts/vr-edit/assets/create/cylinder.fbx | Bin 20240 -> 24364 bytes scripts/vr-edit/assets/create/icosahedron.fbx | Bin 17904 -> 20028 bytes scripts/vr-edit/assets/create/octahedron.fbx | Bin 17024 -> 17420 bytes scripts/vr-edit/assets/create/prism.fbx | Bin 17392 -> 17548 bytes scripts/vr-edit/assets/create/sphere.fbx | Bin 29312 -> 46572 bytes scripts/vr-edit/assets/create/tetrahedron.fbx | Bin 16720 -> 17148 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/vr-edit/assets/create/cone.fbx b/scripts/vr-edit/assets/create/cone.fbx index b883b042c56337c43754ddd3fa7614304cdb7c48..d590ee04be59c63f7b77610f5902c2b05cd718dc 100644 GIT binary patch literal 23740 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 NwP|{-Z-mOU{|6d!3Ge^_ literal 19056 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 CO_Y=X diff --git a/scripts/vr-edit/assets/create/cube.fbx b/scripts/vr-edit/assets/create/cube.fbx index 530122b16efab5be9127898ba6f25bcf7d28872e..fa079219fe04503653973f859f3416d3c0212dfa 100644 GIT binary patch literal 17564 zcmc&+2~-qU)_$l6ih?ojQNhuO;sz20)D~Gp39GcAiAk{NE}&v}Ra;#xpaL#2CdO#Y zBu5h!@+Wbee;hQ?iA%y_Ox(tWQARN?;7V&W0wSP*_MBVqb$7K*H!aS8{ )m3%B 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 Lbi1KjKho_#Ti%C= literal 17696 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 diff --git a/scripts/vr-edit/assets/create/cylinder.fbx b/scripts/vr-edit/assets/create/cylinder.fbx index 250ce66773f3f970ef26c58798ac070956746bad..319cbf0a6d40d7ed2c5ac0bd05b8593106b10546 100644 GIT binary patch literal 24364 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 MZB~mI$J4r@%XV%$SNFV}LXsfM{ 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 k*{AMK^Mn_F5AMHJE diff --git a/scripts/vr-edit/assets/create/icosahedron.fbx b/scripts/vr-edit/assets/create/icosahedron.fbx index 52f948816ba00e8a1722ef375621f394d1260460..3103d6b2d6709bb33acd40fd9872bbe570a459fb 100644 GIT binary patch literal 20028 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 Q)5{AU+-*^)OYhhGzf}QULjV8( literal 17392 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 diff --git a/scripts/vr-edit/assets/create/tetrahedron.fbx b/scripts/vr-edit/assets/create/tetrahedron.fbx index 23424e0b47ff42bf2d7a8aa10adfc89aaa1d0dbb..60dd70edb1b9cd279a1b0cdb213396950e9eb994 100644 GIT binary patch literal 17148 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 Date: Fri, 15 Sep 2017 13:43:53 +1200 Subject: [PATCH 329/504] Fix not kicking off physics if hand is inside entity --- scripts/vr-edit/modules/selection.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index b06b75fba9..a36e69aa96 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -29,6 +29,7 @@ Selection = function (side) { scaleRootOrientation, startPosition, startOrientation, + isEditing = false, ENTITY_TYPE = "entity", ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"], ENTITY_TYPES_2D = ["Text", "Web"], @@ -207,7 +208,7 @@ Selection = function (side) { DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; - if (entityID === rootEntityID) { + if (entityID === rootEntityID && isEditing) { // Don't kick if have started editing entity again. return; } @@ -247,6 +248,8 @@ Selection = function (side) { // Stop moving. Entities.editEntity(rootEntityID, { velocity: Vec3.ZERO, angularVelocity: Vec3.ZERO }); + + isEditing = true; } function finishEditing() { @@ -283,6 +286,8 @@ Selection = function (side) { if (selection.length > 0 && selection[0].dynamic) { kickPhysics(selection[0].id); } + + isEditing = false; } function getPositionAndOrientation() { From 4b63d8b7d91eb770b4e5815c2a648df186d27375 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 13:48:01 +1200 Subject: [PATCH 330/504] Don't kick polylines when apply physics --- scripts/vr-edit/modules/history.js | 2 +- scripts/vr-edit/modules/selection.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index 92cd4d047f..c9e2f5f934 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -52,7 +52,7 @@ History = (function () { function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web"], // These entities don't respond to gravity so don't kick them. + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine"], // These entities don't respond to gravity so don't kick them. DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index a36e69aa96..d0c8ca3ebd 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -204,7 +204,7 @@ Selection = function (side) { function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web"], // These entities don't respond to gravity so don't kick them. + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine"], // These entities don't respond to gravity so don't kick them. DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; From 1f63425e9f032edfaa73654402fb419a1ddd86b1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 13:58:58 +1200 Subject: [PATCH 331/504] Don't kick particle effects when apply physics --- scripts/vr-edit/modules/history.js | 2 +- scripts/vr-edit/modules/selection.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/history.js b/scripts/vr-edit/modules/history.js index c9e2f5f934..88537710b9 100644 --- a/scripts/vr-edit/modules/history.js +++ b/scripts/vr-edit/modules/history.js @@ -52,7 +52,7 @@ History = (function () { function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine"], // These entities don't respond to gravity so don't kick them. + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index d0c8ca3ebd..c0d3333a77 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -204,7 +204,7 @@ Selection = function (side) { function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine"], // These entities don't respond to gravity so don't kick them. + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; From 455db1ac829c94850e5eff716183ed91fd178183 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 15 Sep 2017 19:11:04 +1200 Subject: [PATCH 332/504] Tighten up handle scaling to keep handle 1-1 with hand/laser --- scripts/vr-edit/modules/hand.js | 12 ++++++++++-- scripts/vr-edit/modules/handles.js | 17 +++++++++++++---- scripts/vr-edit/utilities/utilities.js | 6 ++++++ scripts/vr-edit/vr-edit.js | 8 +++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 16bd033a74..174068a3d1 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -111,6 +111,7 @@ Hand = function (side) { overlayID, overlayIDs, overlayDistance, + intersectionPosition, distance, entityID, entityIDs, @@ -169,9 +170,12 @@ Hand = function (side) { } } } - if (handleOverlayIDs.indexOf(overlayID) === -1) { + if (overlayID && handleOverlayIDs.indexOf(overlayID) === -1) { overlayID = null; } + if (overlayID) { + intersectionPosition = Overlays.getProperty(overlayID, "position"); + } } // Hand-entity intersection, if any editable, if overlay not intersected. @@ -198,6 +202,9 @@ Hand = function (side) { } } } + if (entityID) { + intersectionPosition = Entities.getEntityProperties(entityID, "position").position; + } } intersection = { @@ -205,7 +212,8 @@ Hand = function (side) { overlayID: overlayID, entityID: entityID, handIntersected: overlayID !== null || entityID !== null, - editableEntity: entityID !== null + editableEntity: entityID !== null, + intersection: intersectionPosition }; } diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index 4c11cdfd8d..f7400c6561 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -89,11 +89,11 @@ Handles = function (side) { ]; FACE_HANDLE_OVERLAY_SCALE_AXES = [ + Vec3.UNIT_NEG_X, Vec3.UNIT_X, - Vec3.UNIT_X, + Vec3.UNIT_NEG_Y, Vec3.UNIT_Y, - Vec3.UNIT_Y, - Vec3.UNIT_Z, + Vec3.UNIT_NEG_Z, Vec3.UNIT_Z ]; @@ -109,6 +109,14 @@ Handles = function (side) { return isAxisHandle(overlayID) || isCornerHandle(overlayID); } + function handleOffset(overlayID) { + // Distance from overlay position to entity surface. + if (isCornerHandle(overlayID)) { + return 0; // Corner overlays are centered on the corner. + } + return faceHandleOffsets.y / 2; + } + function getOverlays() { return [].concat(cornerHandleOverlays, faceHandleOverlays); } @@ -126,7 +134,7 @@ Handles = function (side) { if (isCornerHandle(overlayID)) { return Vec3.ONE; } - return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; + return Vec3.abs(FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]); } function display(rootEntityID, boundingBox, isMultipleEntities, isSuppressZAxis) { @@ -348,6 +356,7 @@ Handles = function (side) { display: display, overlays: getOverlays, isHandle: isHandle, + handleOffset: handleOffset, scalingAxis: scalingAxis, scalingDirections: scalingDirections, startScaling: startScaling, diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index e8c79b9886..07ee894731 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -20,6 +20,12 @@ if (typeof Vec3.max !== "function") { }; } +if (typeof Vec3.abs !== "function") { + Vec3.abs = function (a) { + return { x: Math.abs(a.x), y: Math.abs(a.y), z: Math.abs(a.z) }; + }; +} + if (typeof Quat.ZERO !== "object") { Quat.ZERO = Quat.fromVec3Radians(Vec3.ZERO); } diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d94c84e9d9..94801335cb 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -386,6 +386,7 @@ otherTargetPosition, handleUnitScaleAxis, handleScaleDirections, + handleTargetOffset, initialHandleDistance, laserOffset, MIN_SCALE = 0.001, @@ -544,7 +545,10 @@ handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. scaleAxis = Vec3.multiplyQbyV(selectionPositionAndOrientation.orientation, handleUnitScaleAxis); + handleTargetOffset = handles.handleOffset(overlayID) + + Vec3.dot(Vec3.subtract(otherTargetPosition, Overlays.getProperty(overlayID, "position")), scaleAxis); initialHandleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, initialTargetPosition), scaleAxis)); + initialHandleDistance -= handleTargetOffset; selection.startHandleScaling(initialTargetPosition); handles.startScaling(); @@ -624,10 +628,12 @@ // Desired distance of handle from other hand targetPosition = getScaleTargetPosition(); scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); - handleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, targetPosition), scaleAxis)); + handleDistance = Vec3.dot(Vec3.subtract(otherTargetPosition, targetPosition), scaleAxis); + handleDistance -= handleTargetOffset; // Scale selection relative to initial dimensions. scale = handleDistance / initialHandleDistance; + scale = Math.max(scale, MIN_SCALE); scale3D = Vec3.multiply(scale, handleScaleDirections); scale3D = { x: handleScaleDirections.x !== 0 ? scale3D.x : 1, From 6c5a1c0460e9ee457810b41e33aa2c6afd7f0dd7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 16 Sep 2017 15:45:44 +1200 Subject: [PATCH 333/504] Address laser and controller interactions with HUD overlays and tablet --- .../controllerModules/inVREditMode.js | 111 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 1 + scripts/vr-edit/modules/laser.js | 31 +++-- scripts/vr-edit/vr-edit.js | 3 +- 4 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/inVREditMode.js diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js new file mode 100644 index 0000000000..c48955442b --- /dev/null +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -0,0 +1,111 @@ +"use strict"; + +// inVREditMode.js +// +// Created by David Rowe on 16 Sep 2017. +// Copyright 2017 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 + +/* global Script, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, + makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + +(function () { + + function InVREditMode(hand) { + this.hand = hand; + this.disableModules = false; + this.parameters = makeDispatcherModuleParameters( + 200, // Not too high otherwise the tablet laser doesn't work. + this.hand === RIGHT_HAND + ? ["rightHand", "rightHandEquip", "rightHandTrigger"] + : ["leftHand", "leftHandEquip", "leftHandTrigger"], + [], + 100 + ); + + this.isReady = function (controllerData) { + if (this.disableModules) { + return makeRunningValues(true, [], []); + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + // Default behavior if disabling is not enabled. + if (!this.disableModules) { + return makeRunningValues(false, [], []); + } + + // 2D overlay lasers. + // These are automatically enabled. + + // Tablet stylus. + // Includes the tablet laser. + var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTabletStylusInput" + : "LeftTabletStylusInput"); + if (tabletStylusInput) { + var tabletReady = tabletStylusInput.isReady(controllerData); + if (tabletReady.active) { + return makeRunningValues(false, [], []); + } + } + + // Tablet grabbing. + var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearParentingGrabOverlay" + : "LeftNearParentingGrabOverlay"); + if (nearOverlay) { + var nearOverlayReady = nearOverlay.isReady(controllerData); + if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) { + return makeRunningValues(false, [], []); + } + } + + // Teleport. + var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTeleporter" + : "LeftTeleporter"); + if (teleporter) { + var teleporterReady = teleporter.isReady(controllerData); + if (teleporterReady.active) { + return makeRunningValues(false, [], []); + } + } + + // Other behaviors are disabled. + return makeRunningValues(true, [], []); + }; + } + + var leftHandInVREditMode = new InVREditMode(LEFT_HAND); + var rightHandInVREditMode = new InVREditMode(RIGHT_HAND); + enableDispatcherModule("LeftHandInVREditMode", leftHandInVREditMode); + enableDispatcherModule("RightHandInVREditMode", rightHandInVREditMode); + + var INVREDIT_DISABLER_MESSAGE_CHANNEL = "Hifi-InVREdit-Disabler"; + this.handleMessage = function (channel, message, sender) { + if (sender === MyAvatar.sessionUUID && channel === INVREDIT_DISABLER_MESSAGE_CHANNEL) { + if (message === "both") { + leftHandInVREditMode.disableModules = true; + rightHandInVREditMode.disableModules = true; + } else if (message === "none") { + leftHandInVREditMode.disableModules = false; + rightHandInVREditMode.disableModules = false; + } + } + }; + Messages.subscribe(INVREDIT_DISABLER_MESSAGE_CHANNEL); + Messages.messageReceived.connect(this.handleMessage); + + this.cleanup = function () { + disableDispatcherModule("LeftHandInVREditMode"); + disableDispatcherModule("RightHandInVREditMode"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); \ No newline at end of file diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index e8b07c623d..87c349523a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -26,6 +26,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/overlayLaserInput.js", "controllerModules/webEntityLaserInput.js", "controllerModules/inEditMode.js", + "controllerModules/inVREditMode.js", "controllerModules/disableOtherModule.js", "controllerModules/farTrigger.js", "controllerModules/teleport.js", diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index 9c1a6f5bb3..afd67cf364 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -169,18 +169,27 @@ Laser = function (side) { // Normal laser operation with trigger. intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, VISIBLE_ONLY); - if (!intersection.intersects) { - intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, - VISIBLE_ONLY); - intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); - intersection.overlayID = null; + if (Reticle.pointingAtSystemOverlay || (intersection.overlayID + && [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID].indexOf(intersection.overlayID) !== -1)) { + // No laser if pointing at HUD overlay or tablet; system provides lasers for these cases. + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } else { + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); + intersection.overlayID = null; + } + intersection.laserIntersected = intersection.intersects; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); } - intersection.laserIntersected = intersection.intersects; - laserLength = (specifiedLaserLength !== null) - ? specifiedLaserLength - : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); - isLaserOn = true; - display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); } else if (uiOverlayIDs.length > 0) { diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 94801335cb..e0a54578ff 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1399,8 +1399,7 @@ // Communicate app status to controllerDispatcher.js. var DISABLE_HANDS = "both", ENABLE_HANDS = "none"; - // TODO: Proper method to disable specific laser and grabbing functionality. - Messages.sendLocalMessage('Hifi-Hand-Disabler', isAppActive ? DISABLE_HANDS : ENABLE_HANDS); + Messages.sendLocalMessage("Hifi-InVREdit-Disabler", isAppActive ? DISABLE_HANDS : ENABLE_HANDS); } function onUICommand(command, parameter) { From 353bab5277e3a01b56ef488507ebe72e6fc05fd8 Mon Sep 17 00:00:00 2001 From: beholder Date: Sat, 16 Sep 2017 16:50:45 +0300 Subject: [PATCH 334/504] 7384 On Domains without Edit Access, Create Button Text still Highlights (override color in states if captionColorOverride was specified) --- interface/resources/qml/hifi/tablet/TabletButton.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 58091d9fab..ab7179edf1 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -168,7 +168,7 @@ Item { PropertyChanges { target: text - color: "#ffffff" + color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" text: tabletButton.hoverText } @@ -194,7 +194,7 @@ Item { PropertyChanges { target: text - color: "#333333" + color: captionColorOverride !== "" ? captionColorOverride: "#333333" text: tabletButton.activeText } @@ -225,7 +225,7 @@ Item { PropertyChanges { target: text - color: "#333333" + color: captionColorOverride !== "" ? captionColorOverride: "#333333" text: tabletButton.activeHoverText } From 037550f176b16d258d532297a01094008501c484 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 18 Sep 2017 10:58:38 +0200 Subject: [PATCH 335/504] Switched back to R11G11B10 for HDR cube maps because of sometimes wrong hue shifts with RGB9E5 --- libraries/image/src/image/Image.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 8ada88f008..b1b87883bb 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -213,10 +213,14 @@ uint32 packR11G11B10F(const glm::vec3& color) { // Denormalize else unpacking gives high and incorrect values // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value static const auto minValue = 6.10e-5f; + static const auto maxValue = 6.50e4f; glm::vec3 ucolor; ucolor.r = denormalize(color.r, minValue); ucolor.g = denormalize(color.g, minValue); ucolor.b = denormalize(color.b, minValue); + ucolor.r = std::min(ucolor.r, maxValue); + ucolor.g = std::min(ucolor.g, maxValue); + ucolor.b = std::min(ucolor.b, maxValue); return glm::packF2x11_1x10(ucolor); } @@ -1072,15 +1076,27 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); +//#define DEBUG_COLOR_PACKING + QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); std::function packFunc; +#ifdef DEBUG_COLOR_PACKING + std::function unpackFunc; +#endif + switch (format.getSemantic()) { case gpu::R11G11B10: packFunc = packR11G11B10F; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF2x11_1x10; +#endif break; case gpu::RGB9E5: packFunc = glm::packF3x9_E1x5; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF3x9_E1x5; +#endif break; default: qCWarning(imagelogging) << "Unsupported HDR format"; @@ -1105,6 +1121,10 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { color.g = powf(color.g, 2.2f); color.b = powf(color.b, 2.2f); *hdrLineIt = packFunc(color); +#ifdef DEBUG_COLOR_PACKING + glm::vec3 ucolor = unpackFunc(*hdrLineIt); + assert(glm::distance(color, ucolor) <= 5e-2); +#endif ++srcLineIt; ++hdrLineIt; } @@ -1119,7 +1139,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - const auto cubeMapHDRFormat = gpu::Element::COLOR_RGB9E5; + const auto cubeMapHDRFormat = gpu::Element::COLOR_R11G11B10; gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { From 41647305ed8412e9b3aaa9ff90f6007296ef0590 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 20 Sep 2017 11:11:18 +1200 Subject: [PATCH 336/504] Rename selection manager object to avoid name clash with new API object --- scripts/vr-edit/modules/selection.js | 10 +++++----- scripts/vr-edit/vr-edit.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index c0d3333a77..a01eef5481 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Selection */ +/* global SelectionManager */ -Selection = function (side) { +SelectionManager = function (side) { // Manages set of selected entities. Currently supports just one set of linked entities. "use strict"; @@ -38,8 +38,8 @@ Selection = function (side) { - if (!this instanceof Selection) { - return new Selection(side); + if (!this instanceof SelectionManager) { + return new SelectionManager(side); } function traverseEntityTree(id, selection, selectionProperties) { @@ -781,4 +781,4 @@ Selection = function (side) { }; }; -Selection.prototype = {}; +SelectionManager.prototype = {}; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index e0a54578ff..ea1ac86254 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -59,7 +59,7 @@ Handles, Highlights, Laser, - Selection, + SelectionManager, ToolIcon, ToolsMenu, @@ -400,7 +400,7 @@ return new Editor(); } - selection = new Selection(side); + selection = new SelectionManager(side); highlights = new Highlights(side); handles = new Handles(side); From 906595e4fa95ea4abc0b031c1662a93ca2c5582d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Sep 2017 16:15:06 +1200 Subject: [PATCH 337/504] Automatically return to Tools menu after grouping or ungrouping --- scripts/vr-edit/vr-edit.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index ea1ac86254..2bcd3533cd 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1469,10 +1469,18 @@ case "groupButton": Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); grouping.group(); + grouping.clear(); + toolSelected = TOOL_NONE; + ui.clearTool(); + ui.updateUIOverlays(); break; case "ungroupButton": Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); grouping.ungroup(); + grouping.clear(); + toolSelected = TOOL_NONE; + ui.clearTool(); + ui.updateUIOverlays(); break; case "setColor": From a56a155cc3781fa411f58519a2decf6f2843270f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Sep 2017 17:31:32 +1200 Subject: [PATCH 338/504] Add group selection box buttons --- .../assets/tools/group/cancel-label.svg | 12 ++++ .../tools/group/selection-box-label.svg | 33 ++++++++++ scripts/vr-edit/modules/toolsMenu.js | 63 +++++++++++++++++-- 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 scripts/vr-edit/assets/tools/group/cancel-label.svg create mode 100644 scripts/vr-edit/assets/tools/group/selection-box-label.svg diff --git a/scripts/vr-edit/assets/tools/group/cancel-label.svg b/scripts/vr-edit/assets/tools/group/cancel-label.svg new file mode 100644 index 0000000000..66e9ad4afc --- /dev/null +++ b/scripts/vr-edit/assets/tools/group/cancel-label.svg @@ -0,0 +1,12 @@ + + + + CANCEL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group/selection-box-label.svg b/scripts/vr-edit/assets/tools/group/selection-box-label.svg new file mode 100644 index 0000000000..21bed98a90 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group/selection-box-label.svg @@ -0,0 +1,33 @@ + + + + group-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 2b93c215d4..ebe8d1bac4 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -990,12 +990,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: UIT.dimensions.buttonDimensions.x, - y: 0.0680, + y: 0.0400, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: 0, - y: UIT.dimensions.panel.y / 2 - 0.0280 - 0.0680 / 2, + y: UIT.dimensions.panel.y / 2 - 0.0280 - 0.0400 / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, color: UIT.colors.baseGrayShadow @@ -1018,16 +1018,15 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: UIT.dimensions.buttonDimensions.x, - y: 0.0680, + y: 0.0400, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: 0, - y: -UIT.dimensions.panel.y / 2 + 0.0120 + 0.0680 / 2, + y: UIT.dimensions.panel.y / 2 - 0.0280 - 0.0400 - 0.0040 - 0.0400 / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, color: UIT.colors.baseGrayShadow - }, enabledColor: UIT.colors.redHighlight, highlightColor: UIT.colors.redAccent, @@ -1040,6 +1039,60 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: { method: "ungroupButton" } + }, + { + id: "groupRule2", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: -UIT.dimensions.panel.y / 2 + 0.0603, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } + }, + { + id: "groupSelectionBoxButton", + type: "toggleButton", + properties: { + dimensions: { x: 0.1042, y: 0.0400, z: UIT.dimensions.buttonDimensions.z }, + localPosition: { + x: -0.0040 - 0.1042 / 2, + y: -0.0900 + 0.0120 + 0.0400 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + } + }, + label: { + url: "../assets/tools/group/selection-box-label.svg", + scale: 0.0161 + }, + command: { + method: "toggleGroupSelectionBox" + } + }, + { + id: "groupsSelectionBoxCancelButton", + type: "button", + properties: { + dimensions: { x: 0.1042, y: 0.0400, z: UIT.dimensions.buttonDimensions.z }, + localPosition: { + x: 0.0040 + 0.1042 / 2, + y: -0.0900 + 0.0120 + 0.0400 / 2, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 + }, + color: UIT.colors.baseGrayShadow + }, + enabledColor: UIT.colors.greenHighlight, + highlightColor: UIT.colors.greenShadow, + label: { + url: "../assets/tools/group/cancel-label.svg", + scale: 0.0380, + color: UIT.colors.baseGray + }, + labelEnabledColor: UIT.colors.white, + command: { + method: "cancelGroupSelectionBox" + } } ], physicsOptions: [ From 2f9a314aa9536cb62225d0fe0272e642138d3efa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Sep 2017 19:07:13 +1200 Subject: [PATCH 339/504] Implement group selection box button states --- scripts/vr-edit/modules/toolsMenu.js | 54 ++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index ebe8d1bac4..0c3ca9fb02 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1090,6 +1090,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.baseGray }, labelEnabledColor: UIT.colors.white, + enabled: false, command: { method: "cancelGroupSelectionBox" } @@ -2423,7 +2424,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { swatchHighlightOverlay = Overlays.addOverlay(UI_ELEMENTS.swatchHighlight.overlay, properties); } - optionsEnabled.push(true); + optionsEnabled.push(optionsItems[i].enabled === undefined || optionsItems[i].enabled); } // Special handling for Group options. @@ -2703,6 +2704,54 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setColorPicker(parameter); break; + case "toggleGroupSelectionBox": + optionsToggles.groupSelectionBoxButton = !optionsToggles.groupSelectionBoxButton; + + index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsToggles.groupSelectionBoxButton + ? UI_ELEMENTS[optionsItems[index].type].onHoverColor + : UI_ELEMENTS[optionsItems[index].type].offHoverColor + }); + + index = optionsOverlaysIDs.indexOf("groupsSelectionBoxCancelButton"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsToggles.groupSelectionBoxButton + ? optionsItems[index].enabledColor + : optionsItems[index].properties.color + }); + Overlays.editOverlay(optionsOverlaysLabels[index], { + color: optionsToggles.groupSelectionBoxButton + ? optionsItems[index].labelEnabledColor + : optionsItems[index].label.color + }); + optionsEnabled[index] = optionsToggles.groupSelectionBoxButton; + + uiCommandCallback("toggleGroupSelectionBoxTool", optionsToggles.groupSelectionBoxButton); + break; + + case "cancelGroupSelectionBox": + optionsToggles.groupSelectionBoxButton = false; + + index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); + Overlays.editOverlay(optionsOverlays[index], { + color : optionsToggles.groupSelectionBoxButton + ? UI_ELEMENTS[optionsItems[index].type].onHoverColor + : UI_ELEMENTS[optionsItems[index].type].offHoverColor + }); + + index = optionsOverlaysIDs.indexOf("groupsSelectionBoxCancelButton"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsItems[index].properties.color + }); + Overlays.editOverlay(optionsOverlaysLabels[index], { + color: optionsItems[index].label.color + }); + optionsEnabled[index] = false; + + uiCommandCallback("cancelGroupSelectionBoxTool"); + break; + case "setGravityOn": case "setGrabOn": case "setCollideOn": @@ -3209,7 +3258,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (pressedItem) { // Unpress previous button. Overlays.editOverlay(pressedSource[pressedItem.index], { - localPosition: isHoveringButtonElement && hoveredItem === pressedItem.index + localPosition: + isHoveringButtonElement && hoveredItem === pressedItem.index && optionsEnabled[pressedItem.index] ? Vec3.sum(pressedItem.localPosition, OPTION_HOVER_DELTA) : pressedItem.localPosition }); From 5ec9c5bb45da21b7cbfa1503300434bf1c94fbea Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 21 Sep 2017 09:32:34 +0200 Subject: [PATCH 340/504] Fixed some coding standard issues --- libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp | 2 - libraries/gpu/src/gpu/Texture.cpp | 26 +++++----- libraries/image/src/image/Image.cpp | 47 ++++++++++--------- libraries/image/src/image/Image.h | 7 +-- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 8d4259e240..192a82dafc 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -508,14 +508,12 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::R11G11B10: texel.format = GL_RGB; texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; - // the type should be float texel.internalFormat = GL_R11F_G11F_B10F; break; case gpu::RGB9E5: texel.format = GL_RGB; texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; - // the type should be float texel.internalFormat = GL_RGB9_E5; break; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4c2f4960c7..4a588c3c84 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -686,17 +686,17 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< auto mipFormat = cubeTexture.getStoredMipFormat(); std::function unpackFunc; - switch (mipFormat.getSemantic()) - { - case gpu::R11G11B10: - unpackFunc = glm::unpackF2x11_1x10; - break; - case gpu::RGB9E5: - unpackFunc = glm::unpackF3x9_E1x5; - break; - default: - assert(false); - break; + + switch (mipFormat.getSemantic()) { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; } const uint sqOrder = order*order; @@ -732,7 +732,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); - auto data = (const uint32*)cubeTexture.accessStoredMipFace(0, face)->readData(); + auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); if (data == nullptr) { continue; } @@ -813,7 +813,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // index of texel in texture // get color from texture - glm::vec3 color{ 0.f, 0.f, 0.f }; + glm::vec3 color{ 0.0f, 0.0f, 0.0f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { int k = (int)(x + i - halfStride + (y + j - halfStride) * width); diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index b1b87883bb..2ae62a9ce3 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -67,6 +67,10 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { +enum { + QIMAGE_HDR_FORMAT = QImage::Format_RGB30 +}; + TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { case ALBEDO_TEXTURE: @@ -206,7 +210,7 @@ void setCubeTexturesCompressionEnabled(bool enabled) { } static float denormalize(float value, const float minValue) { - return value < minValue ? 0.f : value; + return value < minValue ? 0.0f : value; } uint32 packR11G11B10F(const glm::vec3& color) { @@ -319,17 +323,18 @@ struct OutputHandler : public nvtt::OutputHandler { _data = static_cast(malloc(size)); _current = _data; } + virtual bool writeData(const void* data, int size) override { assert(_current + size <= _data + _size); memcpy(_current, data, size); _current += size; return true; } + virtual void endImage() override { if (_face >= 0) { _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); - } - else { + } else { _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); } free(_data); @@ -433,11 +438,11 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { data.resize(width*height); dataIt = data.begin(); for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = (const uint32*)image.constScanLine(lineNb); + const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); const uint32* srcPixelEnd = srcPixelIt + width; while (srcPixelIt < srcPixelEnd) { - *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.f); + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); ++srcPixelIt; ++dataIt; } @@ -446,7 +451,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - std::auto_ptr outputHandler; + std::unique_ptr outputHandler; MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); nvtt::Context context; @@ -1086,29 +1091,29 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { #endif switch (format.getSemantic()) { - case gpu::R11G11B10: - packFunc = packR11G11B10F; + case gpu::R11G11B10: + packFunc = packR11G11B10F; #ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF2x11_1x10; + unpackFunc = glm::unpackF2x11_1x10; #endif - break; - case gpu::RGB9E5: - packFunc = glm::packF3x9_E1x5; + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; #ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF3x9_E1x5; + unpackFunc = glm::unpackF3x9_E1x5; #endif - break; - default: - qCWarning(imagelogging) << "Unsupported HDR format"; - Q_UNREACHABLE(); - return srcImage; + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return srcImage; } srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); for (auto y = 0; y < srcImage.height(); y++) { - const QRgb* srcLineIt = (const QRgb*) srcImage.constScanLine(y); + const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); const QRgb* srcLineEnd = srcLineIt + srcImage.width(); - uint32* hdrLineIt = (uint32*) hdrImage.scanLine(y); + uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); glm::vec3 color; while (srcLineIt < srcLineEnd) { @@ -1116,7 +1121,7 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { color.g = qGreen(*srcLineIt); color.b = qBlue(*srcLineIt); // Normalize and apply gamma - color /= 255.f; + color /= 255.0f; color.r = powf(color.r, 2.2f); color.g = powf(color.g, 2.2f); color.b = powf(color.b, 2.2f); diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index c8be097542..3bf45ace98 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -13,11 +13,11 @@ #define hifi_image_Image_h #include -#include #include class QByteArray; +class QImage; namespace image { @@ -74,11 +74,6 @@ void setNormalTexturesCompressionEnabled(bool enabled); void setGrayscaleTexturesCompressionEnabled(bool enabled); void setCubeTexturesCompressionEnabled(bool enabled); -enum -{ - QIMAGE_HDR_FORMAT = QImage::Format_RGB30 -}; - gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType); } // namespace image From 5699dc6f42356874fb96ca86aed65c3e3e01c133 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 21 Sep 2017 10:03:05 +0200 Subject: [PATCH 341/504] Added KTX support of compressed HDR and HDR formats --- libraries/gpu/src/gpu/Texture_ktx.cpp | 15 +++++++++++++++ libraries/ktx/src/khronos/KHR.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 14fd983ec2..eb21121ebe 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -570,6 +570,12 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::R11F_G11F_B10F, ktx::GLBaseInternalFormat::RGB); } else { return false; } @@ -631,6 +637,15 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { + mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; + texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGB9_E5) { + mipFormat = Format::COLOR_RGB9E5; + texelFormat = Format::COLOR_RGB9E5; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::R11F_G11F_B10F) { + mipFormat = Format::COLOR_R11G11B10; + texelFormat = Format::COLOR_R11G11B10; } else { return false; } diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index 98cc1a4736..ae23d9cefe 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -225,6 +225,7 @@ namespace khronos { case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3 case InternalFormat::COMPRESSED_RED_RGTC1: // BC4 case InternalFormat::COMPRESSED_RG_RGTC2: // BC5 + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6 case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 return evalAlignedCompressedBlockCount<4>(value); @@ -241,6 +242,7 @@ namespace khronos { return 8; case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case InternalFormat::COMPRESSED_RG_RGTC2: return 16; From f58b2a3bed5ba07933f7f97f6d9e4e6088212da7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Sep 2017 21:59:19 +1200 Subject: [PATCH 342/504] Add group selection logic --- scripts/vr-edit/vr-edit.js | 76 ++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2bcd3533cd..c50160f4b4 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -29,10 +29,11 @@ TOOL_SCALE = 1, TOOL_CLONE = 2, TOOL_GROUP = 3, - TOOL_COLOR = 4, - TOOL_PICK_COLOR = 5, - TOOL_PHYSICS = 6, - TOOL_DELETE = 7, + TOOL_GROUP_BOX = 4, + TOOL_COLOR = 5, + TOOL_PICK_COLOR = 6, + TOOL_PHYSICS = 7, + TOOL_DELETE = 8, toolSelected = TOOL_NONE, colorToolColor = { red: 128, green: 128, blue: 128 }, physicsToolPhysics = { userData: { grabbableKey: {} } }, @@ -816,8 +817,17 @@ if (!grouping.includes(rootEntityID)) { highlights.display(false, selection.selection(), null, highlights.GROUP_COLOR); } - Feedback.play(side, Feedback.SELECT_ENTITY); - grouping.toggle(selection.selection()); + if (toolSelected === TOOL_GROUP_BOX) { + if (!grouping.includes(rootEntityID)) { + Feedback.play(side, Feedback.SELECT_ENTITY); + grouping.selectInBox(selection.selection()); + } else { + Feedback.play(side, Feedback.GENERAL_ERROR); + } + } else { + Feedback.play(side, Feedback.SELECT_ENTITY); + grouping.toggle(selection.selection()); + } } function exitEditorGrouping() { @@ -954,7 +964,7 @@ } } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); - } else if (toolSelected === TOOL_GROUP) { + } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { setState(EDITOR_HIGHLIGHTING); @@ -1043,7 +1053,7 @@ } } else if (toolSelected === TOOL_CLONE) { setState(EDITOR_CLONING); - } else if (toolSelected === TOOL_GROUP) { + } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { setState(EDITOR_GROUPING); } else if (toolSelected === TOOL_COLOR) { if (selection.applyColor(colorToolColor, false)) { @@ -1181,6 +1191,7 @@ } break; case EDITOR_GROUPING: + // Immediate transition out of state after updating group data during state entry. if (hand.valid() && isTriggerClicked) { // No transition. break; @@ -1270,6 +1281,7 @@ // Grouping highlights and functions. var groups, + isSelectInBox = false, highlights, exludedLeftRootEntityID = null, exludedrightRootEntityID = null, @@ -1295,6 +1307,33 @@ } } + function updateSelectInBox() { + // TODO: Calculate bounding box. + + // TODO: Select further entities per bounding box. + + // TODO: Update bounding box overlay. + } + + function startSelectInBox() { + isSelectInBox = true; + + // TODO: Create bounding box overlay. + + updateSelectInBox(); + } + + function selectInBox(selection) { + toggle(selection); + updateSelectInBox(); + } + + function stopSelectInBox() { + isSelectInBox = false; + + // TODO: Delete bounding box overlay. + } + function includes(rootEntityID) { return groups.includes(rootEntityID); } @@ -1360,6 +1399,9 @@ return { toggle: toggle, + startSelectInBox: startSelectInBox, + selectInBox: selectInBox, + stopSelectInBox: stopSelectInBox, includes: includes, groupsCount: groupsCount, entitiesCount: entitiesCount, @@ -1482,6 +1524,24 @@ ui.clearTool(); ui.updateUIOverlays(); break; + case "toggleGroupSelectionBoxTool": + toolSelected = parameter ? TOOL_GROUP_BOX : TOOL_GROUP; + if (toolSelected === TOOL_GROUP_BOX) { + grouping.startSelectInBox(); + } else { + grouping.stopSelectInBox(); + } + break; + case "cancelGroupSelectionBoxTool": + if (grouping.groupsCount() > 0) { + Feedback.play(dominantHand, Feedback.SELECT_ENTITY); + } + if (toolSelected === TOOL_GROUP_BOX) { + grouping.stopSelectInBox(); + } + grouping.clear(); + toolSelected = TOOL_GROUP; + break; case "setColor": if (toolSelected === TOOL_PICK_COLOR) { From 98820f720d986f7b04960e20e5b907d211b677b6 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 21 Sep 2017 13:17:25 +0200 Subject: [PATCH 343/504] KTX serialization / unserialization working with compressed HDR. Not sure with packed floats --- .../src/RenderableModelEntityItem.cpp | 4 +-- libraries/gpu/src/gpu/Texture_ktx.cpp | 32 +++++++++---------- libraries/image/src/image/Image.cpp | 25 ++++++++++----- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 2508b598af..5b790dbddc 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -319,7 +319,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // should never fall in here when collision model not fully loaded // hence we assert that all geometries exist and are loaded - assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); + assert(model && model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); @@ -408,7 +408,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setParams(type, dimensions, getCompoundShapeURL()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { // should never fall in here when model not fully loaded - assert(_model && _model->isLoaded()); + assert(model && model->isLoaded()); updateModelBounds(); model->updateGeometry(); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index eb21121ebe..2acb5a9be8 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -518,17 +518,17 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry) { - std::unique_ptr ktxPointer = ktx::KTX::create(std::make_shared(cacheEntry->getFilepath().c_str())); - if (!ktxPointer) { - return nullptr; - } +std::unique_ptr ktxPointer = ktx::KTX::create(std::make_shared(cacheEntry->getFilepath().c_str())); +if (!ktxPointer) { + return nullptr; +} - auto texture = build(ktxPointer->toDescriptor()); - if (texture) { - texture->setKtxBacking(cacheEntry); - } +auto texture = build(ktxPointer->toDescriptor()); +if (texture) { + texture->setKtxBacking(cacheEntry); +} - return texture; +return texture; } TexturePointer Texture::unserialize(const std::string& ktxfile) { @@ -536,7 +536,7 @@ TexturePointer Texture::unserialize(const std::string& ktxfile) { if (!ktxPointer) { return nullptr; } - + auto texture = build(ktxPointer->toDescriptor()); if (texture) { texture->setKtxBacking(ktxfile); @@ -618,6 +618,12 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else { return false; } + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV) { + mipFormat = Format::COLOR_R11G11B10; + texelFormat = Format::COLOR_R11G11B10; + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_5_9_9_9_REV) { + mipFormat = Format::COLOR_RGB9E5; + texelFormat = Format::COLOR_RGB9E5; } else if (header.isCompressed()) { if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) { mipFormat = Format::COLOR_COMPRESSED_SRGB; @@ -640,12 +646,6 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; - } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGB9_E5) { - mipFormat = Format::COLOR_RGB9E5; - texelFormat = Format::COLOR_RGB9E5; - } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::R11F_G11F_B10F) { - mipFormat = Format::COLOR_R11G11B10; - texelFormat = Format::COLOR_R11G11B10; } else { return false; } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2ae62a9ce3..b88a067080 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -43,6 +43,8 @@ bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; +static const auto HDR_FORMAT = gpu::Element::COLOR_R11G11B10; + bool needsSparseRectification(const glm::uvec2& size) { // Don't attempt to rectify small textures (textures less than the sparse page size in any dimension) if (glm::any(glm::lessThan(size, SPARSE_PAGE_SIZE))) { @@ -423,18 +425,26 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_Float); compressionOptions.setPixelFormat(32, 32, 32, 0); - unpackFunc = glm::unpackF3x9_E1x5; } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_Float); compressionOptions.setPixelFormat(32, 32, 32, 0); - unpackFunc = glm::unpackF2x11_1x10; } else { qCWarning(imagelogging) << "Unknown mip format"; Q_UNREACHABLE(); return; } + if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { + unpackFunc = glm::unpackF3x9_E1x5; + } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { + unpackFunc = glm::unpackF2x11_1x10; + } else { + qCWarning(imagelogging) << "Unknown HDR encoding format in QImage"; + Q_UNREACHABLE(); + return; + } + data.resize(width*height); dataIt = data.begin(); for (auto lineNb = 0; lineNb < height; lineNb++) { @@ -1144,19 +1154,18 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - const auto cubeMapHDRFormat = gpu::Element::COLOR_R11G11B10; gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = cubeMapHDRFormat; - formatGPU = cubeMapHDRFormat; + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; } if (image.format() != QIMAGE_HDR_FORMAT) { - image = convertToHDRFormat(image, cubeMapHDRFormat); + image = convertToHDRFormat(image, HDR_FORMAT); } // Find the layout of the cubemap in the 2D image @@ -1204,9 +1213,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(cubeMapHDRFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(cubeMapHDRFormat); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } From a757012c10702ca04ddb69141a0d8cbe056323fd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 08:17:35 +1200 Subject: [PATCH 344/504] Change default color to 100% white --- scripts/vr-edit/modules/toolsMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 2b93c215d4..36bc92f187 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -824,7 +824,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setting: { key: "VREdit.colorTool.currentColor", property: "color", - defaultValue: { red: 128, green: 128, blue: 128 }, + defaultValue: { red: 255, green: 255, blue: 255 }, command: "setPickColor" } } From e4a126461b2c41c3f5fb90ffad0800b9357174d9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 10:53:06 +1200 Subject: [PATCH 345/504] Add extra Create palette items --- scripts/vr-edit/assets/create/circle.fbx | Bin 0 -> 30268 bytes .../vr-edit/assets/create/dodecahedron.fbx | Bin 0 -> 20700 bytes scripts/vr-edit/assets/create/hexagon.fbx | Bin 0 -> 19516 bytes scripts/vr-edit/assets/create/octagon.fbx | Bin 0 -> 20092 bytes scripts/vr-edit/modules/createPalette.js | 62 ++++++++++++++++-- 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 scripts/vr-edit/assets/create/circle.fbx create mode 100644 scripts/vr-edit/assets/create/dodecahedron.fbx create mode 100644 scripts/vr-edit/assets/create/hexagon.fbx create mode 100644 scripts/vr-edit/assets/create/octagon.fbx diff --git a/scripts/vr-edit/assets/create/circle.fbx b/scripts/vr-edit/assets/create/circle.fbx new file mode 100644 index 0000000000000000000000000000000000000000..db43fafab646c8bb978539d1f2142ca674983550 GIT binary patch literal 30268 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 C*V-Kb literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/create/octagon.fbx b/scripts/vr-edit/assets/create/octagon.fbx new file mode 100644 index 0000000000000000000000000000000000000000..8de0f6f6f3b8b14dc8bc751c46c41fa1fdacfb69 GIT binary patch literal 20092 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 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index fff8ac34fb..11229562ef 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -219,8 +219,34 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { color: ENTITY_CREATION_COLOR } }, - // TODO: "Dodecahedron" shape type per edit.js. - // TODO: "Hexagon" shape type per edit.js. + { + icon: { + properties: { + url: "../assets/create/dodecahedron.fbx" + } + }, + entity: { + type: "Shape", + shape: "Dodecahedron", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: "../assets/create/hexagon.fbx", + dimensions: { x: 0.02078, y: 0.024, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Hexagon", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, { icon: { properties: { @@ -235,7 +261,21 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { color: ENTITY_CREATION_COLOR } }, - // TODO: "Octagon" shape type per edit.js. + { + icon: { + properties: { + url: "../assets/create/octagon.fbx", + dimensions: { x: 0.023805, y: 0.024, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Octagon", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, { icon: { properties: { @@ -263,8 +303,22 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: ENTITY_CREATION_DIMENSIONS, color: ENTITY_CREATION_COLOR } + }, + { + icon: { + properties: { + url: "../assets/create/circle.fbx", + dimensions: { x: 0.024, y: 0.0005, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Circle", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } } - // TODO: "Circle" shape type per edit.js. ], isDisplaying = false, From 959d497079b62249fe16645f459cbbb49f129018 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 12:00:12 +1200 Subject: [PATCH 346/504] Change group tool's "Cancel" button to "Clear" --- .../assets/tools/group/cancel-label.svg | 12 ---- .../assets/tools/group/clear-label.svg | 11 ++++ scripts/vr-edit/modules/toolsMenu.js | 66 +++++++++---------- scripts/vr-edit/vr-edit.js | 6 +- 4 files changed, 45 insertions(+), 50 deletions(-) delete mode 100644 scripts/vr-edit/assets/tools/group/cancel-label.svg create mode 100644 scripts/vr-edit/assets/tools/group/clear-label.svg diff --git a/scripts/vr-edit/assets/tools/group/cancel-label.svg b/scripts/vr-edit/assets/tools/group/cancel-label.svg deleted file mode 100644 index 66e9ad4afc..0000000000 --- a/scripts/vr-edit/assets/tools/group/cancel-label.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - CANCEL - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/scripts/vr-edit/assets/tools/group/clear-label.svg b/scripts/vr-edit/assets/tools/group/clear-label.svg new file mode 100644 index 0000000000..8bc5daa8b4 --- /dev/null +++ b/scripts/vr-edit/assets/tools/group/clear-label.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 0c3ca9fb02..fdad65e82c 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -1071,7 +1071,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, { - id: "groupsSelectionBoxCancelButton", + id: "clearGroupingButton", type: "button", properties: { dimensions: { x: 0.1042, y: 0.0400, z: UIT.dimensions.buttonDimensions.z }, @@ -1085,14 +1085,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { enabledColor: UIT.colors.greenHighlight, highlightColor: UIT.colors.greenShadow, label: { - url: "../assets/tools/group/cancel-label.svg", - scale: 0.0380, + url: "../assets/tools/group/clear-label.svg", + scale: 0.0314, color: UIT.colors.baseGray }, labelEnabledColor: UIT.colors.white, enabled: false, command: { - method: "cancelGroupSelectionBox" + method: "clearGroupSelection" } } ], @@ -1999,8 +1999,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isGroupButtonEnabled, isUngroupButtonEnabled, + isClearGroupingButtonEnabled, groupButtonIndex, ungroupButtonIndex, + clearGroupingButtonIndex, hsvControl = { hsv: { h: 0, s: 0, v: 0 }, @@ -2431,6 +2433,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (menuItem.toolOptions === "groupOptions") { optionsEnabled[groupButtonIndex] = false; optionsEnabled[ungroupButtonIndex] = false; + optionsEnabled[clearGroupingButtonIndex] = false; } isOptionsOpen = true; @@ -2706,41 +2709,18 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "toggleGroupSelectionBox": optionsToggles.groupSelectionBoxButton = !optionsToggles.groupSelectionBoxButton; - index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); Overlays.editOverlay(optionsOverlays[index], { color: optionsToggles.groupSelectionBoxButton ? UI_ELEMENTS[optionsItems[index].type].onHoverColor : UI_ELEMENTS[optionsItems[index].type].offHoverColor }); - - index = optionsOverlaysIDs.indexOf("groupsSelectionBoxCancelButton"); - Overlays.editOverlay(optionsOverlays[index], { - color: optionsToggles.groupSelectionBoxButton - ? optionsItems[index].enabledColor - : optionsItems[index].properties.color - }); - Overlays.editOverlay(optionsOverlaysLabels[index], { - color: optionsToggles.groupSelectionBoxButton - ? optionsItems[index].labelEnabledColor - : optionsItems[index].label.color - }); - optionsEnabled[index] = optionsToggles.groupSelectionBoxButton; - uiCommandCallback("toggleGroupSelectionBoxTool", optionsToggles.groupSelectionBoxButton); break; - case "cancelGroupSelectionBox": + case "clearGroupSelection": optionsToggles.groupSelectionBoxButton = false; - - index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); - Overlays.editOverlay(optionsOverlays[index], { - color : optionsToggles.groupSelectionBoxButton - ? UI_ELEMENTS[optionsItems[index].type].onHoverColor - : UI_ELEMENTS[optionsItems[index].type].offHoverColor - }); - - index = optionsOverlaysIDs.indexOf("groupsSelectionBoxCancelButton"); + index = clearGroupingButtonIndex; Overlays.editOverlay(optionsOverlays[index], { color: optionsItems[index].properties.color }); @@ -2748,8 +2728,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: optionsItems[index].label.color }); optionsEnabled[index] = false; - - uiCommandCallback("cancelGroupSelectionBoxTool"); + uiCommandCallback("clearGroupSelectionTool"); break; case "setGravityOn": @@ -2974,6 +2953,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { parameterValue, enableGroupButton, enableUngroupButton, + enableClearGroupingButton, sliderProperties, overlayDimensions, basePoint, @@ -3412,7 +3392,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ? OPTONS_PANELS.groupOptions[groupButtonIndex].labelEnabledColor : OPTONS_PANELS.groupOptions[groupButtonIndex].label.color }); - optionsEnabled[groupButtonIndex] = enableGroupButton; + optionsEnabled[groupButtonIndex] = isGroupButtonEnabled; } enableUngroupButton = groupsCount === 1 && entitiesCount > 1; @@ -3430,7 +3410,23 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { ? OPTONS_PANELS.groupOptions[ungroupButtonIndex].labelEnabledColor : OPTONS_PANELS.groupOptions[ungroupButtonIndex].label.color }); - optionsEnabled[ungroupButtonIndex] = enableUngroupButton; + optionsEnabled[ungroupButtonIndex] = isUngroupButtonEnabled; + } + + enableClearGroupingButton = groupsCount > 0; + if (enableClearGroupingButton !== isClearGroupingButtonEnabled) { + isClearGroupingButtonEnabled = enableClearGroupingButton; + Overlays.editOverlay(optionsOverlays[clearGroupingButtonIndex], { + color: isClearGroupingButtonEnabled + ? optionsItems[clearGroupingButtonIndex].enabledColor + : optionsItems[clearGroupingButtonIndex].properties.color + }); + Overlays.editOverlay(optionsOverlaysLabels[clearGroupingButtonIndex], { + color: isClearGroupingButtonEnabled + ? optionsItems[clearGroupingButtonIndex].labelEnabledColor + : optionsItems[clearGroupingButtonIndex].label.color + }); + optionsEnabled[clearGroupingButtonIndex] = isClearGroupingButtonEnabled; } } @@ -3522,6 +3518,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Special handling for Group options. isGroupButtonEnabled = false; isUngroupButtonEnabled = false; + isClearGroupingButtonEnabled = false; for (i = 0, length = OPTONS_PANELS.groupOptions.length; i < length; i += 1) { id = OPTONS_PANELS.groupOptions[i].id; if (id === "groupButton") { @@ -3530,6 +3527,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (id === "ungroupButton") { ungroupButtonIndex = i; } + if (id === "clearGroupingButton") { + clearGroupingButtonIndex = i; + } } isDisplaying = true; diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index c50160f4b4..5f7b3bb732 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1532,15 +1532,11 @@ grouping.stopSelectInBox(); } break; - case "cancelGroupSelectionBoxTool": + case "clearGroupSelectionTool": if (grouping.groupsCount() > 0) { Feedback.play(dominantHand, Feedback.SELECT_ENTITY); } - if (toolSelected === TOOL_GROUP_BOX) { - grouping.stopSelectInBox(); - } grouping.clear(); - toolSelected = TOOL_GROUP; break; case "setColor": From ca000acf2ae753f8e05126b65330a32a8b46197a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 17:40:34 +1200 Subject: [PATCH 347/504] Add space for a footer to put undo/redo buttons in --- scripts/vr-edit/modules/createPalette.js | 8 +--- scripts/vr-edit/modules/toolsMenu.js | 60 ++++++++++++------------ scripts/vr-edit/modules/uit.js | 5 +- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 11229562ef..132be3ab79 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -98,11 +98,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_PANEL_PROPERTIES = { dimensions: UIT.dimensions.panel, - localPosition: { - x: 0, - y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, - z: UIT.dimensions.panel.z / 2 - }, + localPosition: { x: 0, y: (UIT.dimensions.panel.y - UIT.dimensions.canvas.y) / 2, z: UIT.dimensions.panel.z / 2 }, localRotation: Quat.ZERO, color: UIT.colors.baseGray, alpha: 1.0, @@ -430,7 +426,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { function itemPosition(index) { // Position relative to palette panel. var ITEMS_PER_ROW = 4, - ROW_ZERO_Y_OFFSET = 0.0580, + ROW_ZERO_Y_OFFSET = 0.0860, ROW_SPACING = 0.0560, COLUMN_ZERO_OFFSET = -0.08415, COLUMN_SPACING = 0.0561, diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 36bc92f187..7ca5d807a9 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -180,7 +180,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_PANEL_PROPERTIES = { dimensions: UIT.dimensions.panel, - localPosition: { x: 0, y: UIT.dimensions.panel.y / 2 - UIT.dimensions.canvas.y / 2, z: UIT.dimensions.panel.z / 2 }, + localPosition: { x: 0, y: (UIT.dimensions.panel.y - UIT.dimensions.canvas.y) / 2, z: UIT.dimensions.panel.z / 2 }, localRotation: Quat.ZERO, color: UIT.colors.baseGray, alpha: 1.0, @@ -612,7 +612,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0935, - y: 0.0513, + y: 0.0513 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -633,7 +633,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0561, - y: 0.0513, + y: 0.0513 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -654,7 +654,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0935, - y: 0.0153, + y: 0.0153 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -675,7 +675,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0561, - y: 0.0153, + y: 0.0153 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -696,7 +696,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0935, - y: -0.0207, + y: -0.0207 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -717,7 +717,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: -0.0561, - y: -0.0207, + y: -0.0207 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -739,7 +739,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0668, y: 0.001 }, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0668 / 2, - y: -UIT.dimensions.panel.y / 2 + 0.0481, + y: -UIT.dimensions.panel.y / 2 + 0.0481 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset } } @@ -750,7 +750,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: 0.04675, - y: 0.01655, + y: 0.01655 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -766,7 +766,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: 0.04675, - y: -0.0620, + y: -0.0620 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }) @@ -786,7 +786,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.1229, y: 0.001 }, localPosition: { x: 0.04675, - y: -0.0781, + y: -0.0781 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset } } @@ -798,7 +798,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0935, - y: -0.064, + y: -0.064 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -817,7 +817,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.0280, z: UIT.dimensions.imageOverlayOffset }, localPosition: { x: -0.0561, - y: -0.064, + y: -0.064 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -892,7 +892,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0321, y: 0.0320 }, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, - y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, color: UIT.colors.white // Icon SVG is already lightGray color. @@ -904,9 +904,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/stretch/info-text.svg", scale: 0.1340, - localPosition: { + localPosition: { // Vertically center on info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, - y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, // Center on info icon. + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, color: UIT.colors.white @@ -1023,7 +1023,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, localPosition: { x: 0, - y: -UIT.dimensions.panel.y / 2 + 0.0120 + 0.0680 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0120 + 0.0680 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, color: UIT.colors.baseGrayShadow @@ -1076,7 +1076,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0668, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0748, - y: 0.0480, + y: 0.0480 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1124,7 +1124,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0668, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0748, - y: 0.0120, + y: 0.0120 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1172,7 +1172,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0668, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0748, - y: -0.0240, + y: -0.0240 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1376,7 +1376,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.1416, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: UIT.dimensions.panel.x / 2 - UIT.dimensions.rightMargin - 0.1416 / 2, - y: 0.0480, + y: 0.0480 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1435,7 +1435,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.1000, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: -0.0187, - y: -0.0240, + y: -0.0240 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1464,7 +1464,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.1000, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: 0.0187, - y: -0.0240, + y: -0.0240 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1493,7 +1493,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.1000, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: 0.0561, - y: -0.0240, + y: -0.0240 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1522,7 +1522,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: 0.1000, z: UIT.dimensions.buttonDimensions.z }, localPosition: { x: 0.0935, - y: -0.0240, + y: -0.0240 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, @@ -1608,7 +1608,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0321, y: 0.0320 }, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, - y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2, + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, color: UIT.colors.white // Icon SVG is already lightGray color. @@ -1620,9 +1620,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: "../assets/tools/delete/info-text.svg", scale: 0.1416, - localPosition: { + localPosition: { // Vertically off-center w.r.t. info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, - y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0240 / 2 + 0.0063 / 2, // Off-center w.r.t. info icon. + y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0240 / 2 + 0.0063 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, color: UIT.colors.white @@ -1632,7 +1632,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_ITEM_XS = [-0.08415, -0.02805, 0.02805, 0.08415], - MENU_ITEM_YS = [0.058, 0.002, -0.054], + MENU_ITEM_YS = [0.086, 0.030, -0.026], MENU_ITEMS = [ { @@ -1998,7 +1998,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); function getOverlayIDs() { - return [menuPanelOverlay, menuHeaderOverlay].concat(menuOverlays).concat(optionsOverlays); + return [menuHeaderOverlay, menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); } function getIconInfo(tool) { diff --git a/scripts/vr-edit/modules/uit.js b/scripts/vr-edit/modules/uit.js index fa69c9045e..7ad6e67c56 100644 --- a/scripts/vr-edit/modules/uit.js +++ b/scripts/vr-edit/modules/uit.js @@ -32,7 +32,7 @@ UIT = (function () { // Coordinate system: UI lies in x-y plane with the front surface being +z. // Offsets are relative to parents' centers. dimensions: { - canvas: { x: 0.24, y: 0.24 }, // Overall UI size. + canvas: { x: 0.24, y: 0.296 }, // Overall UI size. canvasSeparation: 0.004, // Gap between Tools menu and Create panel. handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. handLateralOffset: 0.01, // Offset of UI in direction of palm normal. @@ -44,7 +44,8 @@ UIT = (function () { header: { x: 0.24, y: 0.048, z: 0.012 }, headerHeading: { x: 0.24, y: 0.044, z: 0.012 }, headerBar: { x: 0.24, y: 0.004, z: 0.012 }, - panel: { x: 0.24, y: 0.18, z: 0.008 }, + panel: { x: 0.24, y: 0.236, z: 0.008 }, + footerHeight: 0.056, itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0040 }, // Cursor intersection zone for Tools and Create items. From edd6310a071ab24288fb2fb50315ce8841a74c64 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 21:46:33 +1200 Subject: [PATCH 348/504] Move undo/redo buttons to footer area and make persistent --- scripts/vr-edit/modules/createPalette.js | 1 - scripts/vr-edit/modules/toolsMenu.js | 190 ++++++++++++++++++----- 2 files changed, 151 insertions(+), 40 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 132be3ab79..e178e41294 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -492,7 +492,6 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(PALETTE_ITEM.properties); properties.parentID = palettePanelOverlay; properties.localPosition = itemPosition(i); - paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.overlay, properties); paletteItemPositions[i] = properties.localPosition; diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 7ca5d807a9..b520ccbc3e 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -53,6 +53,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, optionsToggles = {}, // Cater for toggle buttons without a setting. + footerOverlays = [], + footerHoverOverlays = [], + footerIconOverlays = [], + footerLabelOverlays = [], + footerEnabled = [], + swatchHighlightOverlay = null, staticOverlays = [], @@ -766,7 +772,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: 0.04675, - y: -0.0620 + UIT.dimensions.footerHeight / 2, + y: -0.064 + UIT.dimensions.footerHeight / 2, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 }, localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }) @@ -779,18 +785,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { method: "setColorPerSlider" } }, - { - id: "colorRule3", - type: "horizontalRule", - properties: { - dimensions: { x: 0.1229, y: 0.001 }, - localPosition: { - x: 0.04675, - y: -0.0781 + UIT.dimensions.footerHeight / 2, - z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset - } - } - }, { id: "pickColor", type: "toggleButton", @@ -1632,7 +1626,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_ITEM_XS = [-0.08415, -0.02805, 0.02805, 0.08415], - MENU_ITEM_YS = [0.086, 0.030, -0.026], + MENU_ITEM_YS = [0.086, 0.030, -0.026, -0.082], MENU_ITEMS = [ { @@ -1863,6 +1857,26 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { callback: { method: "deleteTool" } + } + ], + COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. + SCALE_TOOL = 1, + CLONE_TOOL = 2, + GROUP_TOOL = 3, + PHYSICS_TOOL = 4, + DELETE_TOOL = 5, + + FOOTER_ITEMS = [ + { + id: "footerRule", + type: "horizontalRule", + properties: { + localPosition: { + x: 0, + y: -UIT.dimensions.panel.y / 2 + 0.0600, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset + } + } }, { id: "undoButton", @@ -1870,7 +1884,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: MENU_ITEM_XS[2], - y: MENU_ITEM_YS[2], + y: MENU_ITEM_YS[3] - 0.008, // Allow space for horizontal rule and Line up with Create palette row. z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 } }, @@ -1896,7 +1910,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: MENU_ITEM_XS[3], - y: MENU_ITEM_YS[2], + y: MENU_ITEM_YS[3] - 0.008, z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 } }, @@ -1917,12 +1931,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], - COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. - SCALE_TOOL = 1, - CLONE_TOOL = 2, - GROUP_TOOL = 3, - PHYSICS_TOOL = 4, - DELETE_TOOL = 5, NONE = -1, @@ -1998,7 +2006,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { setHand(side); function getOverlayIDs() { - return [menuHeaderOverlay, menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays); + return [menuHeaderOverlay, menuPanelOverlay].concat(menuOverlays).concat(optionsOverlays).concat(footerOverlays); } function getIconInfo(tool) { @@ -2092,6 +2100,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuHoverOverlays = []; menuIconOverlays = []; menuLabelOverlays = []; + menuEnabled = []; pressedItem = null; } @@ -2415,6 +2424,57 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { openMenu(); } + function displayFooter() { + var properties, + itemID, + buttonID, + overlayID, + i, + length; + + // Display footer items. + for (i = 0, length = FOOTER_ITEMS.length; i < length; i += 1) { + properties = Object.clone(UI_ELEMENTS[FOOTER_ITEMS[i].type].properties); + properties = Object.merge(properties, FOOTER_ITEMS[i].properties); + properties.visible = isVisible; + properties.parentID = menuPanelOverlay; + if (properties.url) { + properties.url = Script.resolvePath(properties.url); + } + itemID = Overlays.addOverlay(UI_ELEMENTS[FOOTER_ITEMS[i].type].overlay, properties); + footerOverlays[i] = itemID; + footerEnabled[i] = true; + + if (FOOTER_ITEMS[i].type === "menuButton") { + // Collision overlay. + properties = Object.clone(UI_ELEMENTS.menuButton.hoverButton.properties); + properties.parentID = itemID; + buttonID = Overlays.addOverlay(UI_ELEMENTS.menuButton.hoverButton.overlay, properties); + footerHoverOverlays[i] = buttonID; + + // Icon. + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.icon.properties); + properties = Object.merge(properties, FOOTER_ITEMS[i].icon.properties); + properties.url = Script.resolvePath(properties.url); + properties.visible = isVisible; + properties.parentID = buttonID; + overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].overlay, properties); + footerIconOverlays[i] = overlayID; + + // Label. + properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].properties); + properties = Object.merge(properties, UI_ELEMENTS.menuButton.label.properties); + properties = Object.merge(properties, FOOTER_ITEMS[i].label.properties); + properties.url = Script.resolvePath(properties.url); + properties.visible = isVisible; + properties.parentID = itemID; + overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].overlay, properties); + footerLabelOverlays.push(overlayID); + } + } + } + function clearTool() { closeOptions(); } @@ -2860,6 +2920,21 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } + for (i = 0, length = footerOverlays.length; i < length; i += 1) { + Overlays.editOverlay(footerOverlays[i], { visible: visible }); + } + for (i = 0, length = footerIconOverlays.length; i < length; i += 1) { + Overlays.editOverlay(footerIconOverlays[i], { visible: visible }); + } + for (i = 0, length = footerLabelOverlays.length; i < length; i += 1) { + Overlays.editOverlay(footerLabelOverlays[i], { visible: visible }); + } + if (!visible) { + for (i = 0, length = footerHoverOverlays.length; i < length; i += 1) { + Overlays.editOverlay(footerHoverOverlays[i], { visible: false }); + } + } + isVisible = visible; } @@ -2953,6 +3028,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { intersectionItems = optionsItems; intersectionOverlays = optionsOverlays; intersectionEnabled = optionsEnabled; + } else { + intersectedItem = footerOverlays.indexOf(intersection.overlayID); + if (intersectedItem !== NONE) { + intersectionItems = FOOTER_ITEMS; + intersectionOverlays = footerOverlays; + intersectionEnabled = footerEnabled; + } } } } @@ -2964,13 +3046,23 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Unhover old item. switch (hoveredElementType) { case "menuButton": - Overlays.editOverlay(menuHoverOverlays[hoveredItem], { - localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - visible: false - }); - Overlays.editOverlay(menuIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.properties.color - }); + if (hoveredSourceOverlays === menuOverlays) { + Overlays.editOverlay(menuHoverOverlays[hoveredItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false + }); + Overlays.editOverlay(menuIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.properties.color + }); + } else { + Overlays.editOverlay(footerHoverOverlays[hoveredItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false + }); + Overlays.editOverlay(footerIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.properties.color + }); + } break; case "button": if (hoveredSourceItems[hoveredItem].enabledColor !== undefined && optionsEnabled[hoveredItem]) { @@ -3054,13 +3146,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { switch (hoveredElementType) { case "menuButton": Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); - Overlays.editOverlay(menuHoverOverlays[hoveredItem], { - localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, MENU_HOVER_DELTA), - visible: true - }); - Overlays.editOverlay(menuIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.highlightColor - }); + if (intersectionOverlays === menuOverlays) { + Overlays.editOverlay(menuHoverOverlays[hoveredItem], { + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + MENU_HOVER_DELTA), + visible: true + }); + Overlays.editOverlay(menuIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.highlightColor + }); + } else { + Overlays.editOverlay(footerHoverOverlays[hoveredItem], { + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + MENU_HOVER_DELTA), + visible: true + }); + Overlays.editOverlay(footerIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.highlightColor + }); + } break; case "button": if (intersectionEnabled[hoveredItem]) { @@ -3179,7 +3283,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }; // Button press actions. - if (intersectionOverlays === menuOverlays && intersectionItems[intersectedItem].toolOptions) { + if (intersectionOverlays === menuOverlays) { openOptions(intersectionItems[intersectedItem]); } if (intersectionItems[intersectedItem].command) { @@ -3396,8 +3500,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { staticOverlays = [menuHeaderOverlay, menuHeaderHeadingOverlay, menuHeaderBarOverlay, menuHeaderTitleOverlay, menuPanelOverlay]; - // Menu items. + // Menu and footer. openMenu(); + displayFooter(); // Initial values. optionsItems = null; @@ -3442,10 +3547,17 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuHoverOverlays = []; menuIconOverlays = []; menuLabelOverlays = []; + menuEnabled = []; optionsOverlays = []; optionsOverlaysLabels = []; optionsOverlaysSublabels = []; optionsExtraOverlays = []; + optionsEnabled = []; + footerOverlays = []; + footerHoverOverlays = []; + footerIconOverlays = []; + footerLabelOverlays = []; + footerEnabled = []; isDisplaying = false; } From f53f8c14fc8ef79b12aabbf4abb283226553bb74 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 23 Sep 2017 11:34:57 +1200 Subject: [PATCH 349/504] Update wrist tool icon --- scripts/vr-edit/assets/tools/tool-icon.fbx | Bin 55292 -> 78732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/vr-edit/assets/tools/tool-icon.fbx b/scripts/vr-edit/assets/tools/tool-icon.fbx index f407ad7feb4824077f715ede315c691f1a52eedc..b4a21c523b595d0380e7fbdbbbd6f2ca71e58fae 100644 GIT binary patch delta 36740 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 delta 14694 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 P1kwe1%xw4Npqu{(h|;)@ From bc5cd7415b5a2fd12a54138ad166092f755f9efd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 23 Sep 2017 11:56:42 +1200 Subject: [PATCH 350/504] Use emissive FBX models for header bars --- scripts/vr-edit/assets/blue-header-bar.fbx | Bin 0 -> 20524 bytes scripts/vr-edit/assets/green-header-bar.fbx | Bin 0 -> 20492 bytes scripts/vr-edit/modules/createPalette.js | 12 ++++++++---- scripts/vr-edit/modules/toolsMenu.js | 12 ++++++++---- 4 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 scripts/vr-edit/assets/blue-header-bar.fbx create mode 100644 scripts/vr-edit/assets/green-header-bar.fbx diff --git a/scripts/vr-edit/assets/blue-header-bar.fbx b/scripts/vr-edit/assets/blue-header-bar.fbx new file mode 100644 index 0000000000000000000000000000000000000000..951a6667e72d963ab1cf09f7b3fdb0e6f1cb2640 GIT binary patch literal 20524 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 Date: Sat, 23 Sep 2017 13:01:38 +1200 Subject: [PATCH 351/504] Press undo/redo and menu buttons when click them --- scripts/vr-edit/modules/toolsMenu.js | 102 ++++++++++++++++----------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index b520ccbc3e..50e001d5a1 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -2941,6 +2941,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function update(intersection, groupsCount, entitiesCount) { var intersectedItem = NONE, intersectionItems, + menuButtonHoverOverlays, + menuButtonIconOverlays, color, localPosition, parameter, @@ -3047,22 +3049,19 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { switch (hoveredElementType) { case "menuButton": if (hoveredSourceOverlays === menuOverlays) { - Overlays.editOverlay(menuHoverOverlays[hoveredItem], { - localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - visible: false - }); - Overlays.editOverlay(menuIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.properties.color - }); + menuButtonHoverOverlays = menuHoverOverlays; + menuButtonIconOverlays = menuIconOverlays; } else { - Overlays.editOverlay(footerHoverOverlays[hoveredItem], { - localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - visible: false - }); - Overlays.editOverlay(footerIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.properties.color - }); + menuButtonHoverOverlays = footerHoverOverlays; + menuButtonIconOverlays = footerIconOverlays; } + Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false + }); + Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.properties.color + }); break; case "button": if (hoveredSourceItems[hoveredItem].enabledColor !== undefined && optionsEnabled[hoveredItem]) { @@ -3147,24 +3146,20 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "menuButton": Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); if (intersectionOverlays === menuOverlays) { - Overlays.editOverlay(menuHoverOverlays[hoveredItem], { - localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - MENU_HOVER_DELTA), - visible: true - }); - Overlays.editOverlay(menuIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.highlightColor - }); + menuButtonHoverOverlays = menuHoverOverlays; + menuButtonIconOverlays = menuIconOverlays; } else { - Overlays.editOverlay(footerHoverOverlays[hoveredItem], { - localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - MENU_HOVER_DELTA), - visible: true - }); - Overlays.editOverlay(footerIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.highlightColor - }); + menuButtonHoverOverlays = footerHoverOverlays; + menuButtonIconOverlays = footerIconOverlays; } + Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + MENU_HOVER_DELTA), + visible: true + }); + Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.highlightColor + }); break; case "button": if (intersectionEnabled[hoveredItem]) { @@ -3259,28 +3254,51 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { || isTriggerClicked !== (pressedItem !== null)) { if (pressedItem) { // Unpress previous button. - Overlays.editOverlay(pressedSource[pressedItem.index], { - localPosition: isHoveringButtonElement && hoveredItem === pressedItem.index - ? Vec3.sum(pressedItem.localPosition, OPTION_HOVER_DELTA) - : pressedItem.localPosition - }); - pressedItem = null; + if (pressedItem !== null) { + if (pressedItem.pressedOverlays) { + pressedSource = pressedItem.pressedOverlays; + } + Overlays.editOverlay(pressedSource[pressedItem.index], { + localPosition: isHoveringButtonElement && hoveredItem === pressedItem.index + ? Vec3.sum(pressedItem.localPosition, pressedItem.hoverDelta) + : pressedItem.localPosition + }); + pressedItem = null; + } pressedSource = null; } if (isHoveringButtonElement && (intersectionEnabled === null || intersectionEnabled[intersectedItem]) && isTriggerClicked && !wasTriggerClicked) { // Press new button. - localPosition = intersectionItems[intersectedItem].properties.localPosition; - if (hoveredElementType !== "menuButton") { + pressedSource = intersectionOverlays; + if (hoveredElementType === "menuButton") { + if (intersectionItems === MENU_ITEMS) { + menuButtonHoverOverlays = menuHoverOverlays; + } else { + menuButtonHoverOverlays = footerHoverOverlays; + } + localPosition = UI_ELEMENTS.menuButton.hoverButton.properties.localPosition; + Overlays.editOverlay(menuButtonHoverOverlays[intersectedItem], { + localPosition: Vec3.sum(Vec3.sum(localPosition, MENU_HOVER_DELTA), BUTTON_PRESS_DELTA) + }); + pressedItem = { + index: intersectedItem, + localPosition: localPosition, + hoverDelta: MENU_HOVER_DELTA, + pressedOverlays: menuButtonHoverOverlays + }; + } else { + localPosition = intersectionItems[intersectedItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[intersectedItem], { localPosition: Vec3.sum(localPosition, BUTTON_PRESS_DELTA) }); + pressedItem = { + index: intersectedItem, + localPosition: localPosition, + hoverDelta: OPTION_HOVER_DELTA + }; } pressedSource = intersectionOverlays; - pressedItem = { - index: intersectedItem, - localPosition: localPosition - }; // Button press actions. if (intersectionOverlays === menuOverlays) { From 8976befaeb93987ee0db3ac12df585f9754f616b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 23 Sep 2017 14:20:53 +1200 Subject: [PATCH 352/504] Use emissive FBX model for highlighted Tools menu header --- scripts/vr-edit/assets/gray-header.fbx | Bin 0 -> 18892 bytes scripts/vr-edit/assets/green-header.fbx | Bin 0 -> 18604 bytes scripts/vr-edit/modules/createPalette.js | 14 +++++--------- scripts/vr-edit/modules/toolsMenu.js | 21 +++++++++------------ 4 files changed, 14 insertions(+), 21 deletions(-) create mode 100644 scripts/vr-edit/assets/gray-header.fbx create mode 100644 scripts/vr-edit/assets/green-header.fbx diff --git a/scripts/vr-edit/assets/gray-header.fbx b/scripts/vr-edit/assets/gray-header.fbx new file mode 100644 index 0000000000000000000000000000000000000000..0f51d66f38a59c2e6f4ee3ed573bd8989128e645 GIT binary patch literal 18892 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 a?Kjunas7Y&>PKJt=!0qRWWIXo)c*s&RlorN literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/assets/green-header.fbx b/scripts/vr-edit/assets/green-header.fbx new file mode 100644 index 0000000000000000000000000000000000000000..db77ecf0bd3314e89a8c66aa213cc91cee04c42a GIT binary patch literal 18604 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 literal 0 HcmV?d00001 diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index 9cc0f3deb8..4ba6b5f238 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -50,14 +50,14 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { }, PALETTE_HEADER_HEADING_PROPERTIES = { - dimensions: UIT.dimensions.headerHeading, + url: Script.resolvePath("../assets/gray-header.fbx"), + dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y / 2, z: UIT.dimensions.headerHeading.z / 2 }, localRotation: Quat.ZERO, - color: UIT.colors.baseGray, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -66,17 +66,13 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_HEADER_BAR_PROPERTIES = { url: Script.resolvePath("../assets/blue-header-bar.fbx"), - dimensions: { // FBX model is in rotated coordinate system. - x: UIT.dimensions.headerBar.z, - y: UIT.dimensions.headerBar.x, - z: UIT.dimensions.headerBar.y - }, + dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y - UIT.dimensions.headerBar.y / 2, z: UIT.dimensions.headerBar.z / 2 }, - localRotation: Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 }), // FBX model is in rotated coordinate system. + localRotation: Quat.ZERO, alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -480,7 +476,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Header. properties = Object.clone(PALETTE_HEADER_HEADING_PROPERTIES); properties.parentID = paletteOriginOverlay; - paletteHeaderHeadingOverlay = Overlays.addOverlay("cube", properties); + paletteHeaderHeadingOverlay = Overlays.addOverlay("model", properties); properties = Object.clone(PALETTE_HEADER_BAR_PROPERTIES); properties.parentID = paletteOriginOverlay; paletteHeaderBarOverlay = Overlays.addOverlay("model", properties); diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index e54c81acf7..831275c1b5 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -100,10 +100,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_HEADING_PROPERTIES = { - dimensions: UIT.dimensions.headerHeading, + url: Script.resolvePath("../assets/gray-header.fbx"), + highlightURL: Script.resolvePath("../assets/green-header.fbx"), + dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.headerBar.y / 2, z: -MENU_HEADER_HOVER_OFFSET.z / 2 }, localRotation: Quat.ZERO, - color: UIT.colors.baseGray, alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -112,13 +113,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_HEADER_BAR_PROPERTIES = { url: Script.resolvePath("../assets/green-header-bar.fbx"), - dimensions: { // FBX model is in rotated coordinate system. - x: UIT.dimensions.headerBar.z, - y: UIT.dimensions.headerBar.x, - z: UIT.dimensions.headerBar.y - }, + dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: -UIT.dimensions.headerHeading.y / 2 - UIT.dimensions.headerBar.y / 2, z: 0 }, - localRotation: Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 }), // FBX model is in rotated coordinate system. + localRotation: Quat.ZERO, alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -2894,8 +2891,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (isTriggerClicked && !wasTriggerClicked) { // Lower and unhighlight heading; go back to Tools menu. Overlays.editOverlay(menuHeaderHeadingOverlay, { + url: MENU_HEADER_HEADING_PROPERTIES.url, localPosition: MENU_HEADER_HEADING_PROPERTIES.localPosition, - color: UIT.colors.baseGray, emissive: false }); isOptionsHeadingRaised = false; @@ -2904,8 +2901,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Hover heading. Feedback.play(otherSide, Feedback.HOVER_BUTTON); Overlays.editOverlay(menuHeaderHeadingOverlay, { + url: MENU_HEADER_HEADING_PROPERTIES.highlightURL, localPosition: Vec3.sum(MENU_HEADER_HEADING_PROPERTIES.localPosition, MENU_HEADER_HOVER_OFFSET), - color: UIT.colors.greenHighlight, emissive: true // TODO: This has no effect. }); Overlays.editOverlay(menuHeaderBackOverlay, { @@ -2924,8 +2921,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (isOptionsHeadingRaised) { // Unhover heading. Overlays.editOverlay(menuHeaderHeadingOverlay, { + url: MENU_HEADER_HEADING_PROPERTIES.url, localPosition: MENU_HEADER_HEADING_PROPERTIES.localPosition, - color: UIT.colors.baseGray, emissive: false }); Overlays.editOverlay(menuHeaderBackOverlay, { @@ -3372,7 +3369,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuHeaderOverlay = Overlays.addOverlay("cube", properties); properties = Object.clone(MENU_HEADER_HEADING_PROPERTIES); properties.parentID = menuHeaderOverlay; - menuHeaderHeadingOverlay = Overlays.addOverlay("cube", properties); + menuHeaderHeadingOverlay = Overlays.addOverlay("model", properties); properties = Object.clone(MENU_HEADER_BAR_PROPERTIES); properties.parentID = menuHeaderHeadingOverlay; menuHeaderBarOverlay = Overlays.addOverlay("model", properties); From 2afc84cf0bb85e7049863c00c5dc64129dc1788d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 24 Sep 2017 12:06:04 +1300 Subject: [PATCH 353/504] Display bounding box of currently selected groups --- scripts/vr-edit/modules/groups.js | 5 ++ scripts/vr-edit/modules/highlights.js | 35 +++++++++++- scripts/vr-edit/modules/selection.js | 7 +++ scripts/vr-edit/vr-edit.js | 82 ++++++++++++++++++++------- 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/scripts/vr-edit/modules/groups.js b/scripts/vr-edit/modules/groups.js index c3e4172ebe..9331071fe2 100644 --- a/scripts/vr-edit/modules/groups.js +++ b/scripts/vr-edit/modules/groups.js @@ -71,6 +71,10 @@ Groups = function () { return rootEntityIDs.indexOf(rootEntityID) !== -1; } + function getRootEntityIDs() { + return rootEntityIDs; + } + function groupsCount() { return selections.length; } @@ -264,6 +268,7 @@ Groups = function () { toggle: toggle, selection: selection, includes: includes, + rootEntityIDs: getRootEntityIDs, groupsCount: groupsCount, entitiesCount: entitiesCount, group: group, diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/vr-edit/modules/highlights.js index 7c8cb0e99f..d893e69f10 100644 --- a/scripts/vr-edit/modules/highlights.js +++ b/scripts/vr-edit/modules/highlights.js @@ -17,11 +17,14 @@ Highlights = function (side) { var handOverlay, entityOverlays = [], + boundingBoxOverlay, + isDisplayingBoundingBox = false, HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, SCALE_COLOR = { red: 0, green: 240, blue: 240 }, GROUP_COLOR = { red: 220, green: 60, blue: 220 }, HAND_HIGHLIGHT_ALPHA = 0.35, ENTITY_HIGHLIGHT_ALPHA = 0.8, + BOUNDING_BOX_ALPHA = 0.8, HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, LEFT_HAND = 0; @@ -43,6 +46,14 @@ Highlights = function (side) { visible: false }); + boundingBoxOverlay = Overlays.addOverlay("cube", { + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + function setHandHighlightRadius(radius) { var dimension = 2 * radius; Overlays.editOverlay(handOverlay, { @@ -75,7 +86,7 @@ Highlights = function (side) { }); } - function display(handIntersected, selection, entityIndex, overlayColor) { + function display(handIntersected, selection, entityIndex, boundingBox, overlayColor) { // Displays highlight for just entityIndex if non-null, otherwise highlights whole selection. var i, length; @@ -86,7 +97,8 @@ Highlights = function (side) { visible: handIntersected }); - if (entityIndex !== null) { + // Display entity overlays. + if (entityIndex !== null) { // Add/edit entity overlay for just entityIndex. maybeAddEntityOverlay(0); editEntityOverlay(0, selection[entityIndex], overlayColor); @@ -103,14 +115,30 @@ Highlights = function (side) { Overlays.deleteOverlay(entityOverlays[i]); entityOverlays.splice(i, 1); } + + // Update bounding box overlay. + if (boundingBox !== null) { + Overlays.editOverlay(boundingBoxOverlay, { + position: boundingBox.center, + rotation: boundingBox.orientation, + dimensions: boundingBox.dimensions, + color: overlayColor, + visible: true + }); + isDisplayingBoundingBox = true; + } else if (isDisplayingBoundingBox) { + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + isDisplayingBoundingBox = false; + } } function clear() { var i, length; - // Hide hand overlay. + // Hide hand and bounding box overlays. Overlays.editOverlay(handOverlay, { visible: false }); + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); // Delete entity overlays. for (i = 0, length = entityOverlays.length; i < length; i += 1) { @@ -122,6 +150,7 @@ Highlights = function (side) { function destroy() { clear(); Overlays.deleteOverlay(handOverlay); + Overlays.deleteOverlay(boundingBoxOverlay); } return { diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index a01eef5481..b26d5e46f6 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -99,6 +99,12 @@ SelectionManager = function (side) { traverseEntityTree(rootEntityID, selection, selectionProperties); } + function append(rootEntityID) { + // Add further entities to the selection. + // Assumes that rootEntityID is not already in the selection. + traverseEntityTree(rootEntityID, selection, selectionProperties); + } + function getIntersectedEntityID() { return intersectedEntityID; } @@ -754,6 +760,7 @@ SelectionManager = function (side) { return { select: select, + append: append, selection: getSelection, count: count, intersectedEntityID: getIntersectedEntityID, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 5f7b3bb732..212f85e487 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -690,6 +690,7 @@ if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), toolSelected === TOOL_COLOR || toolSelected === TOOL_PICK_COLOR ? selection.intersectedEntityIndex() : null, + null, toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); } @@ -702,6 +703,7 @@ if (toolSelected !== TOOL_SCALE || !otherEditor.isEditing(rootEntityID)) { highlights.display(intersection.handIntersected, selection.selection(), toolSelected === TOOL_COLOR || toolSelected === TOOL_PICK_COLOR ? selection.intersectedEntityIndex() : null, + null, toolSelected === TOOL_SCALE || otherEditor.isEditing(rootEntityID) ? highlights.SCALE_COLOR : highlights.HIGHLIGHT_COLOR); if (!intersection.laserIntersected && !isUIVisible) { @@ -815,12 +817,13 @@ function enterEditorGrouping() { if (!grouping.includes(rootEntityID)) { - highlights.display(false, selection.selection(), null, highlights.GROUP_COLOR); + highlights.display(false, selection.selection(), null, null, highlights.GROUP_COLOR); } if (toolSelected === TOOL_GROUP_BOX) { if (!grouping.includes(rootEntityID)) { Feedback.play(side, Feedback.SELECT_ENTITY); - grouping.selectInBox(selection.selection()); + grouping.toggle(selection.selection()); + grouping.selectInBox(); } else { Feedback.play(side, Feedback.GENERAL_ERROR); } @@ -1281,13 +1284,14 @@ // Grouping highlights and functions. var groups, - isSelectInBox = false, highlights, exludedLeftRootEntityID = null, exludedrightRootEntityID = null, excludedRootEntityIDs = [], hasHighlights = false, - hasSelectionChanged = false; + hasSelectionChanged = false, + isSelectInBox = false, + selectInBoxSelection = null; // Selection of all groups combined in order to calculate bounding box. if (!this instanceof Grouping) { return new Grouping(); @@ -1298,6 +1302,14 @@ function toggle(selection) { groups.toggle(selection); + if (isSelectInBox) { + // When selecting in a box, toggle() is only called to add entities to the selection. + if (selectInBoxSelection.count() === 0) { + selectInBoxSelection.select(selection[0].id); + } else { + selectInBoxSelection.append(selection[0].id); + } + } if (groups.groupsCount() === 0) { hasHighlights = false; highlights.clear(); @@ -1307,31 +1319,54 @@ } } - function updateSelectInBox() { - // TODO: Calculate bounding box. + function selectInBox() { + // Add any entities or groups of entities wholly within bounding box of current selection. + // Must be wholly within otherwise selection could grow uncontrollably. + var boundingBox; - // TODO: Select further entities per bounding box. + if (selectInBoxSelection.count() > 1) { + boundingBox = selectInBoxSelection.boundingBox(); - // TODO: Update bounding box overlay. + // TODO: Select further entities per bounding box. + + hasSelectionChanged = true; + } } function startSelectInBox() { + // Start automatically selecting entities in bounding box of current selection. + var rootEntityIDs, + i, + length; + isSelectInBox = true; - // TODO: Create bounding box overlay. + // Select entities current groups combined. + selectInBoxSelection = new SelectionManager(); + rootEntityIDs = groups.rootEntityIDs(); + if (rootEntityIDs.length > 0) { + selectInBoxSelection.select(rootEntityIDs[0]); + for (i = 1, length = rootEntityIDs.length; i < length; i += 1) { + selectInBoxSelection.append(rootEntityIDs[i]); + } + } - updateSelectInBox(); - } + // Add any enclosed entities. + selectInBox(); - function selectInBox(selection) { - toggle(selection); - updateSelectInBox(); + // Show bounding box overlay plus any newly selected entities. + hasSelectionChanged = true; } function stopSelectInBox() { - isSelectInBox = false; + // Stop automatically selecting entities within bounding box of current selection. + selectInBoxSelection.destroy(); + selectInBoxSelection = null; - // TODO: Delete bounding box overlay. + // Hide bounding box overlay. + hasSelectionChanged = true; + + isSelectInBox = false; } function includes(rootEntityID) { @@ -1355,7 +1390,9 @@ } function update(leftRootEntityID, rightRootEntityID) { - var hasExludedRootEntitiesChanged; + // Update highlights displayed, excluding entities highlighted by left or right hands. + var hasExludedRootEntitiesChanged, + boundingBox; hasExludedRootEntitiesChanged = leftRootEntityID !== exludedLeftRootEntityID || rightRootEntityID !== exludedrightRootEntityID; @@ -1376,12 +1413,15 @@ exludedrightRootEntityID = rightRootEntityID; } - highlights.display(false, groups.selection(excludedRootEntityIDs), null, highlights.GROUP_COLOR); - + boundingBox = isSelectInBox && selectInBoxSelection.count() > 1 ? selectInBoxSelection.boundingBox() : null; + highlights.display(false, groups.selection(excludedRootEntityIDs), null, boundingBox, highlights.GROUP_COLOR); hasSelectionChanged = false; } function clear() { + if (isSelectInBox) { + stopSelectInBox(); + } groups.clear(); highlights.clear(); } @@ -1395,6 +1435,10 @@ highlights.destroy(); highlights = null; } + if (selectInBoxSelection) { + selectInBoxSelection.destroy(); + selectInBoxSelection = null; + } } return { From 4e0efdaa1a52007eac522200feea341612c0e32a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 24 Sep 2017 16:55:15 +1300 Subject: [PATCH 354/504] Automatically add entities in bounding box to currently selected groups --- scripts/vr-edit/modules/selection.js | 16 +++- scripts/vr-edit/vr-edit.js | 128 ++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 16 deletions(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index b26d5e46f6..4821da1429 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -16,6 +16,7 @@ SelectionManager = function (side) { "use strict"; var selection = [], // Subset of properties to provide externally. + selectionIDs = [], selectionProperties = [], // Full set of properties for history. intersectedEntityID = null, intersectedEntityIndex, @@ -42,7 +43,7 @@ SelectionManager = function (side) { return new SelectionManager(side); } - function traverseEntityTree(id, selection, selectionProperties) { + function traverseEntityTree(id, selection, selectionIDs, selectionProperties) { // Recursively traverses tree of entities and their children, gather IDs and properties. // The root entity is always the first entry. var children, @@ -65,6 +66,7 @@ SelectionManager = function (side) { collisionless: properties.collisionless, userData: properties.userData }); + selectionIDs.push(id); selectionProperties.push({ entityID: id, properties: properties }); if (id === intersectedEntityID) { @@ -74,7 +76,7 @@ SelectionManager = function (side) { children = Entities.getChildrenIDs(id); for (i = 0, length = children.length; i < length; i += 1) { if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { - traverseEntityTree(children[i], selection, selectionProperties); + traverseEntityTree(children[i], selection, selectionIDs, selectionProperties); } } } @@ -95,14 +97,15 @@ SelectionManager = function (side) { // Find all children. selection = []; + selectionIDs = []; selectionProperties = []; - traverseEntityTree(rootEntityID, selection, selectionProperties); + traverseEntityTree(rootEntityID, selection, selectionIDs, selectionProperties); } function append(rootEntityID) { // Add further entities to the selection. // Assumes that rootEntityID is not already in the selection. - traverseEntityTree(rootEntityID, selection, selectionProperties); + traverseEntityTree(rootEntityID, selection, selectionIDs, selectionProperties); } function getIntersectedEntityID() { @@ -121,6 +124,10 @@ SelectionManager = function (side) { return selection; } + function contains(entityID) { + return selectionIDs.indexOf(entityID) !== -1; + } + function count() { return selection.length; } @@ -762,6 +769,7 @@ SelectionManager = function (side) { select: select, append: append, selection: getSelection, + contains: contains, count: count, intersectedEntityID: getIntersectedEntityID, intersectedEntityIndex: getIntersectedEntityIndex, diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 212f85e487..4d187572b4 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1285,13 +1285,14 @@ var groups, highlights, + selectInBoxSelection, // Selection of all entities selected. + groupSelection, // New group to add to selection. exludedLeftRootEntityID = null, exludedrightRootEntityID = null, excludedRootEntityIDs = [], hasHighlights = false, hasSelectionChanged = false, - isSelectInBox = false, - selectInBoxSelection = null; // Selection of all groups combined in order to calculate bounding box. + isSelectInBox = false; if (!this instanceof Grouping) { return new Grouping(); @@ -1299,6 +1300,74 @@ groups = new Groups(); highlights = new Highlights(); + selectInBoxSelection = new SelectionManager(); + groupSelection = new SelectionManager(); + + function getAllChildrenIDs(entityID) { + var childrenIDs = [], + ENTITY_TYPE = "entity"; + + function traverseEntityTree(id) { + var children, + i, + length; + children = Entities.getChildrenIDs(id); + for (i = 0, length = children.length; i < length; i += 1) { + if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { + childrenIDs.push(children[i]); + traverseEntityTree(children[i]); + } + } + } + + traverseEntityTree(entityID); + return childrenIDs; + } + + function isInsideBoundingBox(entityID, boundingBox) { + // Are all 8 corners of entityID's bounding box inside boundingBox? + var entityProperties, + cornerPosition, + boundingBoxInverseRotation, + boundingBoxHalfDimensions, + isInside = true, + i, + CORNER_REGISTRATION_OFFSETS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 } + ], + NUM_CORNERS = 8; + + entityProperties = Entities.getEntityProperties(entityID, ["position", "rotation", "dimensions", + "registrationPoint"]); + + // Convert entity coordinates into boundingBox coordinates. + boundingBoxInverseRotation = Quat.inverse(boundingBox.orientation); + entityProperties.position = Vec3.multiplyQbyV(boundingBoxInverseRotation, + Vec3.subtract(entityProperties.position, boundingBox.center)); + entityProperties.rotation = Quat.multiply(boundingBoxInverseRotation, entityProperties.rotation); + + // Check all 8 corners of entity's bounding box are inside the given bounding box. + boundingBoxHalfDimensions = Vec3.multiply(0.5, boundingBox.dimensions); + i = 0; + while (isInside && i < NUM_CORNERS) { + cornerPosition = Vec3.sum(entityProperties.position, Vec3.multiplyQbyV(entityProperties.rotation, + Vec3.multiplyVbyV(Vec3.subtract(CORNER_REGISTRATION_OFFSETS[i], entityProperties.registrationPoint), + entityProperties.dimensions))); + isInside = Math.abs(cornerPosition.x) <= boundingBoxHalfDimensions.x + && Math.abs(cornerPosition.y) <= boundingBoxHalfDimensions.y + && Math.abs(cornerPosition.z) <= boundingBoxHalfDimensions.z; + i += 1; + } + + return isInside; + } function toggle(selection) { groups.toggle(selection); @@ -1322,14 +1391,47 @@ function selectInBox() { // Add any entities or groups of entities wholly within bounding box of current selection. // Must be wholly within otherwise selection could grow uncontrollably. - var boundingBox; + var boundingBox, + entityIDs, + checkedEntityIDs = [], + entityID, + rootID, + groupIDs, + doIncludeGroup, + i, + lengthI, + j, + lengthJ; if (selectInBoxSelection.count() > 1) { boundingBox = selectInBoxSelection.boundingBox(); - - // TODO: Select further entities per bounding box. - - hasSelectionChanged = true; + entityIDs = Entities.findEntities(boundingBox.center, Vec3.length(boundingBox.dimensions) / 2); + for (i = 0, lengthI = entityIDs.length; i < lengthI; i += 1) { + entityID = entityIDs[i]; + if (checkedEntityIDs.indexOf(entityID) === -1) { + rootID = Entities.rootOf(entityID); + if (!selectInBoxSelection.contains(entityID) && Entities.hasEditableRoot(rootID)) { + groupIDs = [rootID].concat(getAllChildrenIDs(rootID)); + doIncludeGroup = true; + j = 0; + lengthJ = groupIDs.length; + while (doIncludeGroup && j < lengthJ) { + doIncludeGroup = isInsideBoundingBox(groupIDs[j], boundingBox); + j += 1; + } + checkedEntityIDs = checkedEntityIDs.concat(groupIDs); + if (doIncludeGroup) { + groupSelection.select(rootID); + groups.toggle(groupSelection.selection()); + groupSelection.clear(); + selectInBoxSelection.append(rootID); + hasSelectionChanged = true; + } + } else { + checkedEntityIDs.push(rootID); + } + } + } } } @@ -1338,11 +1440,10 @@ var rootEntityIDs, i, length; - + isSelectInBox = true; // Select entities current groups combined. - selectInBoxSelection = new SelectionManager(); rootEntityIDs = groups.rootEntityIDs(); if (rootEntityIDs.length > 0) { selectInBoxSelection.select(rootEntityIDs[0]); @@ -1360,10 +1461,9 @@ function stopSelectInBox() { // Stop automatically selecting entities within bounding box of current selection. - selectInBoxSelection.destroy(); - selectInBoxSelection = null; // Hide bounding box overlay. + selectInBoxSelection.clear(); hasSelectionChanged = true; isSelectInBox = false; @@ -1420,7 +1520,7 @@ function clear() { if (isSelectInBox) { - stopSelectInBox(); + selectInBoxSelection.clear(); } groups.clear(); highlights.clear(); @@ -1439,6 +1539,10 @@ selectInBoxSelection.destroy(); selectInBoxSelection = null; } + if (groupSelection) { + groupSelection.destroy(); + groupSelection = null; + } } return { From bbc6e5d1b592414ba6f900ae066261fd3c113274 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 24 Sep 2017 17:06:42 +1300 Subject: [PATCH 355/504] Fix bounding box isn't cleared when exit tool with header or grip click --- scripts/vr-edit/vr-edit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 4d187572b4..8a1e131a43 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -1520,7 +1520,7 @@ function clear() { if (isSelectInBox) { - selectInBoxSelection.clear(); + stopSelectInBox(); } groups.clear(); highlights.clear(); @@ -1685,6 +1685,9 @@ Feedback.play(dominantHand, Feedback.SELECT_ENTITY); } grouping.clear(); + if (toolSelected === TOOL_GROUP_BOX) { + grouping.startSelectInBox(); + } break; case "setColor": From f31e9df21cffb3842a746d03e01fe42356ef3140 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 25 Sep 2017 10:31:49 +0200 Subject: [PATCH 356/504] Fixed incorrect pixel byte size computation in KTX --- libraries/ktx/src/khronos/KHR.h | 135 ++++++++++++++++++++++++++++++++ libraries/ktx/src/ktx/KTX.cpp | 17 ++-- libraries/ktx/src/ktx/KTX.h | 2 +- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index ae23d9cefe..cda22513ee 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -209,6 +209,137 @@ namespace khronos { COMPRESSED_SIGNED_RG11_EAC = 0x9273, }; + inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) { + switch (format) { + case InternalFormat::R8: + case InternalFormat::R8_SNORM: + return 8; + case InternalFormat::R16: + case InternalFormat::R16_SNORM: + case InternalFormat::RG8: + case InternalFormat::RG8_SNORM: + return 16; + case InternalFormat::RG16: + case InternalFormat::RG16_SNORM: + return 16; + case InternalFormat::R3_G3_B2: + return 8; + case InternalFormat::RGB4: + return 12; + case InternalFormat::RGB5: + case InternalFormat::RGB565: + return 16; + case InternalFormat::RGB8: + case InternalFormat::RGB8_SNORM: + return 24; + case InternalFormat::RGB10: + // TODO: check if this is really the case + return 32; + case InternalFormat::RGB12: + // TODO: check if this is really the case + return 48; + case InternalFormat::RGB16: + case InternalFormat::RGB16_SNORM: + return 48; + case InternalFormat::RGBA2: + return 8; + case InternalFormat::RGBA4: + case InternalFormat::RGB5_A1: + return 16; + case InternalFormat::RGBA8: + case InternalFormat::RGBA8_SNORM: + case InternalFormat::RGB10_A2: + case InternalFormat::RGB10_A2UI: + return 32; + case InternalFormat::RGBA12: + return 48; + case InternalFormat::RGBA16: + case InternalFormat::RGBA16_SNORM: + return 64; + case InternalFormat::SRGB8: + return 24; + case InternalFormat::SRGB8_ALPHA8: + return 32; + case InternalFormat::R16F: + return 16; + case InternalFormat::RG16F: + return 32; + case InternalFormat::RGB16F: + return 48; + case InternalFormat::RGBA16F: + return 64; + case InternalFormat::R32F: + return 32; + case InternalFormat::RG32F: + return 64; + case InternalFormat::RGB32F: + return 96; + case InternalFormat::RGBA32F: + return 128; + case InternalFormat::R11F_G11F_B10F: + case InternalFormat::RGB9_E5: + return 32; + case InternalFormat::R8I: + case InternalFormat::R8UI: + return 8; + case InternalFormat::R16I: + case InternalFormat::R16UI: + return 16; + case InternalFormat::R32I: + case InternalFormat::R32UI: + return 32; + case InternalFormat::RG8I: + case InternalFormat::RG8UI: + return 16; + case InternalFormat::RG16I: + case InternalFormat::RG16UI: + return 32; + case InternalFormat::RG32I: + case InternalFormat::RG32UI: + return 64; + case InternalFormat::RGB8I: + case InternalFormat::RGB8UI: + return 24; + case InternalFormat::RGB16I: + case InternalFormat::RGB16UI: + return 48; + case InternalFormat::RGB32I: + case InternalFormat::RGB32UI: + return 96; + case InternalFormat::RGBA8I: + case InternalFormat::RGBA8UI: + return 32; + case InternalFormat::RGBA16I: + case InternalFormat::RGBA16UI: + return 64; + case InternalFormat::RGBA32I: + case InternalFormat::RGBA32UI: + return 128; + case InternalFormat::DEPTH_COMPONENT16: + return 16; + case InternalFormat::DEPTH_COMPONENT24: + return 24; + case InternalFormat::DEPTH_COMPONENT32: + case InternalFormat::DEPTH_COMPONENT32F: + case InternalFormat::DEPTH24_STENCIL8: + return 32; + case InternalFormat::DEPTH32F_STENCIL8: + // TODO : check if this true + return 40; + case InternalFormat::STENCIL_INDEX1: + return 1; + case InternalFormat::STENCIL_INDEX4: + return 4; + case InternalFormat::STENCIL_INDEX8: + return 8; + case InternalFormat::STENCIL_INDEX16: + return 16; + + default: + return 0; + } + } + template inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) }; @@ -252,6 +383,10 @@ namespace khronos { } } + inline uint8_t evalCompressedBlockBitSize(InternalFormat format) { + return evalCompressedBlockSize(format) * 8; + } + enum class BaseInternalFormat : uint32_t { // GL 4.4 Table 8.11 DEPTH_COMPONENT = 0x1902, diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 3b1a12cba7..5bd45549c1 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -54,15 +54,13 @@ uint32_t Header::evalPixelOrBlockDepth(uint32_t level) const { return evalMipDimension(level, getPixelDepth()); } -size_t Header::evalPixelOrBlockSize() const { +size_t Header::evalPixelOrBlockBitSize() const { size_t result = 0; + auto format = getGLInternaFormat(); if (isCompressed()) { - auto format = getGLInternaFormat(); - result = khronos::gl::texture::evalCompressedBlockSize(format); + result = khronos::gl::texture::evalCompressedBlockBitSize(format); } else { - // FIXME should really be using the internal format, not the base internal format - auto baseFormat = getGLBaseInternalFormat(); - result = khronos::gl::texture::evalComponentCount(baseFormat); + result = khronos::gl::texture::evalUncompressedBlockBitSize(format); } if (0 == result) { @@ -73,11 +71,14 @@ size_t Header::evalPixelOrBlockSize() const { size_t Header::evalRowSize(uint32_t level) const { auto pixWidth = evalPixelOrBlockWidth(level); - auto pixSize = evalPixelOrBlockSize(); + auto pixSize = evalPixelOrBlockBitSize(); if (pixSize == 0) { return 0; } - return evalPaddedSize(pixWidth * pixSize); + auto totalByteSize = pixWidth * pixSize; + // Round to the nearest upper byte size + totalByteSize = (totalByteSize / 8) + (((totalByteSize % 8) != 0) & 1); + return evalPaddedSize(totalByteSize); } size_t Header::evalFaceSize(uint32_t level) const { diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 8dc4ec7a47..54a8188a42 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -170,7 +170,7 @@ namespace ktx { uint32_t evalPixelOrBlockHeight(uint32_t level) const; uint32_t evalPixelOrBlockDepth(uint32_t level) const; - size_t evalPixelOrBlockSize() const; + size_t evalPixelOrBlockBitSize() const; size_t evalRowSize(uint32_t level) const; size_t evalFaceSize(uint32_t level) const; size_t evalImageSize(uint32_t level) const; From d64e3aca550f27a04ccda0354d72fd66f2c29a14 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Sep 2017 11:10:05 +1300 Subject: [PATCH 357/504] Polyvoxels don't have a color property --- scripts/vr-edit/modules/selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/selection.js b/scripts/vr-edit/modules/selection.js index a01eef5481..d060e65171 100644 --- a/scripts/vr-edit/modules/selection.js +++ b/scripts/vr-edit/modules/selection.js @@ -31,7 +31,7 @@ SelectionManager = function (side) { startOrientation, isEditing = false, ENTITY_TYPE = "entity", - ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine", "PolyVox"], + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine"], ENTITY_TYPES_2D = ["Text", "Web"], MIN_HISTORY_MOVE_DISTANCE = 0.005, MIN_HISTORY_ROTATE_ANGLE = 0.017453; // Radians = 1 degree. From 9b7197cf3f4efedf611f34a1acc77eefbfd17a8d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Sep 2017 15:36:40 +1300 Subject: [PATCH 358/504] Don't highlight entity you're inside unless you laser it --- scripts/vr-edit/vr-edit.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 2bcd3533cd..8a4885db6a 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -452,9 +452,8 @@ return rootEntityID; } - function isCameraOutsideEntity(entityID) { + function isCameraOutsideEntity(entityID, entityPosition) { var cameraPosition, - grabPosition, pickRay, PRECISION_PICKING = true, NO_EXCLUDE_IDS = [], @@ -462,11 +461,10 @@ intersection; cameraPosition = Camera.position; - grabPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); pickRay = { origin: cameraPosition, - direction: Vec3.normalize(Vec3.subtract(grabPosition, cameraPosition)), - length: Vec3.distance(grabPosition, cameraPosition) + direction: Vec3.normalize(Vec3.subtract(entityPosition, cameraPosition)), + length: Vec3.distance(entityPosition, cameraPosition) }; intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, [entityID], NO_EXCLUDE_IDS, VISIBLE_ONLY); @@ -894,7 +892,8 @@ function update() { - var showUI, + var isTriggerPressed, + showUI, previousState = editorState, doUpdateState, color; @@ -902,11 +901,12 @@ intersection = getIntersection(); isTriggerClicked = hand.triggerClicked(); isGripClicked = hand.gripClicked(); + isTriggerPressed = hand.triggerPressed(); // Hide UI if hand is intersecting entity and camera is outside entity, or it hand is intersecting stretch handle. if (dominantHand !== side) { - showUI = !intersection.handIntersected - || (intersection.entityID !== null && !isCameraOutsideEntity(intersection.entityID)); + showUI = !intersection.handIntersected || (intersection.entityID !== null + && !isCameraOutsideEntity(intersection.entityID, intersection.intersection)); if (showUI !== isUIVisible) { isUIVisible = !isUIVisible; ui.setVisible(isUIVisible); @@ -924,9 +924,13 @@ break; case EDITOR_SEARCHING: if (hand.valid() - && (!intersection.entityID || !(intersection.editableEntity || toolSelected === TOOL_PICK_COLOR)) && !(intersection.overlayID && !wasTriggerClicked && isTriggerClicked - && otherEditor.isHandle(intersection.overlayID))) { + && otherEditor.isHandle(intersection.overlayID)) + && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab + && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed)) + && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked)) { // No transition. updateState(); updateTool(); @@ -940,7 +944,8 @@ rootEntityID = otherEditor.rootEntityID(); setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab) { + && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab + && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed)) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); @@ -991,6 +996,7 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed) && !(!wasTriggerClicked && isTriggerClicked && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID @@ -1070,7 +1076,8 @@ } else { setState(EDITOR_GRABBING); } - } else if (!intersection.entityID || !intersection.editableEntity) { + } else if (!intersection.entityID || !intersection.editableEntity + || (!isCameraOutsideEntity(intersection.entityID, intersection.intersection) && !isTriggerPressed)) { setState(EDITOR_SEARCHING); } else { log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); From 76889d1b6708b1c669f14c4a98ab3c7e9e8edf88 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 12:04:05 +1300 Subject: [PATCH 359/504] Improve handle scaling of entity with near-zero dimension --- scripts/vr-edit/vr-edit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index a2c4ef7997..89eae8da52 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -391,6 +391,7 @@ initialHandleDistance, laserOffset, MIN_SCALE = 0.001, + MIN_SCALE_HANDLE_DISTANCE = 0.0001, getIntersection, // Function. intersection, @@ -548,6 +549,7 @@ + Vec3.dot(Vec3.subtract(otherTargetPosition, Overlays.getProperty(overlayID, "position")), scaleAxis); initialHandleDistance = Math.abs(Vec3.dot(Vec3.subtract(otherTargetPosition, initialTargetPosition), scaleAxis)); initialHandleDistance -= handleTargetOffset; + initialHandleDistance = Math.max(initialHandleDistance, MIN_SCALE_HANDLE_DISTANCE); selection.startHandleScaling(initialTargetPosition); handles.startScaling(); @@ -629,6 +631,7 @@ scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handleDistance = Vec3.dot(Vec3.subtract(otherTargetPosition, targetPosition), scaleAxis); handleDistance -= handleTargetOffset; + handleDistance = Math.max(handleDistance, MIN_SCALE_HANDLE_DISTANCE); // Scale selection relative to initial dimensions. scale = handleDistance / initialHandleDistance; From 895ffaf1f9a2e582a17082293c377120393b2277 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 15:09:10 +1300 Subject: [PATCH 360/504] Fix group clear button messing up select-in-box state --- scripts/vr-edit/modules/toolsMenu.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 5640a29937..35ac7347ba 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -2776,7 +2776,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { break; case "clearGroupSelection": - optionsToggles.groupSelectionBoxButton = false; index = clearGroupingButtonIndex; Overlays.editOverlay(optionsOverlays[index], { color: optionsItems[index].properties.color From d4fc26f3b728e792010d191d7ed0abb6d5a7ae80 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 15:22:33 +1300 Subject: [PATCH 361/504] Fix header highlighting if close and reeopen app with tool selected --- scripts/vr-edit/modules/toolsMenu.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 35ac7347ba..4a3b8c94d9 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -2473,9 +2473,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { pressedItem = null; isOptionsOpen = false; - - // Display menu items. - openMenu(); } function displayFooter() { @@ -2531,6 +2528,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function clearTool() { closeOptions(); + openMenu(); } function setPresetsLabelToCustom() { @@ -2924,6 +2922,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { case "closeOptions": closeOptions(); + openMenu(); break; case "clearTool": @@ -3659,17 +3658,17 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (!isDisplaying) { return; } + + if (isOptionsOpen) { + closeOptions(); + } + Overlays.deleteOverlay(menuOriginOverlay); // Automatically deletes all other overlays because they're children. menuOverlays = []; menuHoverOverlays = []; menuIconOverlays = []; menuLabelOverlays = []; menuEnabled = []; - optionsOverlays = []; - optionsOverlaysLabels = []; - optionsOverlaysSublabels = []; - optionsExtraOverlays = []; - optionsEnabled = []; footerOverlays = []; footerHoverOverlays = []; footerIconOverlays = []; From e623b1e0a531660191ca1f308ca6d5fd7817acdf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 19:21:07 +1300 Subject: [PATCH 362/504] Alternative, more robust handle scaling method --- scripts/vr-edit/vr-edit.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 89eae8da52..81db666727 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -388,6 +388,7 @@ handleUnitScaleAxis, handleScaleDirections, handleTargetOffset, + initialHandToTargetOffset, initialHandleDistance, laserOffset, MIN_SCALE = 0.001, @@ -539,8 +540,12 @@ isScalingWithHand = intersection.handIntersected; + // Grab center of selection. otherTargetPosition = targetPosition; - initialTargetPosition = getScaleTargetPosition(); + initialTargetPosition = selection.boundingBox().center; + initialHandToTargetOffset = Vec3.subtract(initialTargetPosition, hand.position()); + + // Initial handle offset from center of selection. selectionPositionAndOrientation = selection.getPositionAndOrientation(); handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. @@ -551,6 +556,7 @@ initialHandleDistance -= handleTargetOffset; initialHandleDistance = Math.max(initialHandleDistance, MIN_SCALE_HANDLE_DISTANCE); + // Start scaling. selection.startHandleScaling(initialTargetPosition); handles.startScaling(); isHandleScaling = true; @@ -626,8 +632,10 @@ deltaHandOrientation = Quat.multiply(hand.orientation(), initialHandOrientationInverse); selectionOrientation = Quat.multiply(deltaHandOrientation, initialSelectionOrientation); + // Position selection per grabbing hand. + targetPosition = Vec3.sum(hand.position(), Vec3.multiplyQbyV(deltaHandOrientation, initialHandToTargetOffset)); + // Desired distance of handle from other hand - targetPosition = getScaleTargetPosition(); scaleAxis = Vec3.multiplyQbyV(selection.getPositionAndOrientation().orientation, handleUnitScaleAxis); handleDistance = Vec3.dot(Vec3.subtract(otherTargetPosition, targetPosition), scaleAxis); handleDistance -= handleTargetOffset; From c2471e340a1f9438f9e6e3555e5bcaa739f1a14b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 21:49:02 +1300 Subject: [PATCH 363/504] Reduce number of calls to Script.resolvePath --- scripts/vr-edit/modules/createPalette.js | 28 ++- scripts/vr-edit/modules/toolIcon.js | 9 +- scripts/vr-edit/modules/toolsMenu.js | 215 +++++++++++------------ 3 files changed, 117 insertions(+), 135 deletions(-) diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/vr-edit/modules/createPalette.js index da7740d38a..968be03ed4 100644 --- a/scripts/vr-edit/modules/createPalette.js +++ b/scripts/vr-edit/modules/createPalette.js @@ -80,7 +80,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { }, PALETTE_TITLE_PROPERTIES = { - url: "../assets/create/create-heading.svg", + url: Script.resolvePath("../assets/create/create-heading.svg"), scale: 0.0363, localPosition: { x: 0, @@ -155,7 +155,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/cube.fbx" + url: Script.resolvePath("../assets/create/cube.fbx") } }, entity: { @@ -167,7 +167,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/sphere.fbx" + url: Script.resolvePath("../assets/create/sphere.fbx") } }, entity: { @@ -179,7 +179,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/tetrahedron.fbx" + url: Script.resolvePath("../assets/create/tetrahedron.fbx") } }, entity: { @@ -192,7 +192,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/octahedron.fbx" + url: Script.resolvePath("../assets/create/octahedron.fbx") } }, entity: { @@ -205,7 +205,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/icosahedron.fbx" + url: Script.resolvePath("../assets/create/icosahedron.fbx") } }, entity: { @@ -218,7 +218,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/dodecahedron.fbx" + url: Script.resolvePath("../assets/create/dodecahedron.fbx") } }, entity: { @@ -231,7 +231,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/hexagon.fbx", + url: Script.resolvePath("../assets/create/hexagon.fbx"), dimensions: { x: 0.02078, y: 0.024, z: 0.024 }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } @@ -246,7 +246,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/prism.fbx", + url: Script.resolvePath("../assets/create/prism.fbx"), localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } }, @@ -260,7 +260,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/octagon.fbx", + url: Script.resolvePath("../assets/create/octagon.fbx"), dimensions: { x: 0.023805, y: 0.024, z: 0.024 }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } @@ -275,7 +275,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/cylinder.fbx", + url: Script.resolvePath("../assets/create/cylinder.fbx"), localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } }, @@ -289,7 +289,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/cone.fbx", + url: Script.resolvePath("../assets/create/cone.fbx"), localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } }, @@ -303,7 +303,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { { icon: { properties: { - url: "../assets/create/circle.fbx", + url: Script.resolvePath("../assets/create/circle.fbx"), dimensions: { x: 0.024, y: 0.0005, z: 0.024 }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) } @@ -478,7 +478,6 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { paletteHeaderBarOverlay = Overlays.addOverlay("model", properties); properties = Object.clone(PALETTE_TITLE_PROPERTIES); properties.parentID = paletteHeaderHeadingOverlay; - properties.url = Script.resolvePath(properties.url); paletteTitleOverlay = Overlays.addOverlay("image3d", properties); // Palette background. @@ -504,7 +503,6 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(PALETTE_ITEM.icon.properties); properties = Object.merge(properties, PALETTE_ITEMS[i].icon.properties); properties.parentID = paletteItemHoverOverlays[i]; - properties.url = Script.resolvePath(properties.url); iconOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.icon.overlay, properties); } diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index 6ea6965a36..cac31ed83f 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -26,7 +26,7 @@ ToolIcon = function (side) { MODEL_TYPE = "model", MODEL_PROPERTIES = { - url: "../assets/tools/tool-icon.fbx", + url: Script.resolvePath("../assets/tools/tool-icon.fbx"), dimensions: Vec3.multiply(MODEL_SCALE, MODEL_DIMENSIONS), solid: true, alpha: 1.0, @@ -114,7 +114,6 @@ ToolIcon = function (side) { // Model. properties = Object.clone(MODEL_PROPERTIES); - properties.url = Script.resolvePath(properties.url); properties.parentJointIndex = handJointIndex; properties.localPosition = localPosition; properties.localRotation = localRotation; @@ -124,7 +123,7 @@ ToolIcon = function (side) { properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, ICON_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(iconInfo.icon.properties.url); + properties.url = iconInfo.icon.properties.url; properties.dimensions = { x: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.x, y: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.y @@ -136,7 +135,7 @@ ToolIcon = function (side) { properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, LABEL_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(iconInfo.label.properties.url); + properties.url = iconInfo.label.properties.url; properties.scale = LABEL_SCALE_FACTOR * iconInfo.label.properties.scale; Overlays.addOverlay(IMAGE_TYPE, properties); @@ -144,7 +143,7 @@ ToolIcon = function (side) { properties = Object.clone(IMAGE_PROPERTIES); properties = Object.merge(properties, SUBLABEL_PROPERTIES); properties.parentID = modelOverlay; - properties.url = Script.resolvePath(iconInfo.sublabel.properties.url); + properties.url = iconInfo.sublabel.properties.url; properties.scale = LABEL_SCALE_FACTOR * iconInfo.sublabel.properties.scale; Overlays.addOverlay(IMAGE_TYPE, properties); } diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/vr-edit/modules/toolsMenu.js index 4a3b8c94d9..182b53179f 100644 --- a/scripts/vr-edit/modules/toolsMenu.js +++ b/scripts/vr-edit/modules/toolsMenu.js @@ -129,7 +129,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_BACK_PROPERTIES = { - url: "../assets/tools/back-icon.svg", + url: Script.resolvePath("../assets/tools/back-icon.svg"), dimensions: { x: 0.0069, y: 0.0107 }, localPosition: { x: -MENU_HEADER_HEADING_PROPERTIES.dimensions.x / 2 + 0.0118 + 0.0069 / 2, @@ -146,7 +146,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_TITLE_PROPERTIES = { - url: "../assets/tools/tools-heading.svg", + url: Script.resolvePath("../assets/tools/tools-heading.svg"), scale: 0.0327, localPosition: { x: 0, @@ -162,7 +162,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true }, - MENU_HEADER_TITLE_BACK_URL = "../assets/tools/back-heading.svg", + MENU_HEADER_TITLE_BACK_URL = Script.resolvePath("../assets/tools/back-heading.svg"), MENU_HEADER_TITLE_BACK_SCALE = 0.0256, MENU_HEADER_ICON_OFFSET = { @@ -173,7 +173,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_ICON_PROPERTIES = { - url: "../assets/tools/color-icon.svg", // Initial value so that the overlay is initialized OK. + url: Script.resolvePath("../assets/tools/color-icon.svg"), // Initial value so that the overlay is initialized OK. dimensions: { x: 0.01, y: 0.01 }, // "" localPosition: Vec3.ZERO, // "" localRotation: Quat.ZERO, @@ -390,7 +390,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { "horizontalRule": { overlay: "image3d", properties: { - url: "../assets/horizontal-rule.svg", + url: Script.resolvePath("../assets/horizontal-rule.svg"), dimensions: { x: UIT.dimensions.panel.x - 2 * UIT.dimensions.leftMargin, y: 0.001 }, localRotation: Quat.ZERO, color: UIT.colors.baseGrayShadow, @@ -432,7 +432,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { zeroIndicator: { overlay: "image3d", properties: { - url: "../assets/horizontal-rule.svg", + url: Script.resolvePath("../assets/horizontal-rule.svg"), dimensions: { x: 0.02, y: 0.001 }, localRotation: Quat.ZERO, color: UIT.colors.lightGrayText, @@ -561,6 +561,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, + PICKLIST_UP_ARROW = Script.resolvePath("../assets/tools/common/up-arrow.svg"), + PICKLIST_DOWN_ARROW = Script.resolvePath("../assets/tools/common/down-arrow.svg"), + BUTTON_UI_ELEMENTS = ["button", "menuButton", "toggleButton", "swatch"], MENU_HOVER_DELTA = { x: 0, y: 0, z: 0.006 }, @@ -592,7 +595,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/color/swatches-label.svg", + url: Script.resolvePath("../assets/tools/color/swatches-label.svg"), scale: 0.0345, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0345 / 2, @@ -761,8 +764,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { z: UIT.dimensions.panel.z / 2 + UIT.dimensions.buttonDimensions.z / 2 } }, - imageURL: "../assets/tools/color/color-circle.png", - imageOverlayURL: "../assets/tools/color/color-circle-black.png", + imageURL: Script.resolvePath("../assets/tools/color/color-circle.png"), + imageOverlayURL: Script.resolvePath("../assets/tools/color/color-circle-black.png"), command: { method: "setColorPerCircle" } @@ -779,9 +782,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }) }, useBaseColor: true, - imageURL: "../assets/tools/color/slider-white.png", + imageURL: Script.resolvePath("../assets/tools/color/slider-white.png"), // Alpha PNG created by overlaying two black-to-transparent gradients in order to achieve visual effect. - imageOverlayURL: "../assets/tools/color/slider-alpha.png", + imageOverlayURL: Script.resolvePath("../assets/tools/color/slider-alpha.png"), command: { method: "setColorPerSlider" } @@ -798,7 +801,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/color/pick-color-label.svg", + url: Script.resolvePath("../assets/tools/color/pick-color-label.svg"), scale: 0.0120 }, command: { @@ -830,7 +833,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/common/actions-label.svg", + url: Script.resolvePath("../assets/tools/common/actions-label.svg"), scale: 0.0276, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, @@ -861,7 +864,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/common/finish-label.svg", + url: Script.resolvePath("../assets/tools/common/finish-label.svg"), scale: 0.0318 }, command: { @@ -883,7 +886,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "stretchInfoIcon", type: "image", properties: { - url: "../assets/tools/common/info-icon.svg", + url: Script.resolvePath("../assets/tools/common/info-icon.svg"), dimensions: { x: 0.0321, y: 0.0320 }, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, @@ -897,7 +900,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "stretchInfo", type: "image", properties: { - url: "../assets/tools/stretch/info-text.svg", + url: Script.resolvePath("../assets/tools/stretch/info-text.svg"), scale: 0.1340, localPosition: { // Vertically center on info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, @@ -914,7 +917,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/common/actions-label.svg", + url: Script.resolvePath("../assets/tools/common/actions-label.svg"), scale: 0.0276, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, @@ -945,7 +948,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/common/finish-label.svg", + url: Script.resolvePath("../assets/tools/common/finish-label.svg"), scale: 0.0318 }, command: { @@ -959,7 +962,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/common/actions-label.svg", + url: Script.resolvePath("../assets/tools/common/actions-label.svg"), scale: 0.0276, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, @@ -998,7 +1001,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { enabledColor: UIT.colors.greenHighlight, highlightColor: UIT.colors.greenShadow, label: { - url: "../assets/tools/group/group-label.svg", + url: Script.resolvePath("../assets/tools/group/group-label.svg"), scale: 0.0351, color: UIT.colors.baseGray }, @@ -1022,7 +1025,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { enabledColor: UIT.colors.redHighlight, highlightColor: UIT.colors.redAccent, label: { - url: "../assets/tools/group/ungroup-label.svg", + url: Script.resolvePath("../assets/tools/group/ungroup-label.svg"), scale: 0.0496, color: UIT.colors.baseGray }, @@ -1054,7 +1057,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/group/selection-box-label.svg", + url: Script.resolvePath("../assets/tools/group/selection-box-label.svg"), scale: 0.0161 }, command: { @@ -1076,7 +1079,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { enabledColor: UIT.colors.greenHighlight, highlightColor: UIT.colors.greenShadow, label: { - url: "../assets/tools/group/clear-label.svg", + url: Script.resolvePath("../assets/tools/group/clear-label.svg"), scale: 0.0314, color: UIT.colors.baseGray }, @@ -1093,7 +1096,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/physics/properties-label.svg", + url: Script.resolvePath("../assets/tools/physics/properties-label.svg"), scale: 0.0376, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0376 / 2, @@ -1131,7 +1134,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: 0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/gravity-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/gravity-label.svg"), scale: 0.0240, color: UIT.colors.white }, @@ -1141,16 +1144,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, color: UIT.colors.white // SVG has gray color. }, onSublabel: { - url: "../assets/tools/physics/buttons/on-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), scale: 0.0081 }, offSublabel: { - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104 }, setting: { @@ -1179,7 +1182,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: 0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/collisions-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/collisions-label.svg"), scale: 0.0338, color: UIT.colors.white }, @@ -1189,16 +1192,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, color: UIT.colors.white // SVG has gray color. }, onSublabel: { - url: "../assets/tools/physics/buttons/on-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), scale: 0.0081 }, offSublabel: { - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104 }, setting: { @@ -1227,7 +1230,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: 0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/grabbable-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/grabbable-label.svg"), scale: 0.0334, color: UIT.colors.white }, @@ -1237,16 +1240,16 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.0034, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, color: UIT.colors.white // SVG has gray color. }, onSublabel: { - url: "../assets/tools/physics/buttons/on-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), scale: 0.0081 }, offSublabel: { - url: "../assets/tools/physics/buttons/off-label.svg", + url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104 }, setting: { @@ -1264,7 +1267,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/physics/presets-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets-label.svg"), scale: 0.0270, localPosition: { x: UIT.dimensions.panel.x / 2 - UIT.dimensions.rightMargin - 0.1416 + 0.0270 / 2, @@ -1289,7 +1292,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetDefault", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/default-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/default-label.svg"), scale: 0.0436, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0436 / 2, @@ -1306,7 +1309,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetLead", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/lead-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/lead-label.svg"), scale: 0.0243, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0243 / 2, @@ -1320,7 +1323,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetWood", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/wood-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/wood-label.svg"), scale: 0.0316, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0316 / 2, @@ -1334,7 +1337,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetIce", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/ice-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/ice-label.svg"), scale: 0.0144, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0144 / 2, @@ -1348,7 +1351,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetRubber", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/rubber-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/rubber-label.svg"), scale: 0.0400, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0400 / 2, @@ -1362,7 +1365,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetCotton", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/cotton-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/cotton-label.svg"), scale: 0.0393, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0393 / 2, @@ -1376,7 +1379,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetTumbleweed", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/tumbleweed-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/tumbleweed-label.svg"), scale: 0.0687, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0687 / 2, @@ -1390,7 +1393,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetZeroG", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/zero-g-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/zero-g-label.svg"), scale: 0.0375, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0375 / 2, @@ -1404,7 +1407,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "presetBalloon", type: "picklistItem", label: { - url: "../assets/tools/physics/presets/balloon-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/balloon-label.svg"), scale: 0.0459, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0459 / 2, @@ -1426,7 +1429,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/physics/presets/default-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/default-label.svg"), scale: 0.0436, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0436 / 2, @@ -1435,7 +1438,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, sublabel: { - url: "../assets/tools/common/down-arrow.svg", + url: Script.resolvePath("../assets/tools/common/down-arrow.svg"), scale: 0.0080, localPosition: { x: 0.1416 / 2 - 0.0108 - 0.0080 / 2, @@ -1445,7 +1448,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white // SVG is colored baseGrayHighlight }, customLabel: { - url: "../assets/tools/physics/presets/custom-label.svg", + url: Script.resolvePath("../assets/tools/physics/presets/custom-label.svg"), scale: 0.0522, localPosition: { x: -0.1416 / 2 + 0.017 + 0.0522 / 2, @@ -1490,7 +1493,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.04375, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/sliders/gravity-label.svg", + url: Script.resolvePath("../assets/tools/physics/sliders/gravity-label.svg"), scale: 0.0240 }, setting: { @@ -1519,7 +1522,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.04375, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/sliders/bounce-label.svg", + url: Script.resolvePath("../assets/tools/physics/sliders/bounce-label.svg"), scale: 0.0233 }, setting: { @@ -1548,7 +1551,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.04375, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/sliders/friction-label.svg", + url: Script.resolvePath("../assets/tools/physics/sliders/friction-label.svg"), scale: 0.0258 }, setting: { @@ -1577,7 +1580,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -0.04375, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - url: "../assets/tools/physics/sliders/density-label.svg", + url: Script.resolvePath("../assets/tools/physics/sliders/density-label.svg"), scale: 0.0241 }, setting: { @@ -1596,7 +1599,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { type: "image", properties: { color: UIT.colors.white, - url: "../assets/tools/common/actions-label.svg", + url: Script.resolvePath("../assets/tools/common/actions-label.svg"), scale: 0.0276, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0276 / 2, @@ -1627,7 +1630,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, label: { - url: "../assets/tools/common/finish-label.svg", + url: Script.resolvePath("../assets/tools/common/finish-label.svg"), scale: 0.0318 }, command: { @@ -1649,7 +1652,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteInfoIcon", type: "image", properties: { - url: "../assets/tools/common/info-icon.svg", + url: Script.resolvePath("../assets/tools/common/info-icon.svg"), dimensions: { x: 0.0321, y: 0.0320 }, localPosition: { x: -UIT.dimensions.panel.x / 2 + UIT.dimensions.leftMargin + 0.0321 / 2, @@ -1663,7 +1666,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { id: "deleteInfo", type: "image", properties: { - url: "../assets/tools/delete/info-text.svg", + url: Script.resolvePath("../assets/tools/delete/info-text.svg"), scale: 0.1416, localPosition: { // Vertically off-center w.r.t. info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, @@ -1692,25 +1695,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/color-icon.svg", + url: Script.resolvePath("../assets/tools/color-icon.svg"), dimensions: { x: 0.0165, y: 0.0187 } }, headerOffset: { x: -0.00825, y: 0.0020, z: 0 } }, label: { properties: { - url: "../assets/tools/color-label.svg", + url: Script.resolvePath("../assets/tools/color-label.svg"), scale: 0.0241 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/color-tool-heading.svg", + url: Script.resolvePath("../assets/tools/color-tool-heading.svg"), scale: 0.0631 }, toolOptions: "colorOptions", @@ -1731,25 +1734,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/stretch-icon.svg", + url: Script.resolvePath("../assets/tools/stretch-icon.svg"), dimensions: { x: 0.0167, y: 0.0167 } }, headerOffset: { x: -0.00835, y: 0, z: 0 } }, label: { properties: { - url: "../assets/tools/stretch-label.svg", + url: Script.resolvePath("../assets/tools/stretch-label.svg"), scale: 0.0311 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/stretch-tool-heading.svg", + url: Script.resolvePath("../assets/tools/stretch-tool-heading.svg"), scale: 0.0737 }, toolOptions: "scaleOptions", @@ -1769,25 +1772,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/clone-icon.svg", + url: Script.resolvePath("../assets/tools/clone-icon.svg"), dimensions: { x: 0.0154, y: 0.0155 } }, headerOffset: { x: -0.0077, y: 0, z: 0 } }, label: { properties: { - url: "../assets/tools/clone-label.svg", + url: Script.resolvePath("../assets/tools/clone-label.svg"), scale: 0.0231 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/clone-tool-heading.svg", + url: Script.resolvePath("../assets/tools/clone-tool-heading.svg"), scale: 0.0621 }, toolOptions: "cloneOptions", @@ -1807,25 +1810,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/group-icon.svg", + url: Script.resolvePath("../assets/tools/group-icon.svg"), dimensions: { x: 0.0161, y: 0.0114 } }, headerOffset: { x: -0.00805, y: 0, z: 0 } }, label: { properties: { - url: "../assets/tools/group-label.svg", + url: Script.resolvePath("../assets/tools/group-label.svg"), scale: 0.0250 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/group-tool-heading.svg", + url: Script.resolvePath("../assets/tools/group-tool-heading.svg"), scale: 0.0647 }, toolOptions: "groupOptions", @@ -1845,25 +1848,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/physics-icon.svg", + url: Script.resolvePath("../assets/tools/physics-icon.svg"), dimensions: { x: 0.0180, y: 0.0198 } }, headerOffset: { x: -0.009, y: 0, z: 0 } }, label: { properties: { - url: "../assets/tools/physics-label.svg", + url: Script.resolvePath("../assets/tools/physics-label.svg"), scale: 0.0297 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/physics-tool-heading.svg", + url: Script.resolvePath("../assets/tools/physics-tool-heading.svg"), scale: 0.0712 }, toolOptions: "physicsOptions", @@ -1883,25 +1886,25 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/delete-icon.svg", + url: Script.resolvePath("../assets/tools/delete-icon.svg"), dimensions: { x: 0.0161, y: 0.0161 } }, headerOffset: { x: -0.00805, y: 0, z: 0 } }, label: { properties: { - url: "../assets/tools/delete-label.svg", + url: Script.resolvePath("../assets/tools/delete-label.svg"), scale: 0.0254 } }, sublabel: { properties: { - url: "../assets/tools/tool-label.svg", + url: Script.resolvePath("../assets/tools/tool-label.svg"), scale: 0.0152 } }, title: { - url: "../assets/tools/delete-tool-heading.svg", + url: Script.resolvePath("../assets/tools/delete-tool-heading.svg"), scale: 0.0653 }, toolOptions: "deleteOptions", @@ -1941,13 +1944,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/undo-icon.svg", + url: Script.resolvePath("../assets/tools/undo-icon.svg"), dimensions: { x: 0.0180, y: 0.0186 } } }, label: { properties: { - url: "../assets/tools/undo-label.svg", + url: Script.resolvePath("../assets/tools/undo-label.svg"), scale: 0.0205 } }, @@ -1967,13 +1970,13 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, icon: { properties: { - url: "../assets/tools/redo-icon.svg", + url: Script.resolvePath("../assets/tools/redo-icon.svg"), dimensions: { x: 0.0180, y: 0.0186 } } }, label: { properties: { - url: "../assets/tools/redo-label.svg", + url: Script.resolvePath("../assets/tools/redo-label.svg"), scale: 0.0192 } }, @@ -2086,7 +2089,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Update header. Overlays.editOverlay(menuHeaderBackOverlay, { visible: false }); Overlays.editOverlay(menuHeaderTitleOverlay, { - url: Script.resolvePath(MENU_HEADER_TITLE_PROPERTIES.url), + url: MENU_HEADER_TITLE_PROPERTIES.url, scale: MENU_HEADER_TITLE_PROPERTIES.scale }); Overlays.editOverlay(menuHeaderIconOverlay, { visible: false }); @@ -2111,7 +2114,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].properties); properties = Object.merge(properties, UI_ELEMENTS.menuButton.icon.properties); properties = Object.merge(properties, MENU_ITEMS[i].icon.properties); - properties.url = Script.resolvePath(properties.url); properties.visible = isVisible; properties.parentID = buttonID; overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].overlay, properties); @@ -2121,7 +2123,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].properties); properties = Object.merge(properties, UI_ELEMENTS.menuButton.label.properties); properties = Object.merge(properties, MENU_ITEMS[i].label.properties); - properties.url = Script.resolvePath(properties.url); properties.visible = isVisible; properties.parentID = itemID; overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].overlay, properties); @@ -2132,7 +2133,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].properties); properties = Object.merge(properties, UI_ELEMENTS.menuButton.sublabel.properties); properties = Object.merge(properties, MENU_ITEMS[i].sublabel.properties); - properties.url = Script.resolvePath(properties.url); properties.visible = isVisible; properties.parentID = itemID; overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.sublabel.type].overlay, properties); @@ -2176,7 +2176,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { closeMenu(); // Update header. - optionsHeadingURL = Script.resolvePath(menuItem.title.url); + optionsHeadingURL = menuItem.title.url; optionsHeadingScale = menuItem.title.scale; Overlays.editOverlay(menuHeaderBackOverlay, { visible: isVisible }); Overlays.editOverlay(menuHeaderTitleOverlay, { @@ -2184,7 +2184,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { scale: optionsHeadingScale }); Overlays.editOverlay(menuHeaderIconOverlay, { - url: Script.resolvePath(menuItem.icon.properties.url), + url: menuItem.icon.properties.url, dimensions: menuItem.icon.properties.dimensions, localPosition: Vec3.sum(MENU_HEADER_ICON_OFFSET, menuItem.icon.headerOffset), visible: isVisible @@ -2199,9 +2199,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.merge(properties, optionsItems[i].properties); } properties.parentID = parentID; - if (properties.url) { - properties.url = Script.resolvePath(properties.url); - } sublabelModifier = null; if (optionsItems[i].setting) { optionsSettings[optionsItems[i].id] = { key: optionsItems[i].setting.key }; @@ -2244,7 +2241,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties = Object.merge(childProperties, UI_ELEMENTS[optionsItems[i].type].label); childProperties = Object.merge(childProperties, optionsItems[i].label); - childProperties.url = Script.resolvePath(childProperties.url); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; id = Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); optionsOverlaysLabels[i] = id; @@ -2256,7 +2252,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (sublabelModifier) { childProperties = Object.merge(childProperties, sublabelModifier); } - childProperties.url = Script.resolvePath(childProperties.url); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; id = Overlays.addOverlay(UI_ELEMENTS.image.overlay, childProperties); optionsOverlaysSublabels[i] = id; @@ -2290,7 +2285,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Zero indicator. childProperties = Object.clone(UI_ELEMENTS.barSlider.zeroIndicator.properties); - childProperties.url = Script.resolvePath(childProperties.url); childProperties.dimensions = { x: properties.dimensions.x, y: UI_ELEMENTS.barSlider.zeroIndicator.properties.dimensions.y @@ -2311,7 +2305,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Primary image. if (optionsItems[i].imageURL) { childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageURL); + childProperties.url = optionsItems[i].imageURL; childProperties.dimensions = { x: properties.dimensions.x, y: properties.dimensions.y }; imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. if (optionsItems[i].useBaseColor) { @@ -2328,7 +2322,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Overlay image. if (optionsItems[i].imageOverlayURL) { childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); + childProperties.url = optionsItems[i].imageOverlayURL; childProperties.dimensions = { x: properties.dimensions.x, y: properties.dimensions.y }; childProperties.emissive = false; imageOffset += IMAGE_OFFSET; @@ -2362,7 +2356,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Primary image. if (optionsItems[i].imageURL) { childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageURL); + childProperties.url = optionsItems[i].imageURL; childProperties.scale = properties.dimensions.x; imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; @@ -2375,7 +2369,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Overlay image. if (optionsItems[i].imageOverlayURL) { childProperties = Object.clone(UI_ELEMENTS.image.properties); - childProperties.url = Script.resolvePath(optionsItems[i].imageOverlayURL); + childProperties.url = optionsItems[i].imageOverlayURL; childProperties.scale = properties.dimensions.x; imageOffset += IMAGE_OFFSET; childProperties.emissive = false; @@ -2489,9 +2483,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.merge(properties, FOOTER_ITEMS[i].properties); properties.visible = isVisible; properties.parentID = menuPanelOverlay; - if (properties.url) { - properties.url = Script.resolvePath(properties.url); - } itemID = Overlays.addOverlay(UI_ELEMENTS[FOOTER_ITEMS[i].type].overlay, properties); footerOverlays[i] = itemID; footerEnabled[i] = true; @@ -2507,7 +2498,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].properties); properties = Object.merge(properties, UI_ELEMENTS.menuButton.icon.properties); properties = Object.merge(properties, FOOTER_ITEMS[i].icon.properties); - properties.url = Script.resolvePath(properties.url); properties.visible = isVisible; properties.parentID = buttonID; overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.icon.type].overlay, properties); @@ -2517,7 +2507,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties = Object.clone(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].properties); properties = Object.merge(properties, UI_ELEMENTS.menuButton.label.properties); properties = Object.merge(properties, FOOTER_ITEMS[i].label.properties); - properties.url = Script.resolvePath(properties.url); properties.visible = isVisible; properties.parentID = itemID; overlayID = Overlays.addOverlay(UI_ELEMENTS[UI_ELEMENTS.menuButton.label.type].overlay, properties); @@ -2537,7 +2526,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { optionsSettings.physicsPresets.value = "custom"; label = optionsItems[optionsOverlaysIDs.indexOf("physicsPresets")].customLabel; Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("physicsPresets")], { - url: Script.resolvePath(label.url), + url: label.url, scale: label.scale, localPosition: label.localPosition }); @@ -2799,7 +2788,6 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { : UI_ELEMENTS[optionsItems[index].type].offHoverColor }); properties = Object.clone(value ? optionsItems[index].onSublabel : optionsItems[index].offSublabel); - properties.url = Script.resolvePath(properties.url); Overlays.editOverlay(optionsOverlaysSublabels[index], properties); uiCommandCallback(command, value); break; @@ -2820,7 +2808,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { : UI_ELEMENTS.picklist.properties.color }); Overlays.editOverlay(optionsOverlaysSublabels[index], { - url: Script.resolvePath("../assets/tools/common/down-arrow.svg") + url: PICKLIST_DOWN_ARROW }); // Hide options. @@ -2849,7 +2837,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localPosition: Vec3.sum(optionsItems[index].properties.localPosition, PICKLIST_HOVER_DELTA) }); Overlays.editOverlay(optionsOverlaysSublabels[index], { - url: Script.resolvePath("../assets/tools/common/up-arrow.svg") + url: PICKLIST_UP_ARROW }); // Show options. @@ -2875,7 +2863,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Update picklist label. label = optionsItems[optionsOverlaysIDs.indexOf(parameter)].label; Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("physicsPresets")], { - url: Script.resolvePath(label.url), + url: label.url, scale: label.scale, localPosition: label.localPosition }); @@ -3062,7 +3050,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white }); Overlays.editOverlay(menuHeaderTitleOverlay, { - url: Script.resolvePath(MENU_HEADER_TITLE_BACK_URL), + url: MENU_HEADER_TITLE_BACK_URL, scale: MENU_HEADER_TITLE_BACK_SCALE }); Overlays.editOverlay(menuHeaderIconOverlay, { @@ -3592,15 +3580,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Heading content. properties = Object.clone(MENU_HEADER_BACK_PROPERTIES); properties.parentID = menuHeaderHeadingOverlay; - properties.url = Script.resolvePath(properties.url); menuHeaderBackOverlay = Overlays.addOverlay("image3d", properties); properties = Object.clone(MENU_HEADER_TITLE_PROPERTIES); properties.parentID = menuHeaderHeadingOverlay; - properties.url = Script.resolvePath(properties.url); menuHeaderTitleOverlay = Overlays.addOverlay("image3d", properties); properties = Object.clone(MENU_HEADER_ICON_PROPERTIES); properties.parentID = menuHeaderHeadingOverlay; - properties.url = Script.resolvePath(properties.url); menuHeaderIconOverlay = Overlays.addOverlay("image3d", properties); // Panel background. From ad838c6b3c38b71a30bcdbc663539b911bccf6fc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 21:53:26 +1300 Subject: [PATCH 364/504] Adjust position of wrist icon --- scripts/vr-edit/modules/toolIcon.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/vr-edit/modules/toolIcon.js index cac31ed83f..a71dfdc156 100644 --- a/scripts/vr-edit/modules/toolIcon.js +++ b/scripts/vr-edit/modules/toolIcon.js @@ -19,8 +19,8 @@ ToolIcon = function (side) { MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. - MODEL_POSITION_LEFT_HAND = { x: -0.03, y: 0.035, z: 0 }, // x raises in thumb direction; y moves in fingers direction. - MODEL_POSITION_RIGHT_HAND = { x: 0.03, y: 0.035, z: 0 }, // "" + MODEL_POSITION_LEFT_HAND = { x: -0.025, y: 0.03, z: 0 }, // x raises in thumb direction; y moves in fingers direction. + MODEL_POSITION_RIGHT_HAND = { x: 0.025, y: 0.03, z: 0 }, // "" MODEL_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 0, z: 100 }), MODEL_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -100 }), From 20b1ab00876d5d82459c63bf54be303b352cb998 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 Sep 2017 22:06:28 +1300 Subject: [PATCH 365/504] Tidying --- scripts/vr-edit/modules/feedback.js | 8 ++++---- scripts/vr-edit/modules/hand.js | 6 +++--- scripts/vr-edit/utilities/utilities.js | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/vr-edit/modules/feedback.js index f1456df5e6..bf9b00ebc1 100644 --- a/scripts/vr-edit/modules/feedback.js +++ b/scripts/vr-edit/modules/feedback.js @@ -23,8 +23,8 @@ Feedback = (function () { CREATE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/create.wav")), EQUIP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/equip.wav")), ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")), - UNDO_SOUND = DROP_SOUND, // TODO - REDO_SOUND = DROP_SOUND, // TODO + UNDO_SOUND = DROP_SOUND, + REDO_SOUND = DROP_SOUND, FEEDBACK_PARAMETERS = { DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, @@ -37,8 +37,8 @@ Feedback = (function () { EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, - UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, // TODO - REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, // TODO + UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, + REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, diff --git a/scripts/vr-edit/modules/hand.js b/scripts/vr-edit/modules/hand.js index 174068a3d1..bd899c4724 100644 --- a/scripts/vr-edit/modules/hand.js +++ b/scripts/vr-edit/modules/hand.js @@ -27,11 +27,11 @@ Hand = function (side) { isTriggerPressed, isTriggerClicked, - TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js. - TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js. + TRIGGER_ON_VALUE = 0.15, // Per controllerDispatcherUtils.js. + TRIGGER_OFF_VALUE = 0.1, // Per controllerDispatcherUtils.js. TRIGGER_CLICKED_VALUE = 1.0, - NEAR_GRAB_RADIUS = 0.05, // Different from handControllerGrab.js's value of 0.1. + NEAR_GRAB_RADIUS = 0.05, // Different from controllerDispatcherUtils.js. NEAR_HOVER_RADIUS = 0.025, LEFT_HAND = 0, diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/vr-edit/utilities/utilities.js index 07ee894731..198e45c256 100644 --- a/scripts/vr-edit/utilities/utilities.js +++ b/scripts/vr-edit/utilities/utilities.js @@ -115,7 +115,8 @@ if (typeof Object.merge !== "function") { b = JSON.stringify(objectB); if (a === "{}") { return JSON.parse(b); // Always return a new object. - } else if (b === "{}") { + } + if (b === "{}") { return JSON.parse(a); // "" } return JSON.parse(a.slice(0, -1) + "," + b.slice(1)); From 014a7bc9b0a234938eac5d4058235da0565ff814 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 25 Sep 2017 08:36:38 -0700 Subject: [PATCH 366/504] Fix bytes downloaded stat tracking --- interface/src/Application.cpp | 14 ++++++++------ .../scripting/AssetMappingsScriptingInterface.cpp | 1 + libraries/networking/src/AssetResourceRequest.cpp | 6 +++--- libraries/networking/src/AssetResourceRequest.h | 2 ++ libraries/networking/src/FileResourceRequest.cpp | 3 +-- libraries/networking/src/HTTPResourceRequest.cpp | 9 ++++----- libraries/networking/src/ResourceRequest.cpp | 11 +++++++++++ libraries/networking/src/ResourceRequest.h | 2 ++ libraries/shared/src/StatTracker.cpp | 4 ++-- libraries/shared/src/StatTracker.h | 6 +++--- 10 files changed, 37 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 480928ccd2..43541c3ac2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1628,12 +1628,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; QJsonObject bytesDownloaded; - bytesDownloaded["atp"] = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["http"] = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["file"] = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["total"] = bytesDownloaded["atp"].toInt() + bytesDownloaded["http"].toInt() - + bytesDownloaded["file"].toInt(); - properties["bytesDownloaded"] = bytesDownloaded; + auto atpBytes = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto httpBytes = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto fileBytes = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toLongLong(); + bytesDownloaded["atp"] = atpBytes; + bytesDownloaded["http"] = httpBytes; + bytesDownloaded["file"] = fileBytes; + bytesDownloaded["total"] = atpBytes + httpBytes + fileBytes; + properties["bytes_downloaded"] = bytesDownloaded; auto myAvatar = getMyAvatar(); glm::vec3 avatarPosition = myAvatar->getPosition(); diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 5031016c3f..0912c869d3 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -21,6 +21,7 @@ #include #include #include +#include static const int AUTO_REFRESH_INTERVAL = 1000; diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 2dc22a9337..55d0ad4f75 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -155,6 +155,7 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { case AssetRequest::Error::NoError: _data = req->getData(); _result = Success; + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, _data.size()); break; case AssetRequest::InvalidHash: _result = InvalidURL; @@ -202,9 +203,8 @@ void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytes emit progress(bytesReceived, bytesTotal); auto now = p_high_resolution_clock::now(); - - // Recording ATP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); + + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); // if we haven't received the full asset check if it is time to output progress to log // we do so every X seconds to assist with ATP download tracking diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 18b82f2573..ac36c83985 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -41,6 +41,8 @@ private: AssetRequest* _assetRequest { nullptr }; p_high_resolution_clock::time_point _lastProgressDebug; + + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp index 26857716e1..dfff21dae6 100644 --- a/libraries/networking/src/FileResourceRequest.cpp +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -69,8 +69,7 @@ void FileResourceRequest::doSend() { if (_result == ResourceRequest::Success) { statTracker->incrementStat(STAT_FILE_REQUEST_SUCCESS); - // Recording FILE bytes downloaded in stats - statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES,fileSize); + statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES, fileSize); } else { statTracker->incrementStat(STAT_FILE_REQUEST_FAILED); } diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index edba520bd5..a34f92aaa6 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -141,6 +141,8 @@ void HTTPResourceRequest::onRequestFinished() { } } + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size()); + break; case QNetworkReply::TimeoutError: @@ -201,11 +203,8 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT _sendTimer->start(); emit progress(bytesReceived, bytesTotal); - - // Recording HTTP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); - - + + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); } void HTTPResourceRequest::onTimeout() { diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index f028535e2f..5d39583c9e 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -11,6 +11,9 @@ #include "ResourceRequest.h" +#include +#include + #include ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } @@ -40,3 +43,11 @@ QString ResourceRequest::getResultString() const { default: return "Unspecified Error"; } } + +void ResourceRequest::recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived) { + auto dBytes = bytesReceived - _lastRecordedBytesDownloaded; + if (dBytes > 0) { + _lastRecordedBytesDownloaded = bytesReceived; + DependencyManager::get()->updateStat(statName, dBytes); + } +} diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index cf852c1e1b..8dd09ccc49 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -85,6 +85,7 @@ signals: protected: virtual void doSend() = 0; + void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); QUrl _url; QUrl _relativePathURL; @@ -97,6 +98,7 @@ protected: ByteRange _byteRange; bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/shared/src/StatTracker.cpp b/libraries/shared/src/StatTracker.cpp index 0ac9ab0092..1884037ce8 100644 --- a/libraries/shared/src/StatTracker.cpp +++ b/libraries/shared/src/StatTracker.cpp @@ -17,12 +17,12 @@ QVariant StatTracker::getStat(const QString& name) { return _stats[name]; } -void StatTracker::setStat(const QString& name, int value) { +void StatTracker::setStat(const QString& name, int64_t value) { Lock lock(_statsLock); _stats[name] = value; } -void StatTracker::updateStat(const QString& name, int value) { +void StatTracker::updateStat(const QString& name, int64_t value) { Lock lock(_statsLock); auto itr = _stats.find(name); if (_stats.end() == itr) { diff --git a/libraries/shared/src/StatTracker.h b/libraries/shared/src/StatTracker.h index 38afc2c379..4b84bbcb32 100644 --- a/libraries/shared/src/StatTracker.h +++ b/libraries/shared/src/StatTracker.h @@ -24,15 +24,15 @@ class StatTracker : public Dependency { public: StatTracker(); QVariant getStat(const QString& name); - void setStat(const QString& name, int value); - void updateStat(const QString& name, int mod); + void setStat(const QString& name, int64_t value); + void updateStat(const QString& name, int64_t mod); void incrementStat(const QString& name); void decrementStat(const QString& name); private: using Mutex = std::mutex; using Lock = std::lock_guard; Mutex _statsLock; - QHash _stats; + QHash _stats; }; class CounterStat { From 88810d28c1f39c19ffc1d5f4a29aa0fdcf3d0afd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 25 Sep 2017 08:40:14 -0700 Subject: [PATCH 367/504] Add activity logging for uploading_asset --- .../AssetMappingsScriptingInterface.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 0912c869d3..b9ce273901 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -105,6 +105,25 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, startedCallback.call(); + QFile file { path }; + int64_t size { 0 }; + if (file.open(QIODevice::ReadOnly)) { + size = file.size(); + file.close(); + } + + QString extension = ""; + auto idx = path.lastIndexOf("."); + if (idx) { + extension = path.mid(idx + 1); + } + + UserActivityLogger::getInstance().logAction("uploading_asset", { + { "size", size }, + { "mapping", mapping }, + { "extension", extension} + }); + auto upload = DependencyManager::get()->createUpload(path); QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { if (upload->getError() != AssetUpload::NoError) { From 0e3c2f2e50ca12a569dd9c1ba619fda44fcb5fd8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 27 Sep 2017 15:22:23 -0700 Subject: [PATCH 368/504] Fix ambiguous conversion to QVariant in StatTracker --- libraries/shared/src/StatTracker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/StatTracker.cpp b/libraries/shared/src/StatTracker.cpp index 1884037ce8..adfafd0ea7 100644 --- a/libraries/shared/src/StatTracker.cpp +++ b/libraries/shared/src/StatTracker.cpp @@ -14,7 +14,7 @@ StatTracker::StatTracker() { QVariant StatTracker::getStat(const QString& name) { std::lock_guard lock(_statsLock); - return _stats[name]; + return QVariant::fromValue(_stats[name]); } void StatTracker::setStat(const QString& name, int64_t value) { From 57cec5058395501cd64725f65bd597ed08572797 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Sep 2017 12:18:09 +1300 Subject: [PATCH 369/504] Fix UI disappearing when inside complex model --- scripts/vr-edit/vr-edit.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 81db666727..63c16b1ed0 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -455,7 +455,7 @@ return rootEntityID; } - function isCameraOutsideEntity(entityID, entityPosition) { + function isCameraOutsideEntity(entityID, testPosition) { var cameraPosition, pickRay, PRECISION_PICKING = true, @@ -466,12 +466,11 @@ cameraPosition = Camera.position; pickRay = { origin: cameraPosition, - direction: Vec3.normalize(Vec3.subtract(entityPosition, cameraPosition)), - length: Vec3.distance(entityPosition, cameraPosition) + direction: Vec3.normalize(Vec3.subtract(testPosition, cameraPosition)), + length: Vec3.distance(testPosition, cameraPosition) }; intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, [entityID], NO_EXCLUDE_IDS, VISIBLE_ONLY); - - return intersection.distance < pickRay.length; + return !intersection.intersects || intersection.distance < pickRay.length; } @@ -927,10 +926,10 @@ isGripClicked = hand.gripClicked(); isTriggerPressed = hand.triggerPressed(); - // Hide UI if hand is intersecting entity and camera is outside entity, or it hand is intersecting stretch handle. - if (dominantHand !== side) { + // Hide UI if hand is intersecting entity and camera is outside entity, or if hand is intersecting stretch handle. + if (side !== dominantHand) { showUI = !intersection.handIntersected || (intersection.entityID !== null - && !isCameraOutsideEntity(intersection.entityID, intersection.intersection)); + && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition())); if (showUI !== isUIVisible) { isUIVisible = !isUIVisible; ui.setVisible(isUIVisible); @@ -952,7 +951,7 @@ && otherEditor.isHandle(intersection.overlayID)) && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed)) + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked)) { // No transition. @@ -969,7 +968,7 @@ setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed)) { + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); @@ -1020,7 +1019,7 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (isCameraOutsideEntity(intersection.entityID, intersection.intersection) || isTriggerPressed) + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition())) && !(!wasTriggerClicked && isTriggerClicked && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID @@ -1101,7 +1100,7 @@ setState(EDITOR_GRABBING); } } else if (!intersection.entityID || !intersection.editableEntity - || (!isCameraOutsideEntity(intersection.entityID, intersection.intersection) && !isTriggerPressed)) { + || (!isTriggerPressed && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { setState(EDITOR_SEARCHING); } else { log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); From 9a51ce4b29754cec5be2efb00f0d7c97fbb5f7d7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Sep 2017 12:18:36 +1300 Subject: [PATCH 370/504] Simplification --- scripts/vr-edit/vr-edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index 63c16b1ed0..d78b66f60f 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -492,7 +492,7 @@ function getScaleTargetPosition() { if (isScalingWithHand) { - return side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + return hand.palmPosition(); } return Vec3.sum(Vec3.sum(hand.position(), Vec3.multiplyQbyV(hand.orientation(), laserOffset)), Vec3.multiply(laser.length(), Quat.getUp(hand.orientation()))); From a74678a24d2086454f7c069684bac38d77947e5f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Sep 2017 12:57:30 +1300 Subject: [PATCH 371/504] Improve size of scale handles for distant entities --- scripts/vr-edit/modules/handles.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/vr-edit/modules/handles.js b/scripts/vr-edit/modules/handles.js index f7400c6561..0318b21efb 100644 --- a/scripts/vr-edit/modules/handles.js +++ b/scripts/vr-edit/modules/handles.js @@ -38,7 +38,7 @@ Handles = function (side) { FACE_HANDLE_OVERLAY_OFFSETS, FACE_HANDLE_OVERLAY_ROTATIONS, FACE_HANDLE_OVERLAY_SCALE_AXES, - DISTANCE_MULTIPLIER_MULTIPLIER = 0.5, + DISTANCE_MULTIPLIER_MULTIPLIER = 0.25, hoveredOverlayID = null, isVisible = false, @@ -179,9 +179,9 @@ Handles = function (side) { // display smaller in order to give comfortable depth cue. cameraPosition = Camera.position; boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); - distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER * Math.sqrt( - Math.max(Vec3.length(boundingBoxVector, Vec3.length(boundingBox.dimensions) / 2)) - ); + distanceMultiplier = Vec3.length(boundingBoxVector); + distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER + * (distanceMultiplier + (1 - Math.LOG10E * Math.log(distanceMultiplier + 1))); // Corner scale handles. // At right-most and opposite corners of bounding box. From 47ab9d322179b73f53e5cb5033b9b1f73d3df585 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Sep 2017 16:04:22 +1300 Subject: [PATCH 372/504] Fix flash of color from laser target when turn on or jump distance --- scripts/vr-edit/modules/laser.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/vr-edit/modules/laser.js b/scripts/vr-edit/modules/laser.js index afd67cf364..39ce0b713b 100644 --- a/scripts/vr-edit/modules/laser.js +++ b/scripts/vr-edit/modules/laser.js @@ -44,6 +44,7 @@ Laser = function (side) { laserLength, specifiedLaserLength = null, + laserSphereSize = 0, LEFT_HAND = 0, @@ -132,12 +133,16 @@ Laser = function (side) { } else { Overlays.editOverlay(laserLine, { visible: false }); } - updateSphere(searchTarget, sphereSize, color, brightColor); + // Avoid flash from large laser sphere when turn on or suddenly increase distance. Rendering seems to update overlay + // position one frame behind so use sphere size from preceding frame. + updateSphere(searchTarget, laserSphereSize, color, brightColor); + laserSphereSize = sphereSize; } function hide() { Overlays.editOverlay(laserLine, { visible: false }); Overlays.editOverlay(laserSphere, { visible: false }); + laserSphereSize = 0; } function setUIOverlays(overlayIDs) { From 97bf093cabda4dfcb98d2c1416b122533ef8898b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 28 Sep 2017 08:16:54 -0700 Subject: [PATCH 373/504] Fix ambiguous conversion in AssetMappingScriptingInterface --- interface/src/scripting/AssetMappingsScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index b9ce273901..ba533a5bd1 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -119,7 +119,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, } UserActivityLogger::getInstance().logAction("uploading_asset", { - { "size", size }, + { "size", (qint64)size }, { "mapping", mapping }, { "extension", extension} }); From 15ff4ebecb1dc15e3c1f9496fff7482b73ded0cb Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Wed, 2 Aug 2017 16:03:00 -0400 Subject: [PATCH 374/504] [Case 6491] Adds some debugging prints. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 79 ++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4df25c41b7..b367d56bbf 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1583,20 +1583,29 @@ SelectionDisplay = (function() { // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { + print("======> SetSpaceMode called. ========"); if (spaceMode != newSpaceMode) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); spaceMode = newSpaceMode; that.updateHandles(); + } else { + print(" Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); } + print("====== SetSpaceMode called. <========"); }; // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { + print("========> ToggleSpaceMode called. ========="); if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { print("Local space editing is not available with multiple selections"); return; } + print( "PreToggle: " + spaceMode); spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; + print( "PostToggle: " + spaceMode); that.updateHandles(); + print("======== ToggleSpaceMode called. <========="); }; // FUNCTION: UNSELECT ALL @@ -1605,6 +1614,9 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { + print( "======> Update Handles =======" ); + print( " Selections Count: " + SelectionManager.selections.length ); + print( " SpaceMode: " + spaceMode ); if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; @@ -2334,6 +2346,8 @@ SelectionDisplay = (function() { rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), }); + print( "====== Update Handles <=======" ); + }; // FUNCTION: SET OVERLAYS VISIBLE @@ -3551,15 +3565,22 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { + print( "---> updateRotationDegreesOverlay ---" ); + print(" AngleFromZero: " + angleFromZero ); + print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z ); + print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z ); var angle = angleFromZero * (Math.PI / 180); var position = { x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, z: 0, }; + print(" Angle: " + angle ); + print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); position = Vec3.multiplyQbyV(handleRotation, position); position = Vec3.sum(centerPosition, position); - Overlays.editOverlay(rotationDegreesDisplay, { + print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); + var overlayProps = { position: position, dimensions: { x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, @@ -3567,7 +3588,12 @@ SelectionDisplay = (function() { }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(angleFromZero) + "°", - }); + }; + print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z ); + print(" OverlayLineHeight: " + overlayProps.lineHeight ); + print(" OverlayText: " + overlayProps.text ); + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); + print( "<--- updateRotationDegreesOverlay ---" ); } // YAW GRABBER TOOL DEFINITION @@ -3575,6 +3601,7 @@ SelectionDisplay = (function() { addGrabberTool(yawHandle, { mode: "ROTATE_YAW", onBegin: function(event) { + print("================== HANDLE_ROLL(Beg) -> ======================="); SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; @@ -3616,8 +3643,10 @@ SelectionDisplay = (function() { }); updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); + print("================== HANDLE_YAW(Beg) <- ======================="); }, onEnd: function(event, reason) { + print("================== HANDLE_YAW(End) -> ======================="); Overlays.editOverlay(rotateOverlayInner, { visible: false }); @@ -3632,8 +3661,10 @@ SelectionDisplay = (function() { }); pushCommandForSelections(); + print("================== HANDLE_YAW(End) <- ======================="); }, onMove: function(event) { + print("================== HANDLE_YAW(Mve) -> ======================="); var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { visible: false @@ -3651,6 +3682,7 @@ SelectionDisplay = (function() { var centerToIntersect = Vec3.subtract(result.intersection, center); // Note: orientedAngle which wants normalized centerToZero and centerToIntersect // handles that internally, so it's to pass unnormalized vectors here. + print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = distanceFromCenter < innerRadius; @@ -3736,6 +3768,7 @@ SelectionDisplay = (function() { } } + print("================== HANDLE_YAW(Mve) <- ======================="); } }); @@ -3743,6 +3776,7 @@ SelectionDisplay = (function() { addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function(event) { + print("================== HANDLE_PITCH(Beg) -> ======================="); SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; @@ -3784,8 +3818,10 @@ SelectionDisplay = (function() { }); updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); + print("================== HANDLE_PITCH(Beg) <- ======================="); }, onEnd: function(event, reason) { + print("================== HANDLE_PITCH(End) -> ======================="); Overlays.editOverlay(rotateOverlayInner, { visible: false }); @@ -3800,8 +3836,10 @@ SelectionDisplay = (function() { }); pushCommandForSelections(); + print("================== HANDLE_PITCH(End) <- ======================="); }, onMove: function(event) { + print("================== HANDLE_PITCH(Mve) -> ======================="); var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { visible: false @@ -3818,6 +3856,7 @@ SelectionDisplay = (function() { var centerToIntersect = Vec3.subtract(result.intersection, center); // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles // this internally, so it's fine to pass non-normalized versions here. + print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -3894,6 +3933,7 @@ SelectionDisplay = (function() { }); } } + print("================== HANDLE_PITCH(Mve) <- ======================="); } }); @@ -3901,6 +3941,7 @@ SelectionDisplay = (function() { addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function(event) { + print("================== HANDLE_ROLL(Beg) -> ======================="); SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; @@ -3942,8 +3983,10 @@ SelectionDisplay = (function() { }); updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); + print("================== HANDLE_ROLL(Beg) <- ======================="); }, onEnd: function(event, reason) { + print("================== HANDLE_ROLL(End) -> ======================="); Overlays.editOverlay(rotateOverlayInner, { visible: false }); @@ -3958,8 +4001,10 @@ SelectionDisplay = (function() { }); pushCommandForSelections(); + print("================== HANDLE_ROLL(End) <- ======================="); }, onMove: function(event) { + print("================== HANDLE_ROLL(Mve) -> ======================="); var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { visible: false @@ -3976,6 +4021,7 @@ SelectionDisplay = (function() { var centerToIntersect = Vec3.subtract(result.intersection, center); // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles // this internally, so it's fine to pass non-normalized versions here. + print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -4051,6 +4097,8 @@ SelectionDisplay = (function() { }); } } + print("================== HANDLE_ROLL(Mve) <- ======================="); + } }); @@ -4086,7 +4134,10 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function(event) { - var wantDebug = false; + var wantDebug = true; + if ( wantDebug ) { + print( "=============== eST::MousePressEvent BEG ======================="); + } if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -4118,6 +4169,7 @@ SelectionDisplay = (function() { var tool = grabberTools[result.overlayID]; if (tool) { + print("Intersected with known table tool."); activeTool = tool; mode = tool.mode; somethingClicked = 'tool'; @@ -4125,8 +4177,10 @@ SelectionDisplay = (function() { activeTool.onBegin(event); } } else { + print("Intersected with unregistered tool..."); switch (result.overlayID) { case grabberMoveUp: + print("grabberMoveUp"); mode = "TRANSLATE_UP_DOWN"; somethingClicked = mode; @@ -4142,6 +4196,7 @@ SelectionDisplay = (function() { case grabberNEAR: case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? + print("grabberNear variant"); mode = "STRETCH_NEAR"; somethingClicked = mode; break; @@ -4149,31 +4204,37 @@ SelectionDisplay = (function() { case grabberFAR: case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? + print("grabberFar variant"); mode = "STRETCH_FAR"; somethingClicked = mode; break; case grabberTOP: + print("grabberTOP"); mode = "STRETCH_TOP"; somethingClicked = mode; break; case grabberBOTTOM: + print("grabberBOTTOM"); mode = "STRETCH_BOTTOM"; somethingClicked = mode; break; case grabberRIGHT: case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? + print("grabberRight variant"); mode = "STRETCH_RIGHT"; somethingClicked = mode; break; case grabberLEFT: case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? + print("grabberLeft variant"); mode = "STRETCH_LEFT"; somethingClicked = mode; break; default: + print("UNKNOWN( " + result.overlayID + " )"); mode = "UNKNOWN"; break; } @@ -4183,6 +4244,7 @@ SelectionDisplay = (function() { // if one of the items above was clicked, then we know we are in translate or stretch mode, and we // should hide our rotate handles... if (somethingClicked) { + print("Click is triggering hiding of handles, hopefully"); Overlays.editOverlay(yawHandle, { visible: false }); @@ -4225,6 +4287,7 @@ SelectionDisplay = (function() { originalRoll = roll; if (result.intersects) { + print("Intersection detected with handle..."); var resultTool = grabberTools[result.overlayID]; if (resultTool) { activeTool = resultTool; @@ -4236,15 +4299,18 @@ SelectionDisplay = (function() { } switch (result.overlayID) { case yawHandle: + print("Handle_YAW"); mode = "ROTATE_YAW"; somethingClicked = mode; overlayOrientation = yawHandleRotation; overlayCenter = yawCenter; yawZero = result.intersection; rotationNormal = yawNormal; + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); break; case pitchHandle: + print("Handle_PITCH"); mode = "ROTATE_PITCH"; initialPosition = SelectionManager.worldPosition; somethingClicked = mode; @@ -4252,15 +4318,18 @@ SelectionDisplay = (function() { overlayCenter = pitchCenter; pitchZero = result.intersection; rotationNormal = pitchNormal; + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); break; case rollHandle: + print("Handle_ROLL"); mode = "ROTATE_ROLL"; somethingClicked = mode; overlayOrientation = rollHandleRotation; overlayCenter = rollCenter; rollZero = result.intersection; rotationNormal = rollNormal; + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); break; default: @@ -4463,6 +4532,10 @@ SelectionDisplay = (function() { ignoreRayIntersection: false }); + if ( wantDebug ) { + print( "=============== eST::MousePressEvent END ======================="); + } + return somethingClicked; }; From ae8ae6f6cc3f105f9009314fae572874f028bc33 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Thu, 3 Aug 2017 17:02:09 -0400 Subject: [PATCH 375/504] [Case 6491] Grabbers stay invisible while rotating (details below). This fixes the issue where grabbers would re-appear when rotating the selected object rather than staying hidden. updateHandles will now take the mode into account when deciding if the non-light grabber handles should be visible. This can be subverted by the user selecting a light source as in line with the previous behavior. Adds some debug prints in event handlers guarded by local wantDebug checks. Has some minor cleanup changes: * Remove unused var within updateRotationHandles * Readability improvement for light check within updateHandles * Pulled up rotate mode check within updateHandles * Added grabberCloner visibility flag within updateHandles Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 566 +++++++++++------- 1 file changed, 335 insertions(+), 231 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b367d56bbf..934a9081a3 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1530,7 +1530,6 @@ SelectionDisplay = (function() { var rotateHandlesVisible = true; var rotationOverlaysVisible = false; var translateHandlesVisible = true; - var stretchHandlesVisible = true; var selectionBoxVisible = true; var isPointLight = false; @@ -1543,11 +1542,9 @@ SelectionDisplay = (function() { rotationOverlaysVisible = true; rotateHandlesVisible = false; translateHandlesVisible = false; - stretchHandlesVisible = false; selectionBoxVisible = false; } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) { rotateHandlesVisible = false; - stretchHandlesVisible = false; } else if (mode != "UNKNOWN") { // every other mode is a stretch mode... rotateHandlesVisible = false; @@ -1622,6 +1619,7 @@ SelectionDisplay = (function() { return; } + //print( " Triggering updateRotationHandles"); that.updateRotationHandles(); var rotation, dimensions, position, registrationPoint; @@ -1849,201 +1847,212 @@ SelectionDisplay = (function() { EdgeFR = Vec3.sum(position, EdgeFR); EdgeFL = Vec3.sum(position, EdgeFL); - var stretchHandlesVisible = spaceMode == SPACE_LOCAL; - var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles; + var inModeRotate = (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"); + var stretchHandlesVisible = !inModeRotate && (spaceMode == SPACE_LOCAL); + var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); + var cloneHandleVisible = !inModeRotate; + //print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); + var isSingleSelection = (selectionManager.selections.length == 1); - if (selectionManager.selections.length == 1) { + if (isSingleSelection) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); - if (properties.type == "Light" && properties.isSpotlight) { + var isLightSelection = (properties.type == "Light"); + if ( isLightSelection ) { + //print(" Light Selection revoking Non-Light Grabbers Visibility!"); stretchHandlesVisible = false; extendedStretchHandlesVisible = false; + cloneHandleVisible = false; + if(properties.isSpotlight) { + //print(" Trying to show all SpotLight related grabbers"); + Overlays.editOverlay(grabberSpotLightCenter, { + position: position, + visible: false, + }); + Overlays.editOverlay(grabberSpotLightRadius, { + position: NEAR, + rotation: rotation, + visible: true, + }); + var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); - Overlays.editOverlay(grabberSpotLightCenter, { - position: position, - visible: false, - }); - Overlays.editOverlay(grabberSpotLightRadius, { - position: NEAR, - rotation: rotation, - visible: true, - }); - var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); + Overlays.editOverlay(grabberSpotLightL, { + position: EdgeNL, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightR, { + position: EdgeNR, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightT, { + position: EdgeTN, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightB, { + position: EdgeBN, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightCircle, { + position: NEAR, + dimensions: { + x: distance, + y: distance, + z: 1 + }, + lineWidth: 1.5, + rotation: rotation, + visible: true, + }); - Overlays.editOverlay(grabberSpotLightL, { - position: EdgeNL, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightR, { - position: EdgeNR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightT, { - position: EdgeTN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightB, { - position: EdgeBN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightCircle, { - position: NEAR, - dimensions: { - x: distance, - y: distance, - z: 1 - }, - lineWidth: 1.5, - rotation: rotation, - visible: true, - }); + Overlays.editOverlay(grabberSpotLightLineT, { + start: position, + end: EdgeTN, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightLineB, { + start: position, + end: EdgeBN, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightLineR, { + start: position, + end: EdgeNR, + visible: true, + }); + Overlays.editOverlay(grabberSpotLightLineL, { + start: position, + end: EdgeNL, + visible: true, + }); - Overlays.editOverlay(grabberSpotLightLineT, { - start: position, - end: EdgeTN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineB, { - start: position, - end: EdgeBN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineR, { - start: position, - end: EdgeNR, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineL, { - start: position, - end: EdgeNL, - visible: true, - }); + //print(" Trying to hide all PointLight related grabbers"); + Overlays.editOverlay(grabberPointLightCircleX, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleY, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + visible: false + }); + Overlays.editOverlay(grabberPointLightT, { + visible: false + }); + Overlays.editOverlay(grabberPointLightB, { + visible: false + }); + Overlays.editOverlay(grabberPointLightL, { + visible: false + }); + Overlays.editOverlay(grabberPointLightR, { + visible: false + }); + Overlays.editOverlay(grabberPointLightF, { + visible: false + }); + Overlays.editOverlay(grabberPointLightN, { + visible: false + }); + } else { //..it's a PointLight + //print(" Trying to show all PointLight related grabbers"); + Overlays.editOverlay(grabberPointLightT, { + position: TOP, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightB, { + position: BOTTOM, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightL, { + position: LEFT, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightR, { + position: RIGHT, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightF, { + position: FAR, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightN, { + position: NEAR, + rotation: rotation, + visible: true, + }); + Overlays.editOverlay(grabberPointLightCircleX, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true, + }); + Overlays.editOverlay(grabberPointLightCircleY, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true, + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + position: position, + rotation: rotation, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true, + }); - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); - } else if (properties.type == "Light" && !properties.isSpotlight) { - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - Overlays.editOverlay(grabberPointLightT, { - position: TOP, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightB, { - position: BOTTOM, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightL, { - position: LEFT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightR, { - position: RIGHT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightF, { - position: FAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightN, { - position: NEAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleX, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleY, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - position: position, - rotation: rotation, - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - } else { + //print(" Trying to hide all SpotLight related grabbers"); + Overlays.editOverlay(grabberSpotLightRadius, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightB, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightCircle, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineB, { + visible: false + }); + } + } else { //..it's not a light at all + //print(" Trying to hide all Light related grabbers"); Overlays.editOverlay(grabberSpotLightCenter, { visible: false }); @@ -2106,7 +2115,7 @@ SelectionDisplay = (function() { visible: false }); } - } + }//--end of isSingleSelection @@ -2184,7 +2193,7 @@ SelectionDisplay = (function() { }); Overlays.editOverlay(grabberCloner, { - visible: true, + visible: cloneHandleVisible, rotation: rotation, position: EdgeTR }); @@ -2195,7 +2204,7 @@ SelectionDisplay = (function() { position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, - visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), + visible: !inModeRotate, }); // Create more selection box overlays if we don't have enough @@ -2332,7 +2341,7 @@ SelectionDisplay = (function() { }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", + visible: !inModeRotate, solid: true, position: { x: selectionManager.worldPosition.x, @@ -4134,7 +4143,7 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function(event) { - var wantDebug = true; + var wantDebug = false; if ( wantDebug ) { print( "=============== eST::MousePressEvent BEG ======================="); } @@ -4169,7 +4178,9 @@ SelectionDisplay = (function() { var tool = grabberTools[result.overlayID]; if (tool) { - print("Intersected with known table tool."); + if (wantDebug) { + print("Intersected with known table tool( mode: " + tool.mode + " )"); + } activeTool = tool; mode = tool.mode; somethingClicked = 'tool'; @@ -4177,10 +4188,14 @@ SelectionDisplay = (function() { activeTool.onBegin(event); } } else { - print("Intersected with unregistered tool..."); + if (wantDebug) { + print("Intersected with unregistered tool..."); + } switch (result.overlayID) { case grabberMoveUp: - print("grabberMoveUp"); + if (wantDebug){ + print("grabberMoveUp"); + } mode = "TRANSLATE_UP_DOWN"; somethingClicked = mode; @@ -4196,7 +4211,9 @@ SelectionDisplay = (function() { case grabberNEAR: case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? - print("grabberNear variant"); + if(wantDebug){ + print("grabberNear variant"); + } mode = "STRETCH_NEAR"; somethingClicked = mode; break; @@ -4204,47 +4221,64 @@ SelectionDisplay = (function() { case grabberFAR: case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? - print("grabberFar variant"); + if(wantDebug){ + print("grabberFar variant"); + } mode = "STRETCH_FAR"; somethingClicked = mode; break; case grabberTOP: - print("grabberTOP"); + if(wantDebug){ + print("grabberTOP"); + } mode = "STRETCH_TOP"; somethingClicked = mode; break; case grabberBOTTOM: - print("grabberBOTTOM"); + if(wantDebug){ + print("grabberBOTTOM"); + } mode = "STRETCH_BOTTOM"; somethingClicked = mode; break; case grabberRIGHT: case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? - print("grabberRight variant"); + if(wantDebug){ + print("grabberRight variant"); + } mode = "STRETCH_RIGHT"; somethingClicked = mode; break; case grabberLEFT: case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? - print("grabberLeft variant"); + if(wantDebug){ + print("grabberLeft variant"); + } mode = "STRETCH_LEFT"; somethingClicked = mode; break; default: - print("UNKNOWN( " + result.overlayID + " )"); + if(wantDebug){ + print("UNKNOWN( " + result.overlayID + " )"); + } mode = "UNKNOWN"; break; } + if(wantDebug){ + print(" Unregistered Tool Mode: " + mode ); + } } } // if one of the items above was clicked, then we know we are in translate or stretch mode, and we // should hide our rotate handles... if (somethingClicked) { - print("Click is triggering hiding of handles, hopefully"); + if(wantDebug){ + print(" Trying to hide PitchYawRoll Handles"); + } Overlays.editOverlay(yawHandle, { visible: false }); @@ -4256,6 +4290,9 @@ SelectionDisplay = (function() { }); if (mode != "TRANSLATE_UP_DOWN") { + if(wantDebug){ + print(" Trying to hide GrabberMoveUp"); + } Overlays.editOverlay(grabberMoveUp, { visible: false }); @@ -4287,30 +4324,46 @@ SelectionDisplay = (function() { originalRoll = roll; if (result.intersects) { - print("Intersection detected with handle..."); var resultTool = grabberTools[result.overlayID]; + if(wantDebug){ + print("Intersection detected with handle..."); + } if (resultTool) { + if(wantDebug){ + print(" " + resultTool.mode); + } activeTool = resultTool; mode = resultTool.mode; somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { + if(wantDebug){ + print(" Triggering Tool's onBegin"); + } activeTool.onBegin(event); + } else if(wantDebug) { + print(" Tool's missing onBegin"); } } switch (result.overlayID) { case yawHandle: - print("Handle_YAW"); + if(wantDebug){ + print("Handle_YAW"); + } mode = "ROTATE_YAW"; somethingClicked = mode; overlayOrientation = yawHandleRotation; overlayCenter = yawCenter; yawZero = result.intersection; rotationNormal = yawNormal; - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + if(wantDebug){ + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + } break; case pitchHandle: - print("Handle_PITCH"); + if(wantDebug){ + print("Handle_PITCH"); + } mode = "ROTATE_PITCH"; initialPosition = SelectionManager.worldPosition; somethingClicked = mode; @@ -4318,18 +4371,24 @@ SelectionDisplay = (function() { overlayCenter = pitchCenter; pitchZero = result.intersection; rotationNormal = pitchNormal; - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + if(wantDebug){ + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + } break; case rollHandle: - print("Handle_ROLL"); + if(wantDebug){ + print("Handle_ROLL"); + } mode = "ROTATE_ROLL"; somethingClicked = mode; overlayOrientation = rollHandleRotation; overlayCenter = rollCenter; rollZero = result.intersection; rotationNormal = rollNormal; - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + if(wantDebug){ + print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); + } break; default: @@ -4347,6 +4406,9 @@ SelectionDisplay = (function() { if (somethingClicked) { + if(wantDebug){ + print(" Trying to show rotateOverlay Handles"); + } Overlays.editOverlay(rotateOverlayTarget, { visible: true, rotation: overlayOrientation, @@ -4371,6 +4433,9 @@ SelectionDisplay = (function() { startAt: 0, endAt: 0 }); + if(wantDebug){ + print(" Trying to hide PitchYawRoll Handles"); + } Overlays.editOverlay(yawHandle, { visible: false }); @@ -4381,15 +4446,11 @@ SelectionDisplay = (function() { visible: false }); + if(wantDebug){ + print(" Trying to hide Non-Light GrabberHandles"); + } - // TODO: these three duplicate prior three, remove them. - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { + Overlays.editOverlay(grabberCloner, { visible: false }); Overlays.editOverlay(grabberMoveUp, { @@ -4485,6 +4546,9 @@ SelectionDisplay = (function() { switch (result.overlayID) { case selectionBox: activeTool = translateXZTool; + if(wantDebug){ + print("Clicked selectionBox, Activating Tool: " + activeTool.mode ); + } translateXZTool.pickPlanePosition = result.intersection; translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); @@ -4518,7 +4582,10 @@ SelectionDisplay = (function() { } // reset everything as intersectable... - // TODO: we could optimize this since some of these were already flipped back + // TODO: we could optimize this since some of these were already flipped back(i.e: just get rid of this) + if(wantDebug){ + print("Trying to set SelectionBox & PitchYawRoll Handles to NOT_IGNORE Rays"); + } Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); @@ -4541,9 +4608,25 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { + var wantDebug = false; + if(wantDebug){ + print( "=============== eST::MouseMoveEvent BEG ======================="); + } if (activeTool) { + if(wantDebug){ + print(" Trigger ActiveTool( " + activeTool.mode + " )'s onMove"); + } activeTool.onMove(event); + + if(wantDebug){ + print(" Trigger SelectionManager::update"); + } SelectionManager._update(); + + if(wantDebug){ + print( "=============== eST::MouseMoveEvent END ======================="); + } + //--EARLY EXIT--( Move handled via active tool) return true; } @@ -4666,6 +4749,9 @@ SelectionDisplay = (function() { } } + if(wantDebug){ + print("=============== eST::MouseMoveEvent END ======================="); + } return false; }; @@ -4710,13 +4796,27 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { - var showHandles = false; - if (activeTool && activeTool.onEnd) { - activeTool.onEnd(event); + var wantDebug = false; + if(wantDebug){ + print("=============== eST::MouseReleaseEvent BEG ======================="); } - activeTool = null; + var showHandles = false; + if (activeTool) { + if( activeTool.onEnd ) { + if(wantDebug){ + print(" Triggering ActiveTool( " + activeTool.mode + " )'s onEnd"); + } + activeTool.onEnd(event); + }else if(wantDebug){ + print(" ActiveTool( " + activeTool.mode + " )'s missing onEnd"); + } + } + // hide our rotation overlays..., and show our handles if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { + if(wantDebug){ + print(" Triggering hide of RotateOverlays"); + } Overlays.editOverlay(rotateOverlayTarget, { visible: false }); @@ -4729,22 +4829,26 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - showHandles = true; - } - - if (mode != "UNKNOWN") { - showHandles = true; + } + showHandles = (mode != "UNKNOWN");// Date: Mon, 7 Aug 2017 18:10:55 -0400 Subject: [PATCH 376/504] [Case 6491] Cleanup of mousePressEvent/tool(s) onBegin (details below). * Removes unregister tools switch which was never reached. * Rolls all code rotation handle related code within mousePressEvent to the respective rotation handler onBegin functions. * onBegin call site updated accordingly to provide intersection data that code depends upon. * Moves all translateXZTool code explicitly within mousePressEvent to the tool's onBegin function. * onBegin signature updated accordingly to provide intersect results that the tool relies upon. * Found and fixed a bug with translateXZTool where its startingElevation and startingDistance properties were _only_ set when local _debug_ var was set. * This appears to have been responsible for being able to move the object farther than was visible. Re-tested with fix and wasn't able to get that behavior any longer. * Wrap intersect tests within more reader friendly functions. NOTE(s): * Tested GrabberMoveUp and Rotation Handles. They work as they did previously as expected. * Tested selection behavior and it currently maintains as expected. * Tested translationXZTool and it maintains its prior behavior with the improvement noted above. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 515 +++++++++--------- 1 file changed, 244 insertions(+), 271 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 934a9081a3..ce93a6f366 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2392,13 +2392,30 @@ SelectionDisplay = (function() { greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event,isAltFromGrab) { + onBegin: function(event,isAltFromGrab, intersectInfo) { + var wantDebug = true; + if(wantDebug){ + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" intersectInfo.queryRay", intersectInfo.queryRay); + Vec3.print(" intersectInfo.queryRay.origin", intersectInfo.queryRay.origin); + Vec3.print(" intersectInfo.results.intersection", intersectInfo.results.intersection); + } + SelectionManager.saveProperties(); startPosition = SelectionManager.worldPosition; - var dimensions = SelectionManager.worldDimensions; + mode = translateXZTool.mode; - var pickRay = generalComputePickRay(event.x, event.y); - initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { + translateXZTool.pickPlanePosition = intersectInfo.results.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.startingDistance = Vec3.distance(intersectInfo.queryRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(intersectInfo.queryRay.origin, translateXZTool.pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + translateXZTool.greatestDimension); + print(" starting distance: " + translateXZTool.startingDistance); + print(" starting elevation: " + translateXZTool.startingElevation); + } + + initialXZPick = rayPlaneIntersection(intersectInfo.queryRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, z: 0 @@ -2424,6 +2441,9 @@ SelectionDisplay = (function() { } isConstrained = false; + if(wantDebug){ + print("================== TRANSLATE_XZ(End) <- ======================="); + } }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); @@ -2452,8 +2472,10 @@ SelectionDisplay = (function() { // this will happen when someone drags across the horizon from the side they started on. if (!pick) { if (wantDebug) { - print("Pick ray does not intersect XZ plane."); + print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); } + + //--EARLY EXIT--( Invalid ray detected. ) return; } @@ -2468,8 +2490,10 @@ SelectionDisplay = (function() { if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { - print("too close to horizon!"); + print(" "+ translateXZTool.mode + " - too close to horizon!"); } + + //--EARLY EXIT--( Don't proceed past the reached limit. ) return; } @@ -3610,9 +3634,24 @@ SelectionDisplay = (function() { addGrabberTool(yawHandle, { mode: "ROTATE_YAW", onBegin: function(event) { - print("================== HANDLE_ROLL(Beg) -> ======================="); + var wantDebug = true; + if (wantDebug) { + print("================== HANDLE_YAW(Beg) -> ======================="); + } SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + mode = "ROTATE_YAW"; + rotationNormal = yawNormal; + //note: It's expected that the intersection is passed when this is called. + if (arguments.length >= 2 ) { + yawZero = arguments[ 1 ]; + } else { + print("ERROR( yawHandle.onBegin ) - Intersection wasn't passed!"); + } + + if (wantDebug) { + Vec3.print(" yawZero: ", yawZero); + } // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -3623,15 +3662,19 @@ SelectionDisplay = (function() { var outerAlpha = 0.2; Overlays.editOverlay(rotateOverlayInner, { visible: true, + rotation: yawHandleRotation, + position: yawCenter, size: innerRadius, innerRadius: 0.9, startAt: 0, endAt: 360, - alpha: innerAlpha + alpha: innerAlpha, }); Overlays.editOverlay(rotateOverlayOuter, { visible: true, + rotation: yawHandleRotation, + position: yawCenter, size: outerRadius, innerRadius: 0.9, startAt: 0, @@ -3641,18 +3684,28 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: true, + rotation: yawHandleRotation, + position: yawCenter, size: outerRadius, startAt: 0, endAt: 0, innerRadius: 0.9, }); + Overlays.editOverlay(rotateOverlayTarget, { + visible: true, + rotation: yawHandleRotation, + position: yawCenter + }); + Overlays.editOverlay(rotationDegreesDisplay, { visible: true, }); updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); - print("================== HANDLE_YAW(Beg) <- ======================="); + if(wantDebug){ + print("================== HANDLE_YAW(Beg) <- ======================="); + } }, onEnd: function(event, reason) { print("================== HANDLE_YAW(End) -> ======================="); @@ -3784,10 +3837,25 @@ SelectionDisplay = (function() { // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function(event) { - print("================== HANDLE_PITCH(Beg) -> ======================="); + onBegin: function (event) { + var wantDebug = true; + if (wantDebug){ + print("================== HANDLE_PITCH(Beg) -> ======================="); + } SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + mode = "ROTATE_PITCH"; + rotationNormal = pitchNormal; + //note: It's expected that the intersection is passed when this is called. + if (arguments.length >= 2 ) { + pitchZero = arguments[ 1 ]; + } else { + print("ERROR( pitchHandle.onBegin ) - Intersection wasn't passed!"); + } + + if (wantDebug) { + Vec3.print(" pitchZero: ", pitchZero); + } // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -3798,6 +3866,8 @@ SelectionDisplay = (function() { var outerAlpha = 0.2; Overlays.editOverlay(rotateOverlayInner, { visible: true, + rotation: pitchHandleRotation, + position: pitchCenter, size: innerRadius, innerRadius: 0.9, startAt: 0, @@ -3807,6 +3877,8 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayOuter, { visible: true, + rotation: pitchHandleRotation, + position: pitchCenter, size: outerRadius, innerRadius: 0.9, startAt: 0, @@ -3816,6 +3888,8 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: true, + rotation: pitchHandleRotation, + position: pitchCenter, size: outerRadius, startAt: 0, endAt: 0, @@ -3826,8 +3900,16 @@ SelectionDisplay = (function() { visible: true, }); + Overlays.editOverlay(rotateOverlayTarget, { + visible: true, + rotation: pitchHandleRotation, + position: pitchCenter + }); + updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); - print("================== HANDLE_PITCH(Beg) <- ======================="); + if(wantDebug){ + print("================== HANDLE_PITCH(Beg) <- ======================="); + } }, onEnd: function(event, reason) { print("================== HANDLE_PITCH(End) -> ======================="); @@ -3847,7 +3929,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); print("================== HANDLE_PITCH(End) <- ======================="); }, - onMove: function(event) { + onMove: function (event) { print("================== HANDLE_PITCH(Mve) -> ======================="); var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { @@ -3949,10 +4031,25 @@ SelectionDisplay = (function() { // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function(event) { - print("================== HANDLE_ROLL(Beg) -> ======================="); + onBegin: function (event) { + var wantDebug = true; + if(wantDebug){ + print("================== HANDLE_ROLL(Beg) -> ======================="); + } SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + mode = "ROTATE_ROLL"; + rotationNormal = rollNormal; + //note: It's expected that the intersection is passed when this is called. + if (arguments.length >= 2 ) { + rollZero = arguments[ 1 ]; + } else { + print("ERROR( rollHandle.onBegin ) - Intersection wasn't passed!"); + } + + if (wantDebug) { + Vec3.print(" rollZero: ", rollZero); + } // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -3963,6 +4060,8 @@ SelectionDisplay = (function() { var outerAlpha = 0.2; Overlays.editOverlay(rotateOverlayInner, { visible: true, + rotation: rollHandleRotation, + position: rollCenter, size: innerRadius, innerRadius: 0.9, startAt: 0, @@ -3972,6 +4071,8 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayOuter, { visible: true, + rotation: rollHandleRotation, + position: rollCenter, size: outerRadius, innerRadius: 0.9, startAt: 0, @@ -3981,6 +4082,8 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: true, + rotation: rollHandleRotation, + position: rollCenter, size: outerRadius, startAt: 0, endAt: 0, @@ -3991,10 +4094,18 @@ SelectionDisplay = (function() { visible: true, }); + Overlays.editOverlay(rotateOverlayTarget, { + visible: true, + rotation: rollHandleRotation, + position: rollCenter + }); + updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); - print("================== HANDLE_ROLL(Beg) <- ======================="); + if(wantDebug){ + print("================== HANDLE_ROLL(Beg) <- ======================="); + } }, - onEnd: function(event, reason) { + onEnd: function (event, reason) { print("================== HANDLE_ROLL(End) -> ======================="); Overlays.editOverlay(rotateOverlayInner, { visible: false @@ -4141,11 +4252,68 @@ SelectionDisplay = (function() { } }; + + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { + var wantDebug = true; + if ((queryRay === undefined) || (queryRay === null)) { + if (wantDebug) { + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); + + if (wantDebug) { + if ( ! overlayIncludes ){ + print("testRayIntersect - no overlayIncludes provided."); + } + if ( ! overlayExcludes ){ + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + function checkIntersectWithHUD(queryRay) { + var intersectObj = testRayIntersect(queryRay, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + + return intersectObj; + } + + function checkIntersectWithNonSelectionItems(queryRay) { + var intersectObj = testRayIntersect(queryRay, null, [yawHandle, pitchHandle, rollHandle, selectionBox]); + + return intersectObj; + } + + function checkIntersectWithRotationHandles(queryRay) { + var intersectObj = testRayIntersect(queryRay, [yawHandle, pitchHandle, rollHandle]); + + return intersectObj; + } + + function checkIntersectWithSelectionBox(queryRay) { + var intersectObj = testRayIntersect(queryRay, [selectionBox]); + + return intersectObj; + } + //-------------------- + + //--------------------------------------- // FUNCTION: MOUSE PRESS EVENT - that.mousePressEvent = function(event) { - var wantDebug = false; - if ( wantDebug ) { - print( "=============== eST::MousePressEvent BEG ======================="); + that.mousePressEvent = function (event) { + var wantDebug = true; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); } if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it @@ -4155,28 +4323,18 @@ SelectionDisplay = (function() { var somethingClicked = false; var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - if (result.intersects) { + var results_checkHUD = checkIntersectWithHUD(pickRay); + if (results_checkHUD.intersects) { // mouse clicks on the tablet should override the edit affordances return false; } - entityIconOverlayManager.setIconsSelectable(selectionManager.selections,true); + entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); // ignore ray intersection for our selection box and yaw/pitch/roll - result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] ); - if (result.intersects) { - if (wantDebug) { - print("something intersects... "); - print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); - print(" result.intersects:" + result.intersects); - print(" result.overlayID:" + result.overlayID); - print(" result.distance:" + result.distance); - print(" result.face:" + result.face); - Vec3.print(" result.intersection:", result.intersection); - } - - var tool = grabberTools[result.overlayID]; + var results_checkNonSelection = checkIntersectWithNonSelectionItems(pickRay); + if (results_checkNonSelection.intersects) { + var tool = grabberTools[results_checkNonSelection.overlayID]; if (tool) { if (wantDebug) { print("Intersected with known table tool( mode: " + tool.mode + " )"); @@ -4184,99 +4342,20 @@ SelectionDisplay = (function() { activeTool = tool; mode = tool.mode; somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { + if (activeTool.onBegin) { activeTool.onBegin(event); + } else if (wantDebug) { + print(" ActiveTool( " + activeTool.mode + " ) missing onBegin"); } } else { - if (wantDebug) { - print("Intersected with unregistered tool..."); - } - switch (result.overlayID) { - case grabberMoveUp: - if (wantDebug){ - print("grabberMoveUp"); - } - mode = "TRANSLATE_UP_DOWN"; - somethingClicked = mode; - - // in translate mode, we hide our stretch handles... - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - visible: false - }); - } - break; - - - case grabberNEAR: - case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? - case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? - if(wantDebug){ - print("grabberNear variant"); - } - mode = "STRETCH_NEAR"; - somethingClicked = mode; - break; - - case grabberFAR: - case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? - case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? - if(wantDebug){ - print("grabberFar variant"); - } - mode = "STRETCH_FAR"; - somethingClicked = mode; - break; - case grabberTOP: - if(wantDebug){ - print("grabberTOP"); - } - mode = "STRETCH_TOP"; - somethingClicked = mode; - break; - case grabberBOTTOM: - if(wantDebug){ - print("grabberBOTTOM"); - } - mode = "STRETCH_BOTTOM"; - somethingClicked = mode; - break; - case grabberRIGHT: - case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? - case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? - if(wantDebug){ - print("grabberRight variant"); - } - mode = "STRETCH_RIGHT"; - somethingClicked = mode; - break; - case grabberLEFT: - case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? - case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? - if(wantDebug){ - print("grabberLeft variant"); - } - mode = "STRETCH_LEFT"; - somethingClicked = mode; - break; - - default: - if(wantDebug){ - print("UNKNOWN( " + result.overlayID + " )"); - } - mode = "UNKNOWN"; - break; - } - if(wantDebug){ - print(" Unregistered Tool Mode: " + mode ); - } - } - } + mode = "UNKNOWN"; + }//--End_if(tool) + }//--End_if(results_checkNonSelection.intersects) // if one of the items above was clicked, then we know we are in translate or stretch mode, and we // should hide our rotate handles... if (somethingClicked) { - if(wantDebug){ + if (wantDebug) { print(" Trying to hide PitchYawRoll Handles"); } Overlays.editOverlay(yawHandle, { @@ -4307,133 +4386,45 @@ SelectionDisplay = (function() { // Only intersect versus yaw/pitch/roll. - result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); - - var overlayOrientation; - var overlayCenter; - + var results_checkRotationHandles = checkIntersectWithRotationHandles(pickRay); var properties = Entities.getEntityProperties(selectionManager.selections[0]); var angles = Quat.safeEulerAngles(properties.rotation); var pitch = angles.x; var yaw = angles.y; var roll = angles.z; + //TODO_Case6491: Should these only be updated when we actually touched + // a handle. (The answer is most likley: Yes) Potentially move chunk + // to either tool's onBegin or before rayCast here when refactored. originalRotation = properties.rotation; originalPitch = pitch; originalYaw = yaw; originalRoll = roll; - if (result.intersects) { - var resultTool = grabberTools[result.overlayID]; - if(wantDebug){ + if (results_checkRotationHandles.intersects) { + var resultTool = grabberTools[results_checkRotationHandles.overlayID]; + if (wantDebug) { print("Intersection detected with handle..."); } if (resultTool) { - if(wantDebug){ + if (wantDebug) { print(" " + resultTool.mode); } activeTool = resultTool; - mode = resultTool.mode; - somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { - if(wantDebug){ - print(" Triggering Tool's onBegin"); - } - activeTool.onBegin(event); - } else if(wantDebug) { - print(" Tool's missing onBegin"); + somethingClicked = resultTool.mode; + if(activeTool.onBegin) { + activeTool.onBegin(event, results_checkRotationHandles.intersection); + } else if (wantDebug) { + print(" ActiveTool( " + activeTool.mode + " ) missing onBegin"); } - } - switch (result.overlayID) { - case yawHandle: - if(wantDebug){ - print("Handle_YAW"); - } - mode = "ROTATE_YAW"; - somethingClicked = mode; - overlayOrientation = yawHandleRotation; - overlayCenter = yawCenter; - yawZero = result.intersection; - rotationNormal = yawNormal; - if(wantDebug){ - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); - } - break; + }//--End_If(resultTool) + }//--End_If(results_checkRotationHandles.intersects) - case pitchHandle: - if(wantDebug){ - print("Handle_PITCH"); - } - mode = "ROTATE_PITCH"; - initialPosition = SelectionManager.worldPosition; - somethingClicked = mode; - overlayOrientation = pitchHandleRotation; - overlayCenter = pitchCenter; - pitchZero = result.intersection; - rotationNormal = pitchNormal; - if(wantDebug){ - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); - } - break; + if (somethingClicked) { - case rollHandle: - if(wantDebug){ - print("Handle_ROLL"); - } - mode = "ROTATE_ROLL"; - somethingClicked = mode; - overlayOrientation = rollHandleRotation; - overlayCenter = rollCenter; - rollZero = result.intersection; - rotationNormal = rollNormal; - if(wantDebug){ - print("rotationNormal set to: " + rotationNormal.x + ", " + rotationNormal.y + ", " + rotationNormal.z); - } - break; - - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; - } - } - if (wantDebug) { - print(" somethingClicked:" + somethingClicked); - print(" mode:" + mode); - } - - if (somethingClicked) { - - if(wantDebug){ - print(" Trying to show rotateOverlay Handles"); - } - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 0 - }); - if(wantDebug){ + if (wantDebug) { + print(" somethingClicked:" + somethingClicked); + print(" mode:" + mode); print(" Trying to hide PitchYawRoll Handles"); } Overlays.editOverlay(yawHandle, { @@ -4541,49 +4532,31 @@ SelectionDisplay = (function() { if (!somethingClicked) { // Only intersect versus selectionBox. - result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); - if (result.intersects) { - switch (result.overlayID) { - case selectionBox: - activeTool = translateXZTool; - if(wantDebug){ - print("Clicked selectionBox, Activating Tool: " + activeTool.mode ); - } - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - if (wantDebug) { - print("longest dimension: " + translateXZTool.greatestDimension); - translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); - print("starting distance: " + translateXZTool.startingDistance); - translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); - print(" starting elevation: " + translateXZTool.startingElevation); - } - - mode = translateXZTool.mode; - activeTool.onBegin(event); - somethingClicked = 'selectionBox'; - break; - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; + var results_checkSelectionBox = checkIntersectWithSelectionBox( pickRay ); + if (results_checkSelectionBox.intersects) { + activeTool = translateXZTool; + if(wantDebug){ + print("Clicked selectionBox, Activating Tool: " + activeTool.mode ); } + var intersectInfo = { + queryRay: pickRay, + results: results_checkSelectionBox + }; + activeTool.onBegin(event, null, intersectInfo); + somethingClicked = 'selectionBox'; } } if (somethingClicked) { - pickRay = generalComputePickRay(event.x, event.y); if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); + print("mousePressEvent()...... " + somethingClicked); + print(" mode: " + mode); } } // reset everything as intersectable... // TODO: we could optimize this since some of these were already flipped back(i.e: just get rid of this) - if(wantDebug){ + if (wantDebug) { print("Trying to set SelectionBox & PitchYawRoll Handles to NOT_IGNORE Rays"); } Overlays.editOverlay(selectionBox, { @@ -4599,8 +4572,8 @@ SelectionDisplay = (function() { ignoreRayIntersection: false }); - if ( wantDebug ) { - print( "=============== eST::MousePressEvent END ======================="); + if (wantDebug) { + print("=============== eST::MousePressEvent END ======================="); } return somethingClicked; @@ -4613,18 +4586,18 @@ SelectionDisplay = (function() { print( "=============== eST::MouseMoveEvent BEG ======================="); } if (activeTool) { - if(wantDebug){ + if (wantDebug) { print(" Trigger ActiveTool( " + activeTool.mode + " )'s onMove"); } activeTool.onMove(event); - - if(wantDebug){ + + if (wantDebug) { print(" Trigger SelectionManager::update"); } SelectionManager._update(); - - if(wantDebug){ - print( "=============== eST::MouseMoveEvent END ======================="); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); } //--EARLY EXIT--( Move handled via active tool) return true; @@ -4710,7 +4683,7 @@ SelectionDisplay = (function() { pickedAlpha = grabberAlpha; highlightNeeded = true; break; - + default: if (previousHandle) { Overlays.editOverlay(previousHandle, { From 18cc632df5f40a04cdb0937b23d36e4fe584dc66 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Tue, 8 Aug 2017 16:37:59 -0400 Subject: [PATCH 377/504] [Case 6491] Minor: Switching off wantDebug flags that were left on. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index ce93a6f366..85e28d7170 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2393,7 +2393,7 @@ SelectionDisplay = (function() { startingDistance: 0.0, startingElevation: 0.0, onBegin: function(event,isAltFromGrab, intersectInfo) { - var wantDebug = true; + var wantDebug = false; if(wantDebug){ print("================== TRANSLATE_XZ(Beg) -> ======================="); Vec3.print(" intersectInfo.queryRay", intersectInfo.queryRay); @@ -3634,7 +3634,7 @@ SelectionDisplay = (function() { addGrabberTool(yawHandle, { mode: "ROTATE_YAW", onBegin: function(event) { - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("================== HANDLE_YAW(Beg) -> ======================="); } @@ -3838,7 +3838,7 @@ SelectionDisplay = (function() { addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function (event) { - var wantDebug = true; + var wantDebug = false; if (wantDebug){ print("================== HANDLE_PITCH(Beg) -> ======================="); } @@ -4032,7 +4032,7 @@ SelectionDisplay = (function() { addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function (event) { - var wantDebug = true; + var wantDebug = false; if(wantDebug){ print("================== HANDLE_ROLL(Beg) -> ======================="); } @@ -4255,7 +4255,7 @@ SelectionDisplay = (function() { // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { - var wantDebug = true; + var wantDebug = false; if ((queryRay === undefined) || (queryRay === null)) { if (wantDebug) { print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); @@ -4306,12 +4306,10 @@ SelectionDisplay = (function() { return intersectObj; } - //-------------------- - //--------------------------------------- // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function (event) { - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("=============== eST::MousePressEvent BEG ======================="); } From 926789437c1b6571c0bf27e2a7299f01956367ff Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Tue, 8 Aug 2017 18:47:01 -0400 Subject: [PATCH 378/504] [Case 6491] Propagates rotation reposition fix from YawHandle (details below). This wraps the selections rotation update handling into common helper function utilized by all rotation handle tools (yaw,pitch,roll). This function is the generalized fix previously exclusive to yawHandle. This functions is now called within onMove for yaw, pitch, & rollHandle tools. NOTE(s): * Tested yaw, pitch, & roll rotation didn't see any aberrant behavior. ** Tested overlapping shapes and selecting the overlapping portions followed by a rotation handle. Only one took hold as a selection. ** Tested multiple selection and objects rotated & repositioned about the encapsulating bounding box's center point as expected. * Tested translation with multiple items selected and it behaved as expected. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 85e28d7170..159b009725 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -3629,6 +3629,38 @@ SelectionDisplay = (function() { print( "<--- updateRotationDegreesOverlay ---" ); } + // FUNCTION DEF: updateSelectionsRotation + // Helper func used by rotation grabber tools + function updateSelectionsRotation( rotationChange ) { + if ( ! rotationChange ) { + print("ERROR( updateSelectionsRotation ) - Invalid arg specified!!"); + + //--EARLY EXIT-- + return; + } + + // Entities should only reposition if we are rotating multiple selections around + // the selections center point. Otherwise, the rotation will be around the entities + // registration point which does not need repositioning. + var reposition = (SelectionManager.selections.length > 1); + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + + var newProperties = { + rotation: Quat.multiply(rotationChange, initialProperties.rotation), + }; + + if (reposition) { + var dPos = Vec3.subtract(initialProperties.position, initialPosition); + dPos = Vec3.multiplyQbyV(rotationChange, dPos); + newProperties.position = Vec3.sum(initialPosition, dPos); + } + + Entities.editEntity(entityID, newProperties); + } + } + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { @@ -3756,27 +3788,7 @@ SelectionDisplay = (function() { z: 0 }); - // Entities should only reposition if we are rotating multiple selections around - // the selections center point. Otherwise, the rotation will be around the entities - // registration point which does not need repositioning. - var reposition = SelectionManager.selections.length > 1; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); - var initialProperties = SelectionManager.savedProperties[entityID]; - - var newProperties = { - rotation: Quat.multiply(yawChange, initialProperties.rotation), - }; - - if (reposition) { - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(yawChange, dPos); - newProperties.position = Vec3.sum(initialPosition, dPos); - } - - Entities.editEntity(entityID, newProperties); - } + updateSelectionsRotation( yawChange ); updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter); @@ -3961,17 +3973,7 @@ SelectionDisplay = (function() { z: 0 }); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(pitchChange, dPos); - - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(pitchChange, initialProperties.rotation), - }); - } + updateSelectionsRotation( pitchChange ); updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter); @@ -4154,17 +4156,8 @@ SelectionDisplay = (function() { y: 0, z: angleFromZero }); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(rollChange, dPos); - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(rollChange, initialProperties.rotation), - }); - } + updateSelectionsRotation( rollChange ); updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter); From 96afbeca23a1d2581ec90c246ad677e64f9989ef Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Wed, 9 Aug 2017 13:56:08 -0400 Subject: [PATCH 379/504] [Case 6491] Dedupe rotation handle tool code (details below). Pulled the common code shared between the rotation handle tools out into helper funcs: * helperRotationHandleOnBegin * helperRotationHandleOnMove * helperRotationHandleOnEnd These functions either take in or derive the necessary info needed to handle a specific rotation axis. NOTE(s): * Tested yaw, pitch, & roll handles using a box. The behavior remained consistent with that prior to this commit. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 743 ++++++------------ 1 file changed, 226 insertions(+), 517 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 159b009725..eb3deb590d 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -3661,557 +3661,266 @@ SelectionDisplay = (function() { } } + function helperRotationHandleOnBegin( rotMode, rotNormal, rotCenter, handleRotation ) { + var wantDebug = false; + if (wantDebug) { + print("================== " + rotMode + "(onBegin) -> ======================="); + } + + SelectionManager.saveProperties(); + initialPosition = SelectionManager.worldPosition; + mode = rotMode; + rotationNormal = rotNormal; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha, + }); + + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); + + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); + + Overlays.editOverlay(rotateOverlayTarget, { + visible: true, + rotation: handleRotation, + position: rotCenter + }); + + Overlays.editOverlay(rotationDegreesDisplay, { + visible: true, + }); + + updateRotationDegreesOverlay(0, handleRotation, rotCenter); + if (wantDebug) { + print("================== " + rotMode + "(onBegin) <- ======================="); + } + }//--End_Function( helperRotationHandleOnBegin ) + + function helperRotationHandleOnMove( event, rotMode, rotZero, rotCenter, handleRotation ) { + + if ( ! (rotMode == "ROTATE_YAW" || rotMode == "ROTATE_PITCH" || rotMode == "ROTATE_ROLL") ) { + print("ERROR( handleRotationHandleOnMove ) - Encountered Unknown/Invalid RotationMode: " + rotMode ); + + //--EARLY EXIT-- + return; + } else if ( ! rotZero ) { + print("ERROR( handleRotationHandleOnMove ) - Invalid RotationZero Specified" ); + + //--EARLY EXIT-- + return; + } + + var wantDebug = false; + if (wantDebug) { + print("================== "+ rotMode + "(onMove) -> ======================="); + Vec3.print(" rotZero: ", rotZero); + } + var pickRay = generalComputePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + visible: false + }); + + var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); + if (result.intersects) { + var centerToZero = Vec3.subtract(rotZero, rotCenter); + var centerToIntersect = Vec3.subtract(result.intersection, rotCenter); + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + } + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(rotCenter, result.intersection); + var snapToInner = distanceFromCenter < innerRadius; + var snapAngle = snapToInner ? innerSnapAngle : 1.0; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + + var rotChange = null; + switch( rotMode ) { + case "ROTATE_YAW": + rotChange = Quat.fromVec3Degrees( {x: 0, y: angleFromZero, z: 0} ); + break; + case "ROTATE_PITCH": + rotChange = Quat.fromVec3Degrees( {x: angleFromZero, y: 0, z: 0} ); + break; + case "ROTATE_ROLL": + rotChange = Quat.fromVec3Degrees( {x: 0, y: 0, z: angleFromZero} ); + break; + } + updateSelectionsRotation( rotChange ); + + updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + }); + } else { + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + }); + } + }//--End_If( results.intersects ) + + if (wantDebug) { + print("================== "+ rotMode + "(onMove) <- ======================="); + } + }//--End_Function( helperRotationHandleOnMove ) + + function helperRotationHandleOnEnd() { + var wantDebug = false; + if (wantDebug) { + print("================== " + mode + "(onEnd) -> ======================="); + } + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); + + pushCommandForSelections(); + + if (wantDebug) { + print("================== " + mode + "(onEnd) <- ======================="); + } + }//--End_Function( helperRotationHandleOnEnd ) + + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", - onBegin: function(event) { - var wantDebug = false; - if (wantDebug) { - print("================== HANDLE_YAW(Beg) -> ======================="); - } - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - mode = "ROTATE_YAW"; - rotationNormal = yawNormal; + onBegin: function(event, zeroPoint) { //note: It's expected that the intersection is passed when this is called. - if (arguments.length >= 2 ) { - yawZero = arguments[ 1 ]; - } else { - print("ERROR( yawHandle.onBegin ) - Intersection wasn't passed!"); - } + // validity will be checked later as a pre-requisite for onMove handling. + yawZero = zeroPoint; - if (wantDebug) { - Vec3.print(" yawZero: ", yawZero); - } - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: yawHandleRotation, - position: yawCenter, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha, - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: yawHandleRotation, - position: yawCenter, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: yawHandleRotation, - position: yawCenter, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: yawHandleRotation, - position: yawCenter - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); - if(wantDebug){ - print("================== HANDLE_YAW(Beg) <- ======================="); - } + helperRotationHandleOnBegin( "ROTATE_YAW", yawNormal, yawCenter, yawHandleRotation ); }, onEnd: function(event, reason) { - print("================== HANDLE_YAW(End) -> ======================="); - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); - print("================== HANDLE_YAW(End) <- ======================="); + helperRotationHandleOnEnd(); }, onMove: function(event) { - print("================== HANDLE_YAW(Mve) -> ======================="); - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = yawCenter; - var zero = yawZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero and centerToIntersect - // handles that internally, so it's to pass unnormalized vectors here. - print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var yawChange = Quat.fromVec3Degrees({ - x: 0, - y: angleFromZero, - z: 0 - }); - - updateSelectionsRotation( yawChange ); - - updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - - } - print("================== HANDLE_YAW(Mve) <- ======================="); + helperRotationHandleOnMove( event, "ROTATE_YAW", yawZero, yawCenter, yawHandleRotation ); } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function (event) { - var wantDebug = false; - if (wantDebug){ - print("================== HANDLE_PITCH(Beg) -> ======================="); - } - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - mode = "ROTATE_PITCH"; - rotationNormal = pitchNormal; + onBegin: function (event, zeroPoint) { //note: It's expected that the intersection is passed when this is called. - if (arguments.length >= 2 ) { - pitchZero = arguments[ 1 ]; - } else { - print("ERROR( pitchHandle.onBegin ) - Intersection wasn't passed!"); - } - - if (wantDebug) { - Vec3.print(" pitchZero: ", pitchZero); - } + // validity will be checked later as a pre-requisite for onMove handling. + pitchZero = zeroPoint; - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: pitchHandleRotation, - position: pitchCenter, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: pitchHandleRotation, - position: pitchCenter, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: pitchHandleRotation, - position: pitchCenter, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: pitchHandleRotation, - position: pitchCenter - }); - - updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); - if(wantDebug){ - print("================== HANDLE_PITCH(Beg) <- ======================="); - } + helperRotationHandleOnBegin( "ROTATE_PITCH", pitchNormal, pitchCenter, pitchHandleRotation ); }, onEnd: function(event, reason) { - print("================== HANDLE_PITCH(End) -> ======================="); - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); - print("================== HANDLE_PITCH(End) <- ======================="); + helperRotationHandleOnEnd(); }, onMove: function (event) { - print("================== HANDLE_PITCH(Mve) -> ======================="); - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = pitchCenter; - var zero = pitchZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var pitchChange = Quat.fromVec3Degrees({ - x: angleFromZero, - y: 0, - z: 0 - }); - - updateSelectionsRotation( pitchChange ); - - updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } - print("================== HANDLE_PITCH(Mve) <- ======================="); + helperRotationHandleOnMove( event, "ROTATE_PITCH", pitchZero, pitchCenter, pitchHandleRotation ); } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function (event) { - var wantDebug = false; - if(wantDebug){ - print("================== HANDLE_ROLL(Beg) -> ======================="); - } - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - mode = "ROTATE_ROLL"; - rotationNormal = rollNormal; + onBegin: function (event, zeroPoint) { //note: It's expected that the intersection is passed when this is called. - if (arguments.length >= 2 ) { - rollZero = arguments[ 1 ]; - } else { - print("ERROR( rollHandle.onBegin ) - Intersection wasn't passed!"); - } - - if (wantDebug) { - Vec3.print(" rollZero: ", rollZero); - } + // validity will be checked later as a pre-requisite for onMove handling. + rollZero = zeroPoint; - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: rollHandleRotation, - position: rollCenter, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: rollHandleRotation, - position: rollCenter, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: rollHandleRotation, - position: rollCenter, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: rollHandleRotation, - position: rollCenter - }); - - updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); - if(wantDebug){ - print("================== HANDLE_ROLL(Beg) <- ======================="); - } + helperRotationHandleOnBegin( "ROTATE_ROLL", rollNormal, rollCenter, rollHandleRotation ); }, onEnd: function (event, reason) { - print("================== HANDLE_ROLL(End) -> ======================="); - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); - print("================== HANDLE_ROLL(End) <- ======================="); + helperRotationHandleOnEnd(); }, onMove: function(event) { - print("================== HANDLE_ROLL(Mve) -> ======================="); - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = rollCenter; - var zero = rollZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - print(" RotNormal - X: " + rotationNormal.x + " Y: " + rotationNormal.y + " Z: " + rotationNormal.z); - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var rollChange = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: angleFromZero - }); - - updateSelectionsRotation( rollChange ); - - updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } - print("================== HANDLE_ROLL(Mve) <- ======================="); - + helperRotationHandleOnMove( event, "ROTATE_ROLL", rollZero, rollCenter, rollHandleRotation ); } }); From fe171af31bef16bf87d3424546c908c241f536be Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Wed, 9 Aug 2017 18:11:44 -0400 Subject: [PATCH 380/504] [Case 6491] entitySelectionTool mousePressEvent refactor/cleanup (details below). Fixes situations where attempting to click on a rotate, grab, or scale handle that was in front of others might unexpectedly activate the obscured handle. As of this commit the flow of mousePressEvent such that the first item whether it be a tool or selection is what reacts and absorbs the click/touch. This is counter to the prior behavior where whichever item or tool last passed the check would determine what was hit and handled the touch _even_ when it wasn't the first thing to be touched. * Got rid of some unused/stale vars * Added some convenience functions * setRotationHandlesVisible function * setStretchHandlesVisible function * setGrabberMoveUpVisible function * Removed checkIntersectWith helper functions as they're no longer used. * Normalized onBegin signatures for all GrabberTools to support the new flow within mousePressEvent. * These are tools registered via addGrabberTool/makeStretchTool. * The onBegin signature changes from onBegin( event ) to onBegin( event, intersectResult ). * This allows for a simpler tool triggering where tools which utilized the additional information provided will have it, those which may need it the future shall have it with little issue, and those that don't care may ignore it. NOTE(s): * Tested normal movement within opening creation menu: Passed * With Creation Menu Open: * Tested clicking around the world in empty space: Passed * Tested single selection: Passed * Tested single selection rotation (pitch, yaw, roll): Passed * Tested single selection translation (xz, y): Passed * Tested multiple selection: Passed * Tested multiple selection rotation (pitch, yaw, roll): Passed * Tested multiple selection translation (xz, y): Passed Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 374 +++++------------- 1 file changed, 92 insertions(+), 282 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index eb3deb590d..3c36f307d0 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -306,11 +306,6 @@ SelectionDisplay = (function() { var rollNormal; var rotationNormal; - var originalRotation; - var originalPitch; - var originalYaw; - var originalRoll; - var handleColor = { red: 255, @@ -2375,6 +2370,28 @@ SelectionDisplay = (function() { } }; + // FUNCTION: SET ROTATION HANDLES VISIBLE + that.setRotationHandlesVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + Overlays.editOverlay(yawHandle, visibilityUpdate); + Overlays.editOverlay(pitchHandle, visibilityUpdate); + Overlays.editOverlay(rollHandle, visibilityUpdate); + }; + + // FUNCTION: SET STRETCH HANDLES VISIBLE + that.setStretchHandlesVisible = function(isVisible) { + var numHandles = stretchHandles.length; + var visibilityUpdate = { visible: isVisible }; + for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { + Overlays.editOverlay(stretchHandles[ handleIndex ], visibilityUpdate); + } + }; + + // FUNCTION: SET GRABBER MOVE UP VISIBLE + that.setGrabberMoveUpVisible = function(isVisible) { + Overlays.editOverlay(grabberMoveUp, { visible: isVisible }); + }; + // FUNCTION: UNSELECT // TODO?: Needs implementation that.unselect = function(entityID) {}; @@ -2392,7 +2409,7 @@ SelectionDisplay = (function() { greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event,isAltFromGrab, intersectInfo) { + onBegin: function(event,isAltFromGrab,intersectInfo) { var wantDebug = false; if(wantDebug){ print("================== TRANSLATE_XZ(Beg) -> ======================="); @@ -2402,6 +2419,9 @@ SelectionDisplay = (function() { } SelectionManager.saveProperties(); + that.setRotationHandlesVisible( false ); + that.setGrabberMoveUpVisible( false ); + startPosition = SelectionManager.worldPosition; mode = translateXZTool.mode; @@ -2603,7 +2623,7 @@ SelectionDisplay = (function() { var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", - onBegin: function(event) { + onBegin: function(event, intersectResult) { pickRay = generalComputePickRay(event.x, event.y); upDownPickNormal = Quat.getForward(lastCameraOrientation); @@ -2613,6 +2633,8 @@ SelectionDisplay = (function() { lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); SelectionManager.saveProperties(); + that.setGrabberMoveUpVisible( true ); + that.setRotationHandlesVisible( false ); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -2676,15 +2698,23 @@ SelectionDisplay = (function() { // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", - onBegin: function(event) { + onBegin: function(event, intersectResult) { var pickRay = generalComputePickRay(event.x, event.y); + //TODO_Case6491: This may be doing duplicate works that's handled + // within translateXZTool.onBegin. Verify and if so + // remove... var result = Overlays.findRayIntersection(pickRay); translateXZTool.pickPlanePosition = result.intersection; translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); - translateXZTool.onBegin(event,true); + var intersectInfo = { + queryRay: pickRay, + results: intersectResult + }; + + translateXZTool.onBegin(event,true,intersectInfo); }, elevation: function (event) { translateXZTool.elevation(event); @@ -2715,6 +2745,7 @@ SelectionDisplay = (function() { // direction - direction to stretch in // pivot - point to use as a pivot // offset - the position of the overlay tool relative to the selections center position + // @return: tool obj var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) { // directionFor3DStretch - direction and pivot for 3D stretch // distanceFor3DStretch - distance from the intersection point and the handController @@ -2756,7 +2787,7 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; - var onBegin = function(event) { + var onBegin = function(event, intersectResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); @@ -3668,6 +3699,10 @@ SelectionDisplay = (function() { } SelectionManager.saveProperties(); + that.setRotationHandlesVisible( false ); + that.setStretchHandlesVisible( false ); + that.setGrabberMoveUpVisible( false ); + initialPosition = SelectionManager.worldPosition; mode = rotMode; rotationNormal = rotNormal; @@ -3871,10 +3906,10 @@ SelectionDisplay = (function() { var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", - onBegin: function(event, zeroPoint) { - //note: It's expected that the intersection is passed when this is called. + onBegin: function(event, intersectResult) { + //note: It's expected that the intersect result is passed when this is called. // validity will be checked later as a pre-requisite for onMove handling. - yawZero = zeroPoint; + yawZero = intersectResult.intersection; helperRotationHandleOnBegin( "ROTATE_YAW", yawNormal, yawCenter, yawHandleRotation ); }, @@ -3890,10 +3925,10 @@ SelectionDisplay = (function() { // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function (event, zeroPoint) { - //note: It's expected that the intersection is passed when this is called. + onBegin: function (event, intersectResult) { + //note: It's expected that the intersect result is passed when this is called. // validity will be checked later as a pre-requisite for onMove handling. - pitchZero = zeroPoint; + pitchZero = intersectResult.intersection; helperRotationHandleOnBegin( "ROTATE_PITCH", pitchNormal, pitchCenter, pitchHandleRotation ); }, @@ -3909,10 +3944,10 @@ SelectionDisplay = (function() { // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function (event, zeroPoint) { - //note: It's expected that the intersection is passed when this is called. + onBegin: function (event, intersectResult) { + //note: It's expected that the intersect result is passed when this is called. // validity will be checked later as a pre-requisite for onMove handling. - rollZero = zeroPoint; + rollZero = intersectResult.intersection; helperRotationHandleOnBegin( "ROTATE_ROLL", rollNormal, rollCenter, rollHandleRotation ); }, @@ -3985,30 +4020,6 @@ SelectionDisplay = (function() { return intersectObj; } - function checkIntersectWithHUD(queryRay) { - var intersectObj = testRayIntersect(queryRay, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - - return intersectObj; - } - - function checkIntersectWithNonSelectionItems(queryRay) { - var intersectObj = testRayIntersect(queryRay, null, [yawHandle, pitchHandle, rollHandle, selectionBox]); - - return intersectObj; - } - - function checkIntersectWithRotationHandles(queryRay) { - var intersectObj = testRayIntersect(queryRay, [yawHandle, pitchHandle, rollHandle]); - - return intersectObj; - } - - function checkIntersectWithSelectionBox(queryRay) { - var intersectObj = testRayIntersect(queryRay, [selectionBox]); - - return intersectObj; - } - // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function (event) { var wantDebug = false; @@ -4016,267 +4027,66 @@ SelectionDisplay = (function() { print("=============== eST::MousePressEvent BEG ======================="); } if (!event.isLeftButton && !that.triggered) { - // if another mouse button than left is pressed ignore it + //--EARLY EXIT-(if another mouse button than left is pressed ignore it) return false; } - var somethingClicked = false; var pickRay = generalComputePickRay(event.x, event.y); - - var results_checkHUD = checkIntersectWithHUD(pickRay); - if (results_checkHUD.intersects) { - // mouse clicks on the tablet should override the edit affordances - return false; - } - - entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); - - // ignore ray intersection for our selection box and yaw/pitch/roll - var results_checkNonSelection = checkIntersectWithNonSelectionItems(pickRay); - if (results_checkNonSelection.intersects) { - var tool = grabberTools[results_checkNonSelection.overlayID]; - if (tool) { - if (wantDebug) { - print("Intersected with known table tool( mode: " + tool.mode + " )"); - } - activeTool = tool; - mode = tool.mode; - somethingClicked = 'tool'; - if (activeTool.onBegin) { - activeTool.onBegin(event); - } else if (wantDebug) { - print(" ActiveTool( " + activeTool.mode + " ) missing onBegin"); - } - } else { - mode = "UNKNOWN"; - }//--End_if(tool) - }//--End_if(results_checkNonSelection.intersects) - - // if one of the items above was clicked, then we know we are in translate or stretch mode, and we - // should hide our rotate handles... - if (somethingClicked) { - if (wantDebug) { - print(" Trying to hide PitchYawRoll Handles"); - } - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - if (mode != "TRANSLATE_UP_DOWN") { - if(wantDebug){ - print(" Trying to hide GrabberMoveUp"); - } - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); + //TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; + for (var key in grabberTools) { + if (grabberTools.hasOwnProperty(key)) { + interactiveOverlays.push( key ); } } - if (!somethingClicked) { - - if (wantDebug) { - print("rotate handle case..."); + mode = "UNKNOWN"; + var results = testRayIntersect( pickRay, interactiveOverlays ); + if ( results.intersects ){ + var hitOverlayID = results.overlayID; + if ( (hitOverlayID == HMD.tabletID) || (hitOverlayID == HMD.tabletScreenID) || (hitOverlayID == HMD.homeButtonID) ) { + //--EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; } + entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); + //TODO_Case6491: Merge if..else( selectionBox ) when onBegin of transXZ is normalized. + if ( hitOverlayID == selectionBox ) { - // Only intersect versus yaw/pitch/roll. - var results_checkRotationHandles = checkIntersectWithRotationHandles(pickRay); - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - var angles = Quat.safeEulerAngles(properties.rotation); - var pitch = angles.x; - var yaw = angles.y; - var roll = angles.z; - - //TODO_Case6491: Should these only be updated when we actually touched - // a handle. (The answer is most likley: Yes) Potentially move chunk - // to either tool's onBegin or before rayCast here when refactored. - originalRotation = properties.rotation; - originalPitch = pitch; - originalYaw = yaw; - originalRoll = roll; - - if (results_checkRotationHandles.intersects) { - var resultTool = grabberTools[results_checkRotationHandles.overlayID]; - if (wantDebug) { - print("Intersection detected with handle..."); - } - if (resultTool) { - if (wantDebug) { - print(" " + resultTool.mode); - } - activeTool = resultTool; - somethingClicked = resultTool.mode; - if(activeTool.onBegin) { - activeTool.onBegin(event, results_checkRotationHandles.intersection); - } else if (wantDebug) { - print(" ActiveTool( " + activeTool.mode + " ) missing onBegin"); - } - }//--End_If(resultTool) - }//--End_If(results_checkRotationHandles.intersects) - - if (somethingClicked) { - - if (wantDebug) { - print(" somethingClicked:" + somethingClicked); - print(" mode:" + mode); - print(" Trying to hide PitchYawRoll Handles"); - } - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - if(wantDebug){ - print(" Trying to hide Non-Light GrabberHandles"); - } - - Overlays.editOverlay(grabberCloner, { - visible: false - }); - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); - Overlays.editOverlay(grabberLBN, { - visible: false - }); - Overlays.editOverlay(grabberLBF, { - visible: false - }); - Overlays.editOverlay(grabberRBN, { - visible: false - }); - Overlays.editOverlay(grabberRBF, { - visible: false - }); - Overlays.editOverlay(grabberLTN, { - visible: false - }); - Overlays.editOverlay(grabberLTF, { - visible: false - }); - Overlays.editOverlay(grabberRTN, { - visible: false - }); - Overlays.editOverlay(grabberRTF, { - visible: false - }); - - Overlays.editOverlay(grabberTOP, { - visible: false - }); - Overlays.editOverlay(grabberBOTTOM, { - visible: false - }); - Overlays.editOverlay(grabberLEFT, { - visible: false - }); - Overlays.editOverlay(grabberRIGHT, { - visible: false - }); - Overlays.editOverlay(grabberNEAR, { - visible: false - }); - Overlays.editOverlay(grabberFAR, { - visible: false - }); - - Overlays.editOverlay(grabberEdgeTR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFL, { - visible: false - }); - } - } - - if (!somethingClicked) { - // Only intersect versus selectionBox. - var results_checkSelectionBox = checkIntersectWithSelectionBox( pickRay ); - if (results_checkSelectionBox.intersects) { activeTool = translateXZTool; if(wantDebug){ - print("Clicked selectionBox, Activating Tool: " + activeTool.mode ); + print(" Clicked selectionBox, Activating Tool: " + activeTool.mode ); } var intersectInfo = { queryRay: pickRay, - results: results_checkSelectionBox + results: results }; + activeTool.onBegin(event, null, intersectInfo); - somethingClicked = 'selectionBox'; - } - } - - if (somethingClicked) { - if (wantDebug) { - print("mousePressEvent()...... " + somethingClicked); - print(" mode: " + mode); - } - } - - // reset everything as intersectable... - // TODO: we could optimize this since some of these were already flipped back(i.e: just get rid of this) - if (wantDebug) { - print("Trying to set SelectionBox & PitchYawRoll Handles to NOT_IGNORE Rays"); - } - Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(yawHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(pitchHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(rollHandle, { - ignoreRayIntersection: false - }); + } else { //...see if a tool was hit as that's the only thing left that we care about. + var hitTool = grabberTools[ hitOverlayID ]; + if ( hitTool ) { + activeTool = hitTool; + mode = activeTool.mode; //< TODO: Is this centrally handled in all tool begins? + if (activeTool.onBegin) { + activeTool.onBegin(event, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool( " + activeTool.mode + " ) missing onBegin"); + } + } else { + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }//--End_if( hitTool ) + }//--End_if( hitOverlayID == selectionBox ) + }//--End_If( results.intersects ) if (wantDebug) { + print(" SelectionDisplay.mode: " + mode ); print("=============== eST::MousePressEvent END ======================="); } - return somethingClicked; + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool or a tool.onBegin. + return (mode != "UNKNOWN"); }; // FUNCTION: MOUSE MOVE EVENT From fa74fbc986550b5cfe662a6da59d9198f3b02b7c Mon Sep 17 00:00:00 2001 From: Leander Hasty <1p-cusack@1stplayable.com> Date: Thu, 10 Aug 2017 13:29:57 -0400 Subject: [PATCH 381/504] [Case 6491] fixes rotation skip near 0 and 180. Previously, when rotating, it would be easy to achieve 1 degree granularity, except near the 0 degree and 180 degree "poles", where it would often e.g. jump from 10 to -10, or 174 to -175, or similar. yawZero/pitchZero/rollZero were all based on the yawHandle/pitchHandle/rollHandle ray intersection, and were not necessarily coplanar with rotateOverlayTarget. As a result, centerToZero didn't lie on the expected plane, while centerToIntersect did. This had the effect of distorting the rotation range. We also have a possible issue in here with editOverlay(rotationOverlayTarget,{rotation:...}) not taking effect immediately. Better to take the existing ray and cast against a known plane, as e.g. translateXZTool and such do. No risk of stale rotation in that case. This also cleans up rotationHelper args a bit to avoid some string switches and keep flow a little more readable. TODO: propagate ray-plane test to helperRotationHandleOnMove rather than relying on ray hit location; normalize onBegin/onMove/etc across all tools to take queryRay and results args to avoid recreating the ray. Reviewed-by: LaShonda Hopper --- .../system/libraries/entitySelectionTool.js | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 3c36f307d0..d37b57e130 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -298,9 +298,7 @@ SelectionDisplay = (function() { var yawCenter; var pitchCenter; var rollCenter; - var yawZero; - var pitchZero; - var rollZero; + var rotZero; var yawNormal; var pitchNormal; var rollNormal; @@ -3692,10 +3690,10 @@ SelectionDisplay = (function() { } } - function helperRotationHandleOnBegin( rotMode, rotNormal, rotCenter, handleRotation ) { + function helperRotationHandleOnBegin( event, rotNormal, rotCenter, handleRotation ) { var wantDebug = false; if (wantDebug) { - print("================== " + rotMode + "(onBegin) -> ======================="); + print("================== " + mode + "(rotation helper onBegin) -> ======================="); } SelectionManager.saveProperties(); @@ -3704,7 +3702,6 @@ SelectionDisplay = (function() { that.setGrabberMoveUpVisible( false ); initialPosition = SelectionManager.worldPosition; - mode = rotMode; rotationNormal = rotNormal; // Size the overlays to the current selection size @@ -3757,28 +3754,31 @@ SelectionDisplay = (function() { }); updateRotationDegreesOverlay(0, handleRotation, rotCenter); + + // Compute zero position now that the overlay is set up. + // TODO editOverlays sync + var pickRay = generalComputePickRay(event.x, event.y); + + var result = rayPlaneIntersection( pickRay, rotCenter, rotationNormal ); + rotZero = result; + if (wantDebug) { - print("================== " + rotMode + "(onBegin) <- ======================="); + print("================== " + mode + "(rotation helper onBegin) <- ======================="); } }//--End_Function( helperRotationHandleOnBegin ) - function helperRotationHandleOnMove( event, rotMode, rotZero, rotCenter, handleRotation ) { + function helperRotationHandleOnMove( event, rotAroundAxis, rotCenter, handleRotation ) { - if ( ! (rotMode == "ROTATE_YAW" || rotMode == "ROTATE_PITCH" || rotMode == "ROTATE_ROLL") ) { - print("ERROR( handleRotationHandleOnMove ) - Encountered Unknown/Invalid RotationMode: " + rotMode ); - - //--EARLY EXIT-- - return; - } else if ( ! rotZero ) { - print("ERROR( handleRotationHandleOnMove ) - Invalid RotationZero Specified" ); + if ( ! rotZero ) { + print("ERROR( handleRotationHandleOnMove ) - Invalid RotationZero Specified (missed rotation target plane?)" ); //--EARLY EXIT-- return; } - var wantDebug = false; + var wantDebug = true; if (wantDebug) { - print("================== "+ rotMode + "(onMove) -> ======================="); + print("================== "+ mode + "(rotation helper onMove) -> ======================="); Vec3.print(" rotZero: ", rotZero); } var pickRay = generalComputePickRay(event.x, event.y); @@ -3794,7 +3794,12 @@ SelectionDisplay = (function() { var centerToZero = Vec3.subtract(rotZero, rotCenter); var centerToIntersect = Vec3.subtract(result.intersection, rotCenter); if (wantDebug) { - Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotZero: ", rotZero); + Vec3.print(" rotCenter: ", rotCenter); + Vec3.print(" intersect: ", result.intersection); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); } // Note: orientedAngle which wants normalized centerToZero and centerToIntersect // handles that internally, so it's to pass unnormalized vectors here. @@ -3805,18 +3810,9 @@ SelectionDisplay = (function() { var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var rotChange = null; - switch( rotMode ) { - case "ROTATE_YAW": - rotChange = Quat.fromVec3Degrees( {x: 0, y: angleFromZero, z: 0} ); - break; - case "ROTATE_PITCH": - rotChange = Quat.fromVec3Degrees( {x: angleFromZero, y: 0, z: 0} ); - break; - case "ROTATE_ROLL": - rotChange = Quat.fromVec3Degrees( {x: 0, y: 0, z: angleFromZero} ); - break; - } + var vec3Degrees = { x: 0, y: 0, z: 0 }; + vec3Degrees[rotAroundAxis] = angleFromZero; + var rotChange = Quat.fromVec3Degrees( vec3Degrees ); updateSelectionsRotation( rotChange ); updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); @@ -3872,7 +3868,7 @@ SelectionDisplay = (function() { }//--End_If( results.intersects ) if (wantDebug) { - print("================== "+ rotMode + "(onMove) <- ======================="); + print("================== "+ mode + "(rotation helper onMove) <- ======================="); } }//--End_Function( helperRotationHandleOnMove ) @@ -3907,17 +3903,14 @@ SelectionDisplay = (function() { addGrabberTool(yawHandle, { mode: "ROTATE_YAW", onBegin: function(event, intersectResult) { - //note: It's expected that the intersect result is passed when this is called. - // validity will be checked later as a pre-requisite for onMove handling. - yawZero = intersectResult.intersection; - - helperRotationHandleOnBegin( "ROTATE_YAW", yawNormal, yawCenter, yawHandleRotation ); + mode = "ROTATE_YAW"; + helperRotationHandleOnBegin( event, yawNormal, yawCenter, yawHandleRotation ); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); }, onMove: function(event) { - helperRotationHandleOnMove( event, "ROTATE_YAW", yawZero, yawCenter, yawHandleRotation ); + helperRotationHandleOnMove( event, "y", yawCenter, yawHandleRotation ); } }); @@ -3926,17 +3919,14 @@ SelectionDisplay = (function() { addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function (event, intersectResult) { - //note: It's expected that the intersect result is passed when this is called. - // validity will be checked later as a pre-requisite for onMove handling. - pitchZero = intersectResult.intersection; - - helperRotationHandleOnBegin( "ROTATE_PITCH", pitchNormal, pitchCenter, pitchHandleRotation ); + mode = "ROTATE_PITCH"; + helperRotationHandleOnBegin( event, pitchNormal, pitchCenter, pitchHandleRotation ); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); }, onMove: function (event) { - helperRotationHandleOnMove( event, "ROTATE_PITCH", pitchZero, pitchCenter, pitchHandleRotation ); + helperRotationHandleOnMove( event, "x", pitchCenter, pitchHandleRotation ); } }); @@ -3945,17 +3935,14 @@ SelectionDisplay = (function() { addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function (event, intersectResult) { - //note: It's expected that the intersect result is passed when this is called. - // validity will be checked later as a pre-requisite for onMove handling. - rollZero = intersectResult.intersection; - - helperRotationHandleOnBegin( "ROTATE_ROLL", rollNormal, rollCenter, rollHandleRotation ); + mode = "ROTATE_ROLL"; + helperRotationHandleOnBegin( event, rollNormal, rollCenter, rollHandleRotation ); }, onEnd: function (event, reason) { helperRotationHandleOnEnd(); }, onMove: function(event) { - helperRotationHandleOnMove( event, "ROTATE_ROLL", rollZero, rollCenter, rollHandleRotation ); + helperRotationHandleOnMove( event, "z", rollCenter, rollHandleRotation ); } }); From 74734f35e98807b1a23a4c796ac5dab0d44b6cfa Mon Sep 17 00:00:00 2001 From: Leander Hasty <1p-cusack@1stplayable.com> Date: Thu, 10 Aug 2017 15:12:36 -0400 Subject: [PATCH 382/504] [Case 6491] more uniform grabber tools; more distrust of rotateOverlayTarget. Rotation tools' onMove methods now use rayPlaneIntersection rather than using the giant invisible rotateOverlayTarget. It can likely be entirely removed in the next commit. tool.onBegin's signature is now (event, pickRay, pickResult, ...) -- this allows us to avoid recomputing the ray or intersection when unnecessary. translateXZTool is now a part of the grabberTools array; addGrabberTool and addStretchTool have been modified to return the tool reference (and not disassemble and reassemble the tools, so they can keep their own local functions and state). This does make the array potentially less type-uniform, which may (?) undo some javascript optimizations, but allows us to eventually remove some state -- and they're only 1x/frame methods. We now compute rotationNormal in rotation tool onBegin, rather than having many different normals lying around. Merged the final branches in mousePressEvent. Also fixed issue with mode TRANSLATE_XZ test within updateRotationHandles. Reviewed-by: LaShonda Hopper --- .../system/libraries/entitySelectionTool.js | 209 +++++------------- 1 file changed, 54 insertions(+), 155 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index d37b57e130..9fd5bfcdee 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -299,9 +299,6 @@ SelectionDisplay = (function() { var pitchCenter; var rollCenter; var rotZero; - var yawNormal; - var pitchNormal; - var rollNormal; var rotationNormal; @@ -1093,12 +1090,8 @@ SelectionDisplay = (function() { return controllerComputePickRay() || Camera.computePickRay(x, y); } function addGrabberTool(overlay, tool) { - grabberTools[overlay] = { - mode: tool.mode, - onBegin: tool.onBegin, - onMove: tool.onMove, - onEnd: tool.onEnd, - }; + grabberTools[overlay] = tool; + return tool; } @@ -1228,22 +1221,6 @@ SelectionDisplay = (function() { z: 0 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1305,23 +1282,6 @@ SelectionDisplay = (function() { z: 90 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1385,22 +1345,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1460,22 +1404,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1531,7 +1459,7 @@ SelectionDisplay = (function() { isPointLight = properties.type == "Light" && !properties.isSpotlight; } - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") { + if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") { rotationOverlaysVisible = true; rotateHandlesVisible = false; translateHandlesVisible = false; @@ -2401,19 +2329,19 @@ SelectionDisplay = (function() { var duplicatedEntityIDs = null; // TOOL DEFINITION: TRANSLATE XZ TOOL - var translateXZTool = { + var translateXZTool = addGrabberTool(selectionBox,{ mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event,isAltFromGrab,intersectInfo) { + onBegin: function(event, pickRay, pickResult, doClone) { var wantDebug = false; if(wantDebug){ print("================== TRANSLATE_XZ(Beg) -> ======================="); - Vec3.print(" intersectInfo.queryRay", intersectInfo.queryRay); - Vec3.print(" intersectInfo.queryRay.origin", intersectInfo.queryRay.origin); - Vec3.print(" intersectInfo.results.intersection", intersectInfo.results.intersection); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); } SelectionManager.saveProperties(); @@ -2421,19 +2349,19 @@ SelectionDisplay = (function() { that.setGrabberMoveUpVisible( false ); startPosition = SelectionManager.worldPosition; - mode = translateXZTool.mode; + mode = translateXZTool.mode; // Note this overrides mode = "CLONE" - translateXZTool.pickPlanePosition = intersectInfo.results.intersection; + translateXZTool.pickPlanePosition = pickResult.intersection; translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); - translateXZTool.startingDistance = Vec3.distance(intersectInfo.queryRay.origin, SelectionManager.position); - translateXZTool.startingElevation = translateXZTool.elevation(intersectInfo.queryRay.origin, translateXZTool.pickPlanePosition); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); if (wantDebug) { print(" longest dimension: " + translateXZTool.greatestDimension); print(" starting distance: " + translateXZTool.startingDistance); print(" starting elevation: " + translateXZTool.startingElevation); } - initialXZPick = rayPlaneIntersection(intersectInfo.queryRay, translateXZTool.pickPlanePosition, { + initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, z: 0 @@ -2442,7 +2370,7 @@ SelectionDisplay = (function() { // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not // the new ones. - if (event.isAlt || isAltFromGrab) { + if (event.isAlt || doClone) { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; @@ -2614,16 +2542,14 @@ SelectionDisplay = (function() { SelectionManager._update(); } - }; - + }); + // GRABBER TOOL: GRABBER MOVE UP var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", - onBegin: function(event, intersectResult) { - pickRay = generalComputePickRay(event.x, event.y); - + onBegin: function(event, pickRay, pickResult) { upDownPickNormal = Quat.getForward(lastCameraOrientation); // Remove y component so the y-axis lies along the plane we picking on - this will // give movements that follow the mouse. @@ -2696,23 +2622,9 @@ SelectionDisplay = (function() { // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", - onBegin: function(event, intersectResult) { - - var pickRay = generalComputePickRay(event.x, event.y); - //TODO_Case6491: This may be doing duplicate works that's handled - // within translateXZTool.onBegin. Verify and if so - // remove... - var result = Overlays.findRayIntersection(pickRay); - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - - var intersectInfo = { - queryRay: pickRay, - results: intersectResult - }; - - translateXZTool.onBegin(event,true,intersectInfo); + onBegin: function(event, pickRay, pickResult) { + var doClone = true; + translateXZTool.onBegin(event,pickRay,pickResult,doClone); }, elevation: function (event) { translateXZTool.elevation(event); @@ -2785,7 +2697,7 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; - var onBegin = function(event, intersectResult) { + var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); @@ -3167,7 +3079,7 @@ SelectionDisplay = (function() { } var tool = makeStretchTool(mode, direction, pivot, offset, handleMove); - addGrabberTool(overlay, tool); + return addGrabberTool(overlay, tool); } // FUNCTION: CUTOFF STRETCH FUNC @@ -3690,7 +3602,7 @@ SelectionDisplay = (function() { } } - function helperRotationHandleOnBegin( event, rotNormal, rotCenter, handleRotation ) { + function helperRotationHandleOnBegin( event, pickRay, rotAroundAxis, rotCenter, handleRotation ) { var wantDebug = false; if (wantDebug) { print("================== " + mode + "(rotation helper onBegin) -> ======================="); @@ -3702,7 +3614,8 @@ SelectionDisplay = (function() { that.setGrabberMoveUpVisible( false ); initialPosition = SelectionManager.worldPosition; - rotationNormal = rotNormal; + rotationNormal = { x: 0, y: 0, z: 0 }; + rotationNormal[rotAroundAxis] = 1; // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -3755,11 +3668,11 @@ SelectionDisplay = (function() { updateRotationDegreesOverlay(0, handleRotation, rotCenter); - // Compute zero position now that the overlay is set up. - // TODO editOverlays sync - var pickRay = generalComputePickRay(event.x, event.y); - + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. var result = rayPlaneIntersection( pickRay, rotCenter, rotationNormal ); + // In case of a parallel ray, this will be null, which will cause early-out + // in the onMove helper. rotZero = result; if (wantDebug) { @@ -3776,7 +3689,7 @@ SelectionDisplay = (function() { return; } - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("================== "+ mode + "(rotation helper onMove) -> ======================="); Vec3.print(" rotZero: ", rotZero); @@ -3789,15 +3702,15 @@ SelectionDisplay = (function() { visible: false }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - if (result.intersects) { + var result = rayPlaneIntersection( pickRay, rotCenter, rotationNormal ); + if (result) { var centerToZero = Vec3.subtract(rotZero, rotCenter); - var centerToIntersect = Vec3.subtract(result.intersection, rotCenter); + var centerToIntersect = Vec3.subtract(result, rotCenter); if (wantDebug) { Vec3.print(" RotationNormal: ", rotationNormal); Vec3.print(" rotZero: ", rotZero); Vec3.print(" rotCenter: ", rotCenter); - Vec3.print(" intersect: ", result.intersection); + Vec3.print(" intersect: ", result); Vec3.print(" centerToZero: ", centerToZero); Vec3.print(" centerToIntersect: ", centerToIntersect); } @@ -3805,7 +3718,7 @@ SelectionDisplay = (function() { // handles that internally, so it's to pass unnormalized vectors here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var distanceFromCenter = Vec3.distance(rotCenter, result.intersection); + var distanceFromCenter = Vec3.length(centerToIntersect); var snapToInner = distanceFromCenter < innerRadius; var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; @@ -3902,9 +3815,8 @@ SelectionDisplay = (function() { var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", - onBegin: function(event, intersectResult) { - mode = "ROTATE_YAW"; - helperRotationHandleOnBegin( event, yawNormal, yawCenter, yawHandleRotation ); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin( event, pickRay, "y", yawCenter, yawHandleRotation ); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); @@ -3918,9 +3830,8 @@ SelectionDisplay = (function() { // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function (event, intersectResult) { - mode = "ROTATE_PITCH"; - helperRotationHandleOnBegin( event, pitchNormal, pitchCenter, pitchHandleRotation ); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin( event, pickRay, "x", pitchCenter, pitchHandleRotation ); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); @@ -3934,9 +3845,8 @@ SelectionDisplay = (function() { // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function (event, intersectResult) { - mode = "ROTATE_ROLL"; - helperRotationHandleOnBegin( event, rollNormal, rollCenter, rollHandleRotation ); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin( event, pickRay, "z", rollCenter, rollHandleRotation ); }, onEnd: function (event, reason) { helperRotationHandleOnEnd(); @@ -4027,7 +3937,9 @@ SelectionDisplay = (function() { } } + // Start with unknown mode, in case no tool can handle this. mode = "UNKNOWN"; + var results = testRayIntersect( pickRay, interactiveOverlays ); if ( results.intersects ){ var hitOverlayID = results.overlayID; @@ -4038,32 +3950,19 @@ SelectionDisplay = (function() { entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); //TODO_Case6491: Merge if..else( selectionBox ) when onBegin of transXZ is normalized. - if ( hitOverlayID == selectionBox ) { - activeTool = translateXZTool; - if(wantDebug){ - print(" Clicked selectionBox, Activating Tool: " + activeTool.mode ); - } - var intersectInfo = { - queryRay: pickRay, - results: results - }; - - activeTool.onBegin(event, null, intersectInfo); - } else { //...see if a tool was hit as that's the only thing left that we care about. - var hitTool = grabberTools[ hitOverlayID ]; - if ( hitTool ) { - activeTool = hitTool; - mode = activeTool.mode; //< TODO: Is this centrally handled in all tool begins? - if (activeTool.onBegin) { - activeTool.onBegin(event, results); - } else { - print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool( " + activeTool.mode + " ) missing onBegin"); - } + var hitTool = grabberTools[ hitOverlayID ]; + if ( hitTool ) { + activeTool = hitTool; + mode = activeTool.mode; + if (activeTool.onBegin) { + activeTool.onBegin(event, pickRay, results); } else { - print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); - }//--End_if( hitTool ) - }//--End_if( hitOverlayID == selectionBox ) + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool( " + activeTool.mode + " ) missing onBegin"); + } + } else { + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }//--End_if( hitTool ) }//--End_If( results.intersects ) if (wantDebug) { From 0b8616950091f35f722cdf7eaa4d687f5a434e17 Mon Sep 17 00:00:00 2001 From: Leander Hasty <1p-cusack@1stplayable.com> Date: Thu, 10 Aug 2017 15:20:58 -0400 Subject: [PATCH 383/504] [Case 6491] remove rotateOverlayTarget. It is no longer used. Reviewed-by: LaShonda Hopper --- .../system/libraries/entitySelectionTool.js | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 9fd5bfcdee..0501b2842e 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -288,7 +288,6 @@ SelectionDisplay = (function() { }; var handleHoverAlpha = 1.0; - var rotateOverlayTargetSize = 10000; // really big target var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool var innerRadius; var outerRadius; @@ -805,24 +804,6 @@ SelectionDisplay = (function() { }); - var rotateOverlayTarget = Overlays.addOverlay("circle3d", { - position: { - x: 0, - y: 0, - z: 0 - }, - size: rotateOverlayTargetSize, - color: { - red: 0, - green: 0, - blue: 0 - }, - alpha: 0.0, - solid: true, - visible: false, - rotation: yawOverlayRotation, - }); - var rotateOverlayInner = Overlays.addOverlay("circle3d", { position: { x: 0, @@ -979,7 +960,6 @@ SelectionDisplay = (function() { yawHandle, pitchHandle, rollHandle, - rotateOverlayTarget, rotateOverlayInner, rotateOverlayOuter, rotateOverlayCurrent, @@ -1034,7 +1014,6 @@ SelectionDisplay = (function() { overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; - overlayNames[rotateOverlayTarget] = "rotateOverlayTarget"; overlayNames[rotateOverlayInner] = "rotateOverlayInner"; overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; @@ -1472,9 +1451,6 @@ SelectionDisplay = (function() { translateHandlesVisible = false; } - Overlays.editOverlay(rotateOverlayTarget, { - visible: rotationOverlaysVisible - }); Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); @@ -3656,12 +3632,6 @@ SelectionDisplay = (function() { innerRadius: 0.9, }); - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: handleRotation, - position: rotCenter - }); - Overlays.editOverlay(rotationDegreesDisplay, { visible: true, }); @@ -4186,9 +4156,6 @@ SelectionDisplay = (function() { if(wantDebug){ print(" Triggering hide of RotateOverlays"); } - Overlays.editOverlay(rotateOverlayTarget, { - visible: false - }); Overlays.editOverlay(rotateOverlayInner, { visible: false }); From 45c4a1081b8c8fdaf8a0b4fd1a3913058db37ce4 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Fri, 11 Aug 2017 11:27:32 -0400 Subject: [PATCH 384/504] [Case 6491] entityToolSelection logging/print review (details below). * Added some wantDebug guards to print sections without them. * Normalized error logging statements to have script name and/or specific script function where reasonable. * All error statements contain at least the script name and a descriptive snippet of the error or clue to resolve it. * Removed some stale todos. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 105 ++++++++++++------ 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 0501b2842e..d1f480e55a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -45,11 +45,12 @@ SelectionManager = (function() { return; } + var wantDebug = false; var messageParsed; try { messageParsed = JSON.parse(message); } catch (err) { - print("error -- entitySelectionTool got malformed message: " + message); + print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message); } // if (message === 'callUpdate') { @@ -57,7 +58,9 @@ SelectionManager = (function() { // } if (messageParsed.method === "selectEntity") { - print("setting selection to " + messageParsed.entityID); + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } that.setSelections([messageParsed.entityID]); } } @@ -225,7 +228,7 @@ SelectionManager = (function() { try { listeners[j](selectionUpdated === true); } catch (e) { - print("EntitySelectionTool got exception: " + JSON.stringify(e)); + print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } } }; @@ -1477,29 +1480,46 @@ SelectionDisplay = (function() { // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { - print("======> SetSpaceMode called. ========"); + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + if (spaceMode != newSpaceMode) { - print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + if (wantDebug){ + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } spaceMode = newSpaceMode; that.updateHandles(); - } else { - print(" Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } else if(wantDebug){ + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } + if(wantDebug) { + print("====== SetSpaceMode called. <========"); } - print("====== SetSpaceMode called. <========"); }; // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { - print("========> ToggleSpaceMode called. ========="); + var wantDebug = false; + if (wantDebug){ + print("========> ToggleSpaceMode called. ========="); + } if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { - print("Local space editing is not available with multiple selections"); + if (wantDebug){ + print("Local space editing is not available with multiple selections"); + } return; } - print( "PreToggle: " + spaceMode); + if(wantDebug) { + print( "PreToggle: " + spaceMode); + } spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; - print( "PostToggle: " + spaceMode); that.updateHandles(); - print("======== ToggleSpaceMode called. <========="); + if (wantDebug){ + print( "PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } }; // FUNCTION: UNSELECT ALL @@ -1508,9 +1528,12 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { - print( "======> Update Handles =======" ); - print( " Selections Count: " + SelectionManager.selections.length ); - print( " SpaceMode: " + spaceMode ); + var wantDebug = false; + if(wantDebug){ + print( "======> Update Handles =======" ); + print( " Selections Count: " + SelectionManager.selections.length ); + print( " SpaceMode: " + spaceMode ); + } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; @@ -2252,7 +2275,9 @@ SelectionDisplay = (function() { rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), }); - print( "====== Update Handles <=======" ); + if(wantDebug){ + print( "====== Update Handles <=======" ); + } }; @@ -2527,7 +2552,7 @@ SelectionDisplay = (function() { mode: "TRANSLATE_UP_DOWN", onBegin: function(event, pickRay, pickResult) { upDownPickNormal = Quat.getForward(lastCameraOrientation); - // Remove y component so the y-axis lies along the plane we picking on - this will + // Remove y component so the y-axis lies along the plane we're picking on - this will // give movements that follow the mouse. upDownPickNormal.y = 0; lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); @@ -3061,7 +3086,10 @@ SelectionDisplay = (function() { // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; - Vec3.print("Radius stretch: ", vector); + var wantDebug = false; + if (wantDebug){ + Vec3.print("Radius stretch: ", vector); + } var length = vector.x + vector.y + vector.z; var props = selectionManager.savedProperties[selectionManager.selections[0]]; @@ -3515,21 +3543,27 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { - print( "---> updateRotationDegreesOverlay ---" ); - print(" AngleFromZero: " + angleFromZero ); - print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z ); - print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z ); + var wantDebug = false; + if(wantDebug){ + print( "---> updateRotationDegreesOverlay ---" ); + print(" AngleFromZero: " + angleFromZero ); + print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z ); + print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z ); + } + var angle = angleFromZero * (Math.PI / 180); var position = { x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, z: 0, }; - print(" Angle: " + angle ); - print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); + if(wantDebug){ + print(" Angle: " + angle ); + print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); + } + position = Vec3.multiplyQbyV(handleRotation, position); position = Vec3.sum(centerPosition, position); - print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); var overlayProps = { position: position, dimensions: { @@ -3539,18 +3573,24 @@ SelectionDisplay = (function() { lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(angleFromZero) + "°", }; - print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z ); - print(" OverlayLineHeight: " + overlayProps.lineHeight ); - print(" OverlayText: " + overlayProps.text ); + if(wantDebug){ + print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); + print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z ); + print(" OverlayLineHeight: " + overlayProps.lineHeight ); + print(" OverlayText: " + overlayProps.text ); + } + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); - print( "<--- updateRotationDegreesOverlay ---" ); + if (wantDebug){ + print( "<--- updateRotationDegreesOverlay ---" ); + } } // FUNCTION DEF: updateSelectionsRotation // Helper func used by rotation grabber tools function updateSelectionsRotation( rotationChange ) { if ( ! rotationChange ) { - print("ERROR( updateSelectionsRotation ) - Invalid arg specified!!"); + print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); //--EARLY EXIT-- return; @@ -3653,7 +3693,7 @@ SelectionDisplay = (function() { function helperRotationHandleOnMove( event, rotAroundAxis, rotCenter, handleRotation ) { if ( ! rotZero ) { - print("ERROR( handleRotationHandleOnMove ) - Invalid RotationZero Specified (missed rotation target plane?)" ); + print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)" ); //--EARLY EXIT-- return; @@ -3919,7 +3959,6 @@ SelectionDisplay = (function() { } entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); - //TODO_Case6491: Merge if..else( selectionBox ) when onBegin of transXZ is normalized. var hitTool = grabberTools[ hitOverlayID ]; if ( hitTool ) { From c286ee95a47f3a468c5f6f5783eb010a42439032 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Fri, 11 Aug 2017 14:26:33 -0400 Subject: [PATCH 385/504] [Case 6491] Zero state improvements (details below). Fixes issue where when translating the selected object(s), user view was obstructed by grabber handles. This also brings more consistency to the selection ui state between rotation and translation user interactions. Both types of interactions feel nicer with the selection ui as concise as possible during the action, limited only to that useful for the current interaction. Expected Behavior: Note the following presumes you're in creation mode. * When the selection is clicked, the rotation, stretch, clone, and translateY handles should turn invisible. * When moving a selection along either the x or z axis, the rotation, stretch, clone, and translateY handles should be invisible and remain that way until the user releases the selection. * When the selection is released, the rotation, stretch, clone, and translateY handles should become visible. * When the translateY handle is clicked, the rotation, stretch, and clone handles should turn invisible. The translateY handle should remain visible. * When moving the selection along the y axis, the rotation, stretch, and clone handles should be invisible and remain that way until the user releases the selection. The translateY handle should be visible during the entire interaction. * When the selection is released, the rotation, stretch, clone should become visible. The translateY handle, visible the entire time, should already be visible. * When the user click somewhere other than a selection, entity object, creation menu or tools, then any current selection should be unselected and all creation ui local to it should turn invisible. * When the user exits creation mode, any current selection should be unselected and all creation ui local to it should turn invisible. TODO: * We may want to visit the scaling interaction ui as well; however, that could be a later task as it's tangential. The changes here are a good first step. * The idea with that interaction is perhaps only showing the handle for the current scaling operation. Currently when scaling all of the other handles remain visible, though their appearance does update. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 225 ++++++------------ 1 file changed, 73 insertions(+), 152 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index d1f480e55a..b1a42bf1ab 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -580,6 +580,12 @@ SelectionDisplay = (function() { var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge); + var spotLightGrabberHandles = [ + grabberSpotLightCircle, grabberSpotLightCenter, grabberSpotLightRadius, + grabberSpotLightLineT, grabberSpotLightLineB, grabberSpotLightLineL, grabberSpotLightLineR, + grabberSpotLightT, grabberSpotLightB, grabberSpotLightL, grabberSpotLightR + ]; + var grabberPointLightCircleX = Overlays.addOverlay("circle3d", { rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), color: lightOverlayColor, @@ -605,6 +611,12 @@ SelectionDisplay = (function() { var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var pointLightGrabberHandles = [ + grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, + grabberPointLightT, grabberPointLightB, grabberPointLightL, + grabberPointLightR, grabberPointLightF, grabberPointLightN, + ]; + var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); var stretchHandles = [ @@ -1432,10 +1444,12 @@ SelectionDisplay = (function() { var rotateHandlesVisible = true; var rotationOverlaysVisible = false; - var translateHandlesVisible = true; - var selectionBoxVisible = true; + //note: Commented out as these are currently unused here; however, + // leaving them around as they document intent of state as it + // relates to modes that may be useful later. + //var translateHandlesVisible = true; + //var selectionBoxVisible = true; var isPointLight = false; - if (selectionManager.selections.length == 1) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); isPointLight = properties.type == "Light" && !properties.isSpotlight; @@ -1444,14 +1458,14 @@ SelectionDisplay = (function() { if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") { rotationOverlaysVisible = true; rotateHandlesVisible = false; - translateHandlesVisible = false; - selectionBoxVisible = false; + //translateHandlesVisible = false; + //selectionBoxVisible = false; } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) { rotateHandlesVisible = false; } else if (mode != "UNKNOWN") { // every other mode is a stretch mode... rotateHandlesVisible = false; - translateHandlesVisible = false; + //translateHandlesVisible = false; } Overlays.editOverlay(rotateZeroOverlay, { @@ -1533,6 +1547,7 @@ SelectionDisplay = (function() { print( "======> Update Handles =======" ); print( " Selections Count: " + SelectionManager.selections.length ); print( " SpaceMode: " + spaceMode ); + print( " DisplayMode: " + mode ); } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); @@ -1768,22 +1783,28 @@ SelectionDisplay = (function() { EdgeFL = Vec3.sum(position, EdgeFL); var inModeRotate = (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"); - var stretchHandlesVisible = !inModeRotate && (spaceMode == SPACE_LOCAL); + var inModeTranslate = (mode == "TRANSLATE_XZ" || mode == "CLONE" || mode == "TRANSLATE_UP_DOWN"); + var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode == SPACE_LOCAL); var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); - var cloneHandleVisible = !inModeRotate; - //print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); + var cloneHandleVisible = !(inModeRotate || inModeTranslate); + if(wantDebug){ + print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); + } var isSingleSelection = (selectionManager.selections.length == 1); if (isSingleSelection) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); var isLightSelection = (properties.type == "Light"); if ( isLightSelection ) { - //print(" Light Selection revoking Non-Light Grabbers Visibility!"); + if(wantDebug){ + print(" Light Selection revoking Non-Light Grabbers Visibility!"); + } stretchHandlesVisible = false; extendedStretchHandlesVisible = false; cloneHandleVisible = false; if(properties.isSpotlight) { - //print(" Trying to show all SpotLight related grabbers"); + that.setPointLightHandlesVisible( false ); + Overlays.editOverlay(grabberSpotLightCenter, { position: position, visible: false, @@ -1848,36 +1869,9 @@ SelectionDisplay = (function() { visible: true, }); - //print(" Trying to hide all PointLight related grabbers"); - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); } else { //..it's a PointLight - //print(" Trying to show all PointLight related grabbers"); + that.setSpotLightHandlesVisible( false ); + Overlays.editOverlay(grabberPointLightT, { position: TOP, rotation: rotation, @@ -1938,102 +1932,10 @@ SelectionDisplay = (function() { }, visible: true, }); - - //print(" Trying to hide all SpotLight related grabbers"); - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); } } else { //..it's not a light at all - //print(" Trying to hide all Light related grabbers"); - Overlays.editOverlay(grabberSpotLightCenter, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); + that.setSpotLightHandlesVisible( false ); + that.setPointLightHandlesVisible( false ); } }//--end of isSingleSelection @@ -2281,20 +2183,18 @@ SelectionDisplay = (function() { }; + function helperSetOverlaysVisibility( handleArray, isVisible ){ + var numHandles = handleArray.length; + var visibilityUpdate = { visible: isVisible }; + for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { + Overlays.editOverlay(handleArray[ handleIndex ], visibilityUpdate); + } + } + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { - var length = allOverlays.length; - for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { - Overlays.editOverlay(allOverlays[overlayIndex], { - visible: isVisible - }); - } - length = selectionBoxes.length; - for (var boxIndex = 0; boxIndex < length; boxIndex++) { - Overlays.editOverlay(selectionBoxes[boxIndex], { - visible: isVisible - }); - } + helperSetOverlaysVisibility( allOverlays, isVisible ); + helperSetOverlaysVisibility( selectionBoxes, isVisible ); }; // FUNCTION: SET ROTATION HANDLES VISIBLE @@ -2307,11 +2207,7 @@ SelectionDisplay = (function() { // FUNCTION: SET STRETCH HANDLES VISIBLE that.setStretchHandlesVisible = function(isVisible) { - var numHandles = stretchHandles.length; - var visibilityUpdate = { visible: isVisible }; - for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { - Overlays.editOverlay(stretchHandles[ handleIndex ], visibilityUpdate); - } + helperSetOverlaysVisibility( stretchHandles, isVisible ); }; // FUNCTION: SET GRABBER MOVE UP VISIBLE @@ -2319,6 +2215,29 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberMoveUp, { visible: isVisible }); }; + // FUNCTION: SET GRABBER TOOLS UP VISIBLE + that.setGrabberToolsVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + for ( var toolKey in grabberTools ) { + if ( ! grabberTools.hasOwnProperty( toolKey ) ){ + //--EARLY ITERATION EXIT--( On to the next one ) + continue; + } + + Overlays.editOverlay( toolKey, visibilityUpdate ); + } + }; + + // FUNCTION: SET POINT LIGHT HANDLES VISIBLE + that.setPointLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility( pointLightGrabberHandles, isVisible ); + }; + + // FUNCTION: SET SPOT LIGHT HANDLES VISIBLE + that.setSpotLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility( spotLightGrabberHandles, isVisible ); + }; + // FUNCTION: UNSELECT // TODO?: Needs implementation that.unselect = function(entityID) {}; @@ -2347,6 +2266,7 @@ SelectionDisplay = (function() { SelectionManager.saveProperties(); that.setRotationHandlesVisible( false ); + that.setStretchHandlesVisible( false ); that.setGrabberMoveUpVisible( false ); startPosition = SelectionManager.worldPosition; @@ -2559,6 +2479,7 @@ SelectionDisplay = (function() { SelectionManager.saveProperties(); that.setGrabberMoveUpVisible( true ); + that.setStretchHandlesVisible( false ); that.setRotationHandlesVisible( false ); // Duplicate entities if alt is pressed. This will make a @@ -3980,7 +3901,7 @@ SelectionDisplay = (function() { } // If mode is known then we successfully handled this; - // otherwise, we're missing a tool or a tool.onBegin. + // otherwise, we're missing a tool. return (mode != "UNKNOWN"); }; From 9279290b2ea6f74c11815efa4eb19941a16e2905 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Fri, 11 Aug 2017 15:10:25 -0400 Subject: [PATCH 386/504] [Case 6491] Remove dupe pickRay var (details below). Ran lint pass and found the dupe define. As noted in a previous commit, the only remaining lint issues are as follows: * scripts/system/libraries/entitySelectionTool.js: line 17, col 1, Read only. * HIFI_PUBLIC_BUCKET assignment * scripts/system/libraries/entitySelectionTool.js: line 19, col 1, Read only. * SPACE_WORLD assignment * scripts/system/libraries/entitySelectionTool.js: line 30, col 1, Read only. * SelectionManager assignment Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b1a42bf1ab..259ac6bd80 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2770,7 +2770,6 @@ SelectionDisplay = (function() { } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); From 3a174780c2b3809e3112551d320de94efafb2798 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Fri, 11 Aug 2017 15:23:43 -0400 Subject: [PATCH 387/504] [Case 6491] Minor: Move that.updateHandleSizes up (details below). It was down below the amidst the mouse event handlers. This just moves it up with the other that.update handle functions definitions. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 259ac6bd80..ccc2e0f47d 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1492,6 +1492,45 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: UPDATE HANDLE SIZES + that.updateHandleSizes = function() { + if (selectionManager.hasSelection()) { + var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; + var dimensions = SelectionManager.worldDimensions; + var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; + grabberSize = Math.min(grabberSize, avgDimension / 10); + + for (var i = 0; i < stretchHandles.length; i++) { + Overlays.editOverlay(stretchHandles[i], { + size: grabberSize, + }); + } + var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; + handleSize = Math.min(handleSize, avgDimension / 3); + + Overlays.editOverlay(yawHandle, { + scale: handleSize, + }); + Overlays.editOverlay(pitchHandle, { + scale: handleSize, + }); + Overlays.editOverlay(rollHandle, { + scale: handleSize, + }); + var pos = Vec3.sum(grabberMoveUpPosition, { + x: 0, + y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, + z: 0 + }); + Overlays.editOverlay(grabberMoveUp, { + position: pos, + scale: handleSize / 1.25, + }); + } + }; + Script.update.connect(that.updateHandleSizes); + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { var wantDebug = false; @@ -4053,45 +4092,6 @@ SelectionDisplay = (function() { return false; }; - // FUNCTION: UPDATE HANDLE SIZES - that.updateHandleSizes = function() { - if (selectionManager.hasSelection()) { - var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; - var dimensions = SelectionManager.worldDimensions; - var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; - grabberSize = Math.min(grabberSize, avgDimension / 10); - - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - size: grabberSize, - }); - } - var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; - handleSize = Math.min(handleSize, avgDimension / 3); - - Overlays.editOverlay(yawHandle, { - scale: handleSize, - }); - Overlays.editOverlay(pitchHandle, { - scale: handleSize, - }); - Overlays.editOverlay(rollHandle, { - scale: handleSize, - }); - var pos = Vec3.sum(grabberMoveUpPosition, { - x: 0, - y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, - z: 0 - }); - Overlays.editOverlay(grabberMoveUp, { - position: pos, - scale: handleSize / 1.25, - }); - } - }; - Script.update.connect(that.updateHandleSizes); - // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { var wantDebug = false; From 9fb76340c70f52d4e1af2116abd73e25d69a6232 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Fri, 11 Aug 2017 19:17:00 -0400 Subject: [PATCH 388/504] [Case 6491] removes SelectionDisplay.mode (details below). This var isn't needed as the var essentially piggy backed off of activeTool and activeTool.mode. This also helps guard against the _majority_ of situations where mode check fails due to typo inserted when composing the check. Most instances of manual string checks have been replaced by querying activeTool via new isActiveTool function. For instances that still require a direct mode check getMode will return the mode string associated with the current activeTool. TODO: Get this code reviewed. --- .../system/libraries/entitySelectionTool.js | 74 +++++++++++++------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index ccc2e0f47d..ec004c9ca4 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -275,7 +275,6 @@ SelectionDisplay = (function() { var showExtendedStretchHandles = false; var spaceMode = SPACE_LOCAL; - var mode = "UNKNOWN"; var overlayNames = []; var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); @@ -1088,6 +1087,35 @@ SelectionDisplay = (function() { return tool; } + // toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + var wantDebug = true; + if ( ! activeTool || ! toolHandle ){ + if(wantDebug){ + if ( activeTool ){ + print("WARNING: entitySelectionTool.isActiveTool - Encountered invalid toolHandle: " + toolHandle ); + } + } + return false; + } + if ( ! grabberTools.hasOwnProperty( toolHandle ) ) { + if(wantDebug){ + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle ); + } + return false; + } + + return (activeTool === grabberTools[ toolHandle ]); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); + } + that.cleanup = function() { for (var i = 0; i < allOverlays.length; i++) { @@ -1455,14 +1483,15 @@ SelectionDisplay = (function() { isPointLight = properties.type == "Light" && !properties.isSpotlight; } - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") { + if ( isActiveTool(yawHandle) || isActiveTool(pitchHandle) || + isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { rotationOverlaysVisible = true; rotateHandlesVisible = false; //translateHandlesVisible = false; //selectionBoxVisible = false; - } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) { + } else if ( isActiveTool(grabberMoveUp) || isPointLight) { rotateHandlesVisible = false; - } else if (mode != "UNKNOWN") { + } else if ( activeTool ) { // every other mode is a stretch mode... rotateHandlesVisible = false; //translateHandlesVisible = false; @@ -1581,12 +1610,12 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { - var wantDebug = false; + var wantDebug = true; if(wantDebug){ print( "======> Update Handles =======" ); print( " Selections Count: " + SelectionManager.selections.length ); print( " SpaceMode: " + spaceMode ); - print( " DisplayMode: " + mode ); + print( " DisplayMode: " + getMode() ); } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); @@ -1821,8 +1850,8 @@ SelectionDisplay = (function() { EdgeFR = Vec3.sum(position, EdgeFR); EdgeFL = Vec3.sum(position, EdgeFL); - var inModeRotate = (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"); - var inModeTranslate = (mode == "TRANSLATE_XZ" || mode == "CLONE" || mode == "TRANSLATE_UP_DOWN"); + var inModeRotate = (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)); + var inModeTranslate = (isActiveTool(selectionBox) || isActiveTool(grabberCloner) || isActiveTool(grabberMoveUp)); var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode == SPACE_LOCAL); var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); var cloneHandleVisible = !(inModeRotate || inModeTranslate); @@ -2198,7 +2227,7 @@ SelectionDisplay = (function() { z: position.z }; Overlays.editOverlay(grabberMoveUp, { - visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") + visible: ( ! activeTool ) || isActiveTool(grabberMoveUp) }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -2309,7 +2338,6 @@ SelectionDisplay = (function() { that.setGrabberMoveUpVisible( false ); startPosition = SelectionManager.worldPosition; - mode = translateXZTool.mode; // Note this overrides mode = "CLONE" translateXZTool.pickPlanePosition = pickResult.intersection; translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); @@ -2843,7 +2871,7 @@ SelectionDisplay = (function() { }; var onMove = function(event) { - var proportional = spaceMode == SPACE_WORLD || event.isShifted || activeTool.mode == "STRETCH_RADIUS"; + var proportional = spaceMode == SPACE_WORLD || event.isShifted || isActiveTool(grabberSpotLightRadius); var position, dimensions, rotation; if (spaceMode == SPACE_LOCAL) { @@ -3580,7 +3608,7 @@ SelectionDisplay = (function() { function helperRotationHandleOnBegin( event, pickRay, rotAroundAxis, rotCenter, handleRotation ) { var wantDebug = false; if (wantDebug) { - print("================== " + mode + "(rotation helper onBegin) -> ======================="); + print("================== " + getMode() + "(rotation helper onBegin) -> ======================="); } SelectionManager.saveProperties(); @@ -3645,7 +3673,7 @@ SelectionDisplay = (function() { rotZero = result; if (wantDebug) { - print("================== " + mode + "(rotation helper onBegin) <- ======================="); + print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); } }//--End_Function( helperRotationHandleOnBegin ) @@ -3660,7 +3688,7 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { - print("================== "+ mode + "(rotation helper onMove) -> ======================="); + print("================== "+ getMode() + "(rotation helper onMove) -> ======================="); Vec3.print(" rotZero: ", rotZero); } var pickRay = generalComputePickRay(event.x, event.y); @@ -3750,14 +3778,14 @@ SelectionDisplay = (function() { }//--End_If( results.intersects ) if (wantDebug) { - print("================== "+ mode + "(rotation helper onMove) <- ======================="); + print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); } }//--End_Function( helperRotationHandleOnMove ) function helperRotationHandleOnEnd() { var wantDebug = false; if (wantDebug) { - print("================== " + mode + "(onEnd) -> ======================="); + print("================== " + getMode() + "(onEnd) -> ======================="); } Overlays.editOverlay(rotateOverlayInner, { visible: false @@ -3775,7 +3803,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); if (wantDebug) { - print("================== " + mode + "(onEnd) <- ======================="); + print("================== " + getMode() + "(onEnd) <- ======================="); } }//--End_Function( helperRotationHandleOnEnd ) @@ -3907,7 +3935,7 @@ SelectionDisplay = (function() { } // Start with unknown mode, in case no tool can handle this. - mode = "UNKNOWN"; + activeTool = null; var results = testRayIntersect( pickRay, interactiveOverlays ); if ( results.intersects ){ @@ -3922,7 +3950,6 @@ SelectionDisplay = (function() { var hitTool = grabberTools[ hitOverlayID ]; if ( hitTool ) { activeTool = hitTool; - mode = activeTool.mode; if (activeTool.onBegin) { activeTool.onBegin(event, pickRay, results); } else { @@ -3934,13 +3961,13 @@ SelectionDisplay = (function() { }//--End_If( results.intersects ) if (wantDebug) { - print(" SelectionDisplay.mode: " + mode ); + print(" DisplayMode: " + getMode()); print("=============== eST::MousePressEvent END ======================="); } // If mode is known then we successfully handled this; // otherwise, we're missing a tool. - return (mode != "UNKNOWN"); + return activeTool; }; // FUNCTION: MOUSE MOVE EVENT @@ -4111,7 +4138,7 @@ SelectionDisplay = (function() { } // hide our rotation overlays..., and show our handles - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { + if ( isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { if(wantDebug){ print(" Triggering hide of RotateOverlays"); } @@ -4127,8 +4154,7 @@ SelectionDisplay = (function() { } - showHandles = (mode != "UNKNOWN");// Date: Wed, 16 Aug 2017 18:15:36 -0400 Subject: [PATCH 389/504] [Case 6491] Fixes consistency issue with lights (details below). Fixes issue with light selection actions having inconsistent ui in comparison to other selections from an earlier commit change. As of this commit: * When translating point lights, the edge grabbers are no longer be visible. * When rotating or translating spot lights, the edge grabbers are no longer visible. Note: * For both point & spot lights, when translating and/or rotating, their circle and/or radial guides should remain visible. This commit shouldn't have any influence on that behavior. Tested: * Rotating and translating spot lights. * Translating point lights. Didn't test rotation as only spot lights support rotation. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index ec004c9ca4..49fa11813a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1091,7 +1091,7 @@ SelectionDisplay = (function() { // that correlates to the tool you wish to query. // @return: bool - Indicates if the activeTool is that queried. function isActiveTool(toolHandle) { - var wantDebug = true; + var wantDebug = false; if ( ! activeTool || ! toolHandle ){ if(wantDebug){ if ( activeTool ){ @@ -1610,7 +1610,7 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { - var wantDebug = true; + var wantDebug = false; if(wantDebug){ print( "======> Update Handles =======" ); print( " Selections Count: " + SelectionManager.selections.length ); @@ -1873,6 +1873,8 @@ SelectionDisplay = (function() { if(properties.isSpotlight) { that.setPointLightHandlesVisible( false ); + var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); + var showEdgeSpotGrabbers = ! (inModeTranslate || inModeRotate); Overlays.editOverlay(grabberSpotLightCenter, { position: position, visible: false, @@ -1880,29 +1882,28 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberSpotLightRadius, { position: NEAR, rotation: rotation, - visible: true, + visible: showEdgeSpotGrabbers, }); - var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); Overlays.editOverlay(grabberSpotLightL, { position: EdgeNL, rotation: rotation, - visible: true, + visible: showEdgeSpotGrabbers, }); Overlays.editOverlay(grabberSpotLightR, { position: EdgeNR, rotation: rotation, - visible: true, + visible: showEdgeSpotGrabbers, }); Overlays.editOverlay(grabberSpotLightT, { position: EdgeTN, rotation: rotation, - visible: true, + visible: showEdgeSpotGrabbers, }); Overlays.editOverlay(grabberSpotLightB, { position: EdgeBN, rotation: rotation, - visible: true, + visible: showEdgeSpotGrabbers, }); Overlays.editOverlay(grabberSpotLightCircle, { position: NEAR, @@ -1939,36 +1940,37 @@ SelectionDisplay = (function() { } else { //..it's a PointLight that.setSpotLightHandlesVisible( false ); - + + var showEdgePointGrabbers = ! inModeTranslate; Overlays.editOverlay(grabberPointLightT, { position: TOP, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightB, { position: BOTTOM, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightL, { position: LEFT, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightR, { position: RIGHT, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightF, { position: FAR, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightN, { position: NEAR, rotation: rotation, - visible: true, + visible: showEdgePointGrabbers, }); Overlays.editOverlay(grabberPointLightCircleX, { position: position, From 2bb76a357a81b25e6fa132402c530579fb88c552 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper <1p-cusack@1stplayable.com> Date: Thu, 17 Aug 2017 17:28:12 -0400 Subject: [PATCH 390/504] [Case 6491] Some adjustments to isActiveTool (details below). * isActiveTool now respects null and undefined args. * If null or undefined toolHandle is passed, activeTool is directly tested against those values. Rather than explicitly returning false. * Added some clarification to unknown tool warning message. Reviewed-by: Leander Hasty Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 49fa11813a..9f0d820f69 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1087,23 +1087,23 @@ SelectionDisplay = (function() { return tool; } - // toolHandle: The overlayID associated with the tool - // that correlates to the tool you wish to query. + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. // @return: bool - Indicates if the activeTool is that queried. function isActiveTool(toolHandle) { - var wantDebug = false; - if ( ! activeTool || ! toolHandle ){ - if(wantDebug){ - if ( activeTool ){ - print("WARNING: entitySelectionTool.isActiveTool - Encountered invalid toolHandle: " + toolHandle ); - } - } - return false; + if ( ! toolHandle ) { + // Allow isActiveTool( null ) and similar to return true if there's + // no active tool + return ( activeTool === toolHandle ); } + if ( ! grabberTools.hasOwnProperty( toolHandle ) ) { - if(wantDebug){ - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle ); - } + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool." ); + //--EARLY EXIT-- return false; } From 0deabf54ef98c2019d9114897804539be6f332d1 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 8 Sep 2017 16:31:59 -0400 Subject: [PATCH 391/504] [Case 6491] Minor: fixes some coding standard spacing with wantDebug statements. --- .../system/libraries/entitySelectionTool.js | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 9f0d820f69..6c5e3b0522 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1568,15 +1568,15 @@ SelectionDisplay = (function() { } if (spaceMode != newSpaceMode) { - if (wantDebug){ + if (wantDebug) { print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); } spaceMode = newSpaceMode; that.updateHandles(); - } else if(wantDebug){ + } else if (wantDebug) { print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); } - if(wantDebug) { + if (wantDebug) { print("====== SetSpaceMode called. <========"); } }; @@ -1584,21 +1584,21 @@ SelectionDisplay = (function() { // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { var wantDebug = false; - if (wantDebug){ + if (wantDebug) { print("========> ToggleSpaceMode called. ========="); } if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { - if (wantDebug){ + if (wantDebug) { print("Local space editing is not available with multiple selections"); } return; } - if(wantDebug) { + if (wantDebug) { print( "PreToggle: " + spaceMode); } spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; that.updateHandles(); - if (wantDebug){ + if (wantDebug) { print( "PostToggle: " + spaceMode); print("======== ToggleSpaceMode called. <========="); } @@ -1611,7 +1611,7 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { var wantDebug = false; - if(wantDebug){ + if (wantDebug) { print( "======> Update Handles =======" ); print( " Selections Count: " + SelectionManager.selections.length ); print( " SpaceMode: " + spaceMode ); @@ -1855,7 +1855,7 @@ SelectionDisplay = (function() { var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode == SPACE_LOCAL); var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); var cloneHandleVisible = !(inModeRotate || inModeTranslate); - if(wantDebug){ + if (wantDebug) { print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); } var isSingleSelection = (selectionManager.selections.length == 1); @@ -1864,13 +1864,13 @@ SelectionDisplay = (function() { var properties = Entities.getEntityProperties(selectionManager.selections[0]); var isLightSelection = (properties.type == "Light"); if ( isLightSelection ) { - if(wantDebug){ + if (wantDebug) { print(" Light Selection revoking Non-Light Grabbers Visibility!"); } stretchHandlesVisible = false; extendedStretchHandlesVisible = false; cloneHandleVisible = false; - if(properties.isSpotlight) { + if (properties.isSpotlight) { that.setPointLightHandlesVisible( false ); var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); @@ -2247,13 +2247,13 @@ SelectionDisplay = (function() { rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), }); - if(wantDebug){ + if (wantDebug) { print( "====== Update Handles <=======" ); } }; - function helperSetOverlaysVisibility( handleArray, isVisible ){ + function helperSetOverlaysVisibility( handleArray, isVisible ) { var numHandles = handleArray.length; var visibilityUpdate = { visible: isVisible }; for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { @@ -2289,7 +2289,7 @@ SelectionDisplay = (function() { that.setGrabberToolsVisible = function(isVisible) { var visibilityUpdate = { visible: isVisible }; for ( var toolKey in grabberTools ) { - if ( ! grabberTools.hasOwnProperty( toolKey ) ){ + if ( ! grabberTools.hasOwnProperty( toolKey ) ) { //--EARLY ITERATION EXIT--( On to the next one ) continue; } @@ -2327,7 +2327,7 @@ SelectionDisplay = (function() { startingElevation: 0.0, onBegin: function(event, pickRay, pickResult, doClone) { var wantDebug = false; - if(wantDebug){ + if (wantDebug) { print("================== TRANSLATE_XZ(Beg) -> ======================="); Vec3.print(" pickRay", pickRay); Vec3.print(" pickRay.origin", pickRay.origin); @@ -2377,7 +2377,7 @@ SelectionDisplay = (function() { } isConstrained = false; - if(wantDebug){ + if (wantDebug) { print("================== TRANSLATE_XZ(End) <- ======================="); } }, @@ -3076,7 +3076,7 @@ SelectionDisplay = (function() { function cutoffStretchFunc(vector, change) { vector = change; var wantDebug = false; - if (wantDebug){ + if (wantDebug) { Vec3.print("Radius stretch: ", vector); } var length = vector.x + vector.y + vector.z; @@ -3533,7 +3533,7 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var wantDebug = false; - if(wantDebug){ + if (wantDebug) { print( "---> updateRotationDegreesOverlay ---" ); print(" AngleFromZero: " + angleFromZero ); print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z ); @@ -3546,7 +3546,7 @@ SelectionDisplay = (function() { y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, z: 0, }; - if(wantDebug){ + if (wantDebug) { print(" Angle: " + angle ); print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); } @@ -3562,7 +3562,7 @@ SelectionDisplay = (function() { lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(angleFromZero) + "°", }; - if(wantDebug){ + if (wantDebug) { print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z ); print(" OverlayLineHeight: " + overlayProps.lineHeight ); @@ -3570,7 +3570,7 @@ SelectionDisplay = (function() { } Overlays.editOverlay(rotationDegreesDisplay, overlayProps); - if (wantDebug){ + if (wantDebug) { print( "<--- updateRotationDegreesOverlay ---" ); } } @@ -3899,10 +3899,10 @@ SelectionDisplay = (function() { var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); if (wantDebug) { - if ( ! overlayIncludes ){ + if ( ! overlayIncludes ) { print("testRayIntersect - no overlayIncludes provided."); } - if ( ! overlayExcludes ){ + if ( ! overlayExcludes ) { print("testRayIntersect - no overlayExcludes provided."); } print("testRayIntersect - Hit: " + intersectObj.intersects); @@ -3940,7 +3940,7 @@ SelectionDisplay = (function() { activeTool = null; var results = testRayIntersect( pickRay, interactiveOverlays ); - if ( results.intersects ){ + if ( results.intersects ) { var hitOverlayID = results.overlayID; if ( (hitOverlayID == HMD.tabletID) || (hitOverlayID == HMD.tabletScreenID) || (hitOverlayID == HMD.homeButtonID) ) { //--EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) @@ -3959,7 +3959,7 @@ SelectionDisplay = (function() { } } else { print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); - }//--End_if( hitTool ) + }//--End_if ( hitTool ) }//--End_If( results.intersects ) if (wantDebug) { @@ -3975,7 +3975,7 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { var wantDebug = false; - if(wantDebug){ + if (wantDebug) { print( "=============== eST::MouseMoveEvent BEG ======================="); } if (activeTool) { @@ -4115,7 +4115,7 @@ SelectionDisplay = (function() { } } - if(wantDebug){ + if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } return false; @@ -4124,24 +4124,24 @@ SelectionDisplay = (function() { // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { var wantDebug = false; - if(wantDebug){ + if (wantDebug) { print("=============== eST::MouseReleaseEvent BEG ======================="); } var showHandles = false; if (activeTool) { - if( activeTool.onEnd ) { - if(wantDebug){ + if ( activeTool.onEnd ) { + if (wantDebug) { print(" Triggering ActiveTool( " + activeTool.mode + " )'s onEnd"); } activeTool.onEnd(event); - }else if(wantDebug){ + }else if (wantDebug) { print(" ActiveTool( " + activeTool.mode + " )'s missing onEnd"); } } // hide our rotation overlays..., and show our handles if ( isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { - if(wantDebug){ + if (wantDebug) { print(" Triggering hide of RotateOverlays"); } Overlays.editOverlay(rotateOverlayInner, { @@ -4162,14 +4162,14 @@ SelectionDisplay = (function() { // if something is selected, then reset the "original" properties for any potential next click+move operation if (SelectionManager.hasSelection()) { if (showHandles) { - if(wantDebug){ + if (wantDebug) { print(" Triggering that.select"); } that.select(SelectionManager.selections[0], event); } } - if(wantDebug){ + if (wantDebug) { print("=============== eST::MouseReleaseEvent END ======================="); } }; From f427b5ba3f830c54991b511e2606ea78c685bbad Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 8 Sep 2017 16:40:22 -0400 Subject: [PATCH 392/504] Adding vscode ignores (details below). vscode ignore settings taken from from Github Global Ignores master@435c4d92 https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 8aa82865a4..072e6001da 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,15 @@ Makefile local.properties android/libraries +# VSCode +# List taken from Github Global Ignores master@435c4d92 +# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + # Xcode *.xcodeproj *.xcworkspace From ff051db79a2ed32d8629f9b087f9f39fc6223d28 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 13 Sep 2017 16:59:21 -0400 Subject: [PATCH 393/504] [Case 6491] Minor: Fixes some paren spacing. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 182 +++++++++--------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 6c5e3b0522..183fba1670 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1095,14 +1095,14 @@ SelectionDisplay = (function() { // are treated as separate values. // @return: bool - Indicates if the activeTool is that queried. function isActiveTool(toolHandle) { - if ( ! toolHandle ) { - // Allow isActiveTool( null ) and similar to return true if there's + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's // no active tool - return ( activeTool === toolHandle ); + return (activeTool === toolHandle); } - if ( ! grabberTools.hasOwnProperty( toolHandle ) ) { - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool." ); + if (!grabberTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool."); //--EARLY EXIT-- return false; } @@ -1483,15 +1483,15 @@ SelectionDisplay = (function() { isPointLight = properties.type == "Light" && !properties.isSpotlight; } - if ( isActiveTool(yawHandle) || isActiveTool(pitchHandle) || + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { rotationOverlaysVisible = true; rotateHandlesVisible = false; //translateHandlesVisible = false; //selectionBoxVisible = false; - } else if ( isActiveTool(grabberMoveUp) || isPointLight) { + } else if (isActiveTool(grabberMoveUp) || isPointLight) { rotateHandlesVisible = false; - } else if ( activeTool ) { + } else if (activeTool) { // every other mode is a stretch mode... rotateHandlesVisible = false; //translateHandlesVisible = false; @@ -1594,12 +1594,12 @@ SelectionDisplay = (function() { return; } if (wantDebug) { - print( "PreToggle: " + spaceMode); + print("PreToggle: " + spaceMode); } spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; that.updateHandles(); if (wantDebug) { - print( "PostToggle: " + spaceMode); + print("PostToggle: " + spaceMode); print("======== ToggleSpaceMode called. <========="); } }; @@ -1612,17 +1612,17 @@ SelectionDisplay = (function() { that.updateHandles = function() { var wantDebug = false; if (wantDebug) { - print( "======> Update Handles =======" ); - print( " Selections Count: " + SelectionManager.selections.length ); - print( " SpaceMode: " + spaceMode ); - print( " DisplayMode: " + getMode() ); + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; } - //print( " Triggering updateRotationHandles"); + //print(" Triggering updateRotationHandles"); that.updateRotationHandles(); var rotation, dimensions, position, registrationPoint; @@ -1863,7 +1863,7 @@ SelectionDisplay = (function() { if (isSingleSelection) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); var isLightSelection = (properties.type == "Light"); - if ( isLightSelection ) { + if (isLightSelection) { if (wantDebug) { print(" Light Selection revoking Non-Light Grabbers Visibility!"); } @@ -1871,10 +1871,10 @@ SelectionDisplay = (function() { extendedStretchHandlesVisible = false; cloneHandleVisible = false; if (properties.isSpotlight) { - that.setPointLightHandlesVisible( false ); + that.setPointLightHandlesVisible(false); var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); - var showEdgeSpotGrabbers = ! (inModeTranslate || inModeRotate); + var showEdgeSpotGrabbers = !(inModeTranslate || inModeRotate); Overlays.editOverlay(grabberSpotLightCenter, { position: position, visible: false, @@ -1939,9 +1939,9 @@ SelectionDisplay = (function() { }); } else { //..it's a PointLight - that.setSpotLightHandlesVisible( false ); + that.setSpotLightHandlesVisible(false); - var showEdgePointGrabbers = ! inModeTranslate; + var showEdgePointGrabbers = !inModeTranslate; Overlays.editOverlay(grabberPointLightT, { position: TOP, rotation: rotation, @@ -2004,8 +2004,8 @@ SelectionDisplay = (function() { }); } } else { //..it's not a light at all - that.setSpotLightHandlesVisible( false ); - that.setPointLightHandlesVisible( false ); + that.setSpotLightHandlesVisible(false); + that.setPointLightHandlesVisible(false); } }//--end of isSingleSelection @@ -2229,7 +2229,7 @@ SelectionDisplay = (function() { z: position.z }; Overlays.editOverlay(grabberMoveUp, { - visible: ( ! activeTool ) || isActiveTool(grabberMoveUp) + visible: (!activeTool) || isActiveTool(grabberMoveUp) }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -2248,12 +2248,12 @@ SelectionDisplay = (function() { }); if (wantDebug) { - print( "====== Update Handles <=======" ); + print("====== Update Handles <======="); } }; - function helperSetOverlaysVisibility( handleArray, isVisible ) { + function helperSetOverlaysVisibility(handleArray, isVisible) { var numHandles = handleArray.length; var visibilityUpdate = { visible: isVisible }; for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { @@ -2263,8 +2263,8 @@ SelectionDisplay = (function() { // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { - helperSetOverlaysVisibility( allOverlays, isVisible ); - helperSetOverlaysVisibility( selectionBoxes, isVisible ); + helperSetOverlaysVisibility(allOverlays, isVisible); + helperSetOverlaysVisibility(selectionBoxes, isVisible); }; // FUNCTION: SET ROTATION HANDLES VISIBLE @@ -2277,7 +2277,7 @@ SelectionDisplay = (function() { // FUNCTION: SET STRETCH HANDLES VISIBLE that.setStretchHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility( stretchHandles, isVisible ); + helperSetOverlaysVisibility(stretchHandles, isVisible); }; // FUNCTION: SET GRABBER MOVE UP VISIBLE @@ -2288,24 +2288,24 @@ SelectionDisplay = (function() { // FUNCTION: SET GRABBER TOOLS UP VISIBLE that.setGrabberToolsVisible = function(isVisible) { var visibilityUpdate = { visible: isVisible }; - for ( var toolKey in grabberTools ) { - if ( ! grabberTools.hasOwnProperty( toolKey ) ) { - //--EARLY ITERATION EXIT--( On to the next one ) + for (var toolKey in grabberTools) { + if (!grabberTools.hasOwnProperty(toolKey)) { + //--EARLY ITERATION EXIT--(On to the next one) continue; } - Overlays.editOverlay( toolKey, visibilityUpdate ); + Overlays.editOverlay(toolKey, visibilityUpdate); } }; // FUNCTION: SET POINT LIGHT HANDLES VISIBLE that.setPointLightHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility( pointLightGrabberHandles, isVisible ); + helperSetOverlaysVisibility(pointLightGrabberHandles, isVisible); }; // FUNCTION: SET SPOT LIGHT HANDLES VISIBLE that.setSpotLightHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility( spotLightGrabberHandles, isVisible ); + helperSetOverlaysVisibility(spotLightGrabberHandles, isVisible); }; // FUNCTION: UNSELECT @@ -2335,9 +2335,9 @@ SelectionDisplay = (function() { } SelectionManager.saveProperties(); - that.setRotationHandlesVisible( false ); - that.setStretchHandlesVisible( false ); - that.setGrabberMoveUpVisible( false ); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); startPosition = SelectionManager.worldPosition; @@ -2411,7 +2411,7 @@ SelectionDisplay = (function() { print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); } - //--EARLY EXIT--( Invalid ray detected. ) + //--EARLY EXIT--(Invalid ray detected.) return; } @@ -2429,7 +2429,7 @@ SelectionDisplay = (function() { print(" "+ translateXZTool.mode + " - too close to horizon!"); } - //--EARLY EXIT--( Don't proceed past the reached limit. ) + //--EARLY EXIT--(Don't proceed past the reached limit.) return; } @@ -2547,9 +2547,9 @@ SelectionDisplay = (function() { lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); SelectionManager.saveProperties(); - that.setGrabberMoveUpVisible( true ); - that.setStretchHandlesVisible( false ); - that.setRotationHandlesVisible( false ); + that.setGrabberMoveUpVisible(true); + that.setStretchHandlesVisible(false); + that.setRotationHandlesVisible(false); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -3534,10 +3534,10 @@ SelectionDisplay = (function() { function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var wantDebug = false; if (wantDebug) { - print( "---> updateRotationDegreesOverlay ---" ); - print(" AngleFromZero: " + angleFromZero ); - print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z ); - print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z ); + print("---> updateRotationDegreesOverlay ---"); + print(" AngleFromZero: " + angleFromZero); + print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z); + print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z); } var angle = angleFromZero * (Math.PI / 180); @@ -3547,7 +3547,7 @@ SelectionDisplay = (function() { z: 0, }; if (wantDebug) { - print(" Angle: " + angle ); + print(" Angle: " + angle); print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); } @@ -3564,21 +3564,21 @@ SelectionDisplay = (function() { }; if (wantDebug) { print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); - print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z ); - print(" OverlayLineHeight: " + overlayProps.lineHeight ); - print(" OverlayText: " + overlayProps.text ); + print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z); + print(" OverlayLineHeight: " + overlayProps.lineHeight); + print(" OverlayText: " + overlayProps.text); } Overlays.editOverlay(rotationDegreesDisplay, overlayProps); if (wantDebug) { - print( "<--- updateRotationDegreesOverlay ---" ); + print("<--- updateRotationDegreesOverlay ---"); } } // FUNCTION DEF: updateSelectionsRotation // Helper func used by rotation grabber tools - function updateSelectionsRotation( rotationChange ) { - if ( ! rotationChange ) { + function updateSelectionsRotation(rotationChange) { + if (!rotationChange) { print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); //--EARLY EXIT-- @@ -3607,16 +3607,16 @@ SelectionDisplay = (function() { } } - function helperRotationHandleOnBegin( event, pickRay, rotAroundAxis, rotCenter, handleRotation ) { + function helperRotationHandleOnBegin(event, pickRay, rotAroundAxis, rotCenter, handleRotation) { var wantDebug = false; if (wantDebug) { print("================== " + getMode() + "(rotation helper onBegin) -> ======================="); } SelectionManager.saveProperties(); - that.setRotationHandlesVisible( false ); - that.setStretchHandlesVisible( false ); - that.setGrabberMoveUpVisible( false ); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); initialPosition = SelectionManager.worldPosition; rotationNormal = { x: 0, y: 0, z: 0 }; @@ -3669,7 +3669,7 @@ SelectionDisplay = (function() { // editOverlays may not have committed rotation changes. // Compute zero position based on where the overlay will be eventually. - var result = rayPlaneIntersection( pickRay, rotCenter, rotationNormal ); + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); // In case of a parallel ray, this will be null, which will cause early-out // in the onMove helper. rotZero = result; @@ -3677,12 +3677,12 @@ SelectionDisplay = (function() { if (wantDebug) { print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); } - }//--End_Function( helperRotationHandleOnBegin ) + }//--End_Function(helperRotationHandleOnBegin) - function helperRotationHandleOnMove( event, rotAroundAxis, rotCenter, handleRotation ) { + function helperRotationHandleOnMove(event, rotAroundAxis, rotCenter, handleRotation) { - if ( ! rotZero ) { - print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)" ); + if (!rotZero) { + print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); //--EARLY EXIT-- return; @@ -3701,7 +3701,7 @@ SelectionDisplay = (function() { visible: false }); - var result = rayPlaneIntersection( pickRay, rotCenter, rotationNormal ); + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); if (result) { var centerToZero = Vec3.subtract(rotZero, rotCenter); var centerToIntersect = Vec3.subtract(result, rotCenter); @@ -3724,8 +3724,8 @@ SelectionDisplay = (function() { var vec3Degrees = { x: 0, y: 0, z: 0 }; vec3Degrees[rotAroundAxis] = angleFromZero; - var rotChange = Quat.fromVec3Degrees( vec3Degrees ); - updateSelectionsRotation( rotChange ); + var rotChange = Quat.fromVec3Degrees(vec3Degrees); + updateSelectionsRotation(rotChange); updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); @@ -3777,12 +3777,12 @@ SelectionDisplay = (function() { minorTickMarksLength: 0.1, }); } - }//--End_If( results.intersects ) + }//--End_If(results.intersects) if (wantDebug) { print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); } - }//--End_Function( helperRotationHandleOnMove ) + }//--End_Function(helperRotationHandleOnMove) function helperRotationHandleOnEnd() { var wantDebug = false; @@ -3807,7 +3807,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("================== " + getMode() + "(onEnd) <- ======================="); } - }//--End_Function( helperRotationHandleOnEnd ) + }//--End_Function(helperRotationHandleOnEnd) // YAW GRABBER TOOL DEFINITION @@ -3815,13 +3815,13 @@ SelectionDisplay = (function() { addGrabberTool(yawHandle, { mode: "ROTATE_YAW", onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin( event, pickRay, "y", yawCenter, yawHandleRotation ); + helperRotationHandleOnBegin(event, pickRay, "y", yawCenter, yawHandleRotation); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); }, onMove: function(event) { - helperRotationHandleOnMove( event, "y", yawCenter, yawHandleRotation ); + helperRotationHandleOnMove(event, "y", yawCenter, yawHandleRotation); } }); @@ -3830,13 +3830,13 @@ SelectionDisplay = (function() { addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin( event, pickRay, "x", pitchCenter, pitchHandleRotation ); + helperRotationHandleOnBegin(event, pickRay, "x", pitchCenter, pitchHandleRotation); }, onEnd: function(event, reason) { helperRotationHandleOnEnd(); }, onMove: function (event) { - helperRotationHandleOnMove( event, "x", pitchCenter, pitchHandleRotation ); + helperRotationHandleOnMove(event, "x", pitchCenter, pitchHandleRotation); } }); @@ -3845,13 +3845,13 @@ SelectionDisplay = (function() { addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin( event, pickRay, "z", rollCenter, rollHandleRotation ); + helperRotationHandleOnBegin(event, pickRay, "z", rollCenter, rollHandleRotation); }, onEnd: function (event, reason) { helperRotationHandleOnEnd(); }, onMove: function(event) { - helperRotationHandleOnMove( event, "z", rollCenter, rollHandleRotation ); + helperRotationHandleOnMove(event, "z", rollCenter, rollHandleRotation); } }); @@ -3899,10 +3899,10 @@ SelectionDisplay = (function() { var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); if (wantDebug) { - if ( ! overlayIncludes ) { + if (!overlayIncludes) { print("testRayIntersect - no overlayIncludes provided."); } - if ( ! overlayExcludes ) { + if (!overlayExcludes) { print("testRayIntersect - no overlayExcludes provided."); } print("testRayIntersect - Hit: " + intersectObj.intersects); @@ -3932,17 +3932,17 @@ SelectionDisplay = (function() { var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; for (var key in grabberTools) { if (grabberTools.hasOwnProperty(key)) { - interactiveOverlays.push( key ); + interactiveOverlays.push(key); } } // Start with unknown mode, in case no tool can handle this. activeTool = null; - var results = testRayIntersect( pickRay, interactiveOverlays ); - if ( results.intersects ) { + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { var hitOverlayID = results.overlayID; - if ( (hitOverlayID == HMD.tabletID) || (hitOverlayID == HMD.tabletScreenID) || (hitOverlayID == HMD.homeButtonID) ) { + if ((hitOverlayID == HMD.tabletID) || (hitOverlayID == HMD.tabletScreenID) || (hitOverlayID == HMD.homeButtonID)) { //--EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) return false; } @@ -3950,17 +3950,17 @@ SelectionDisplay = (function() { entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); var hitTool = grabberTools[ hitOverlayID ]; - if ( hitTool ) { + if (hitTool) { activeTool = hitTool; if (activeTool.onBegin) { activeTool.onBegin(event, pickRay, results); } else { - print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool( " + activeTool.mode + " ) missing onBegin"); + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); } } else { print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); - }//--End_if ( hitTool ) - }//--End_If( results.intersects ) + }//--End_if (hitTool) + }//--End_If(results.intersects) if (wantDebug) { print(" DisplayMode: " + getMode()); @@ -3976,11 +3976,11 @@ SelectionDisplay = (function() { that.mouseMoveEvent = function(event) { var wantDebug = false; if (wantDebug) { - print( "=============== eST::MouseMoveEvent BEG ======================="); + print("=============== eST::MouseMoveEvent BEG ======================="); } if (activeTool) { if (wantDebug) { - print(" Trigger ActiveTool( " + activeTool.mode + " )'s onMove"); + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); } activeTool.onMove(event); @@ -3992,7 +3992,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } - //--EARLY EXIT--( Move handled via active tool) + //--EARLY EXIT--(Move handled via active tool) return true; } @@ -4129,18 +4129,18 @@ SelectionDisplay = (function() { } var showHandles = false; if (activeTool) { - if ( activeTool.onEnd ) { + if (activeTool.onEnd) { if (wantDebug) { - print(" Triggering ActiveTool( " + activeTool.mode + " )'s onEnd"); + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); } activeTool.onEnd(event); }else if (wantDebug) { - print(" ActiveTool( " + activeTool.mode + " )'s missing onEnd"); + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); } } // hide our rotation overlays..., and show our handles - if ( isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { if (wantDebug) { print(" Triggering hide of RotateOverlays"); } From ff019d619516cb1e9b91006c311ff0bdca7a3730 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 28 Sep 2017 13:56:49 -0700 Subject: [PATCH 394/504] Fix extension detection when tracking asset uploads --- interface/src/scripting/AssetMappingsScriptingInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index ba533a5bd1..2d288a8804 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -114,7 +114,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, QString extension = ""; auto idx = path.lastIndexOf("."); - if (idx) { + if (idx >= 0) { extension = path.mid(idx + 1); } @@ -384,4 +384,4 @@ void AssetMappingModel::setupRoles() { roleNames[Qt::DisplayRole] = "name"; roleNames[Qt::UserRole + 5] = "baked"; setItemRoleNames(roleNames); -} \ No newline at end of file +} From b59ec07171505422e958f6c87a3cce44bc302130 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 29 Sep 2017 09:49:08 -0700 Subject: [PATCH 395/504] move hud rendering to render thread, still need to separate out hud overlays --- interface/resources/qml/desktop/Desktop.qml | 10 +-- interface/resources/shaders/hmd_ui.frag | 30 ------- interface/resources/shaders/hmd_ui.vert | 28 ------ interface/src/Application.cpp | 18 +--- interface/src/Application.h | 2 +- .../ControllerScriptingInterface.cpp | 4 +- .../scripting/ControllerScriptingInterface.h | 2 +- .../scripting/DesktopScriptingInterface.cpp | 2 +- .../src/scripting/DesktopScriptingInterface.h | 2 +- interface/src/ui/overlays/Base3DOverlay.cpp | 12 ++- interface/src/ui/overlays/Base3DOverlay.h | 3 + interface/src/ui/overlays/Circle3DOverlay.cpp | 2 +- interface/src/ui/overlays/Cube3DOverlay.cpp | 2 +- interface/src/ui/overlays/Image3DOverlay.cpp | 2 +- interface/src/ui/overlays/Overlay.cpp | 14 --- interface/src/ui/overlays/Overlay.h | 3 - interface/src/ui/overlays/Overlays.cpp | 62 +------------ interface/src/ui/overlays/Overlays.h | 6 -- interface/src/ui/overlays/OverlaysPayload.cpp | 3 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 2 +- interface/src/ui/overlays/Sphere3DOverlay.cpp | 2 +- .../src/display-plugins/CompositorHelper.h | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 54 ++++-------- .../src/display-plugins/OpenGLDisplayPlugin.h | 12 +-- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 88 ++++++------------- .../display-plugins/hmd/HmdDisplayPlugin.h | 15 ++-- libraries/gpu/src/gpu/Batch.h | 2 +- libraries/gpu/src/gpu/Frame.h | 4 - .../plugins/src/plugins/DisplayPlugin.cpp | 9 ++ libraries/plugins/src/plugins/DisplayPlugin.h | 9 +- .../render-utils/src/MeshPartPayload.cpp | 2 +- libraries/render-utils/src/Model.h | 2 + .../render-utils/src/RenderDeferredTask.cpp | 15 ++++ .../render-utils/src/RenderDeferredTask.h | 8 ++ libraries/render-utils/src/hmd_ui.slf | 37 ++++++++ libraries/render-utils/src/hmd_ui.slv | 36 ++++++++ libraries/render/src/render/Args.h | 3 + .../controllerModules/hudOverlayPointer.js | 10 +-- scripts/system/libraries/toolBars.js | 10 +-- 39 files changed, 224 insertions(+), 305 deletions(-) delete mode 100644 interface/resources/shaders/hmd_ui.frag delete mode 100644 interface/resources/shaders/hmd_ui.vert create mode 100644 libraries/render-utils/src/hmd_ui.slf create mode 100644 libraries/render-utils/src/hmd_ui.slv diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 579b4e7fd6..d5b01fd494 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -88,7 +88,7 @@ FocusScope { return; } var oldRecommendedRect = recommendedRect; - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, newRecommendedRectJS.width, newRecommendedRectJS.height); @@ -271,7 +271,7 @@ FocusScope { var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedRect = Controller.getRecommendedHUDRect(); var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { @@ -393,7 +393,7 @@ FocusScope { return; } - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, newRecommendedRectJS.width, newRecommendedRectJS.height); @@ -425,7 +425,7 @@ FocusScope { var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedRect = Controller.getRecommendedHUDRect(); var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); } @@ -442,7 +442,7 @@ FocusScope { return; } - var recommended = Controller.getRecommendedOverlayRect(); + var recommended = Controller.getRecommendedHUDRect(); var maxX = recommended.x + recommended.width; var maxY = recommended.y + recommended.height; var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); diff --git a/interface/resources/shaders/hmd_ui.frag b/interface/resources/shaders/hmd_ui.frag deleted file mode 100644 index 5341ab575d..0000000000 --- a/interface/resources/shaders/hmd_ui.frag +++ /dev/null @@ -1,30 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 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 -// - -uniform sampler2D sampler; - -struct OverlayData { - mat4 mvp; - float alpha; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -in vec2 vTexCoord; - -out vec4 FragColor; - -void main() { - FragColor = texture(sampler, vTexCoord); - FragColor.a *= overlay.alpha; - if (FragColor.a <= 0.0) { - discard; - } -} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui.vert b/interface/resources/shaders/hmd_ui.vert deleted file mode 100644 index 41b9b3666f..0000000000 --- a/interface/resources/shaders/hmd_ui.vert +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 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 -// - -struct OverlayData { - mat4 mvp; - float alpha; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -mat4 mvp = overlay.mvp; - -layout(location = 0) in vec3 Position; -layout(location = 3) in vec2 TexCoord; - -out vec2 vTexCoord; - -void main() { - gl_Position = mvp * vec4(Position, 1); - vTexCoord = TexCoord; -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6668f5cfa0..5e52b443e1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2590,6 +2590,8 @@ void Application::paintGL() { // in the overlay render? // Viewport is assigned to the size of the framebuffer renderArgs._viewport = ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height()); + renderArgs._hudOperator = displayPlugin->getHUDOperator(); + renderArgs._hudTexture = _applicationOverlay.getOverlayTexture(); auto baseProjection = renderArgs.getViewFrustum().getProjection(); if (displayPlugin->isStereo()) { // Stereo modes will typically have a larger projection matrix overall, @@ -2632,24 +2634,12 @@ void Application::paintGL() { displaySide(&renderArgs, _myCamera); } - gpu::Batch postCompositeBatch; - { - PROFILE_RANGE(render, "/postComposite"); - PerformanceTimer perfTimer("postComposite"); - renderArgs._batch = &postCompositeBatch; - renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height())); - renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView()); - _overlays.render3DHUDOverlays(&renderArgs); - } - auto frame = _gpuContext->endFrame(); frame->frameIndex = _frameCount; frame->framebuffer = finalFramebuffer; frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer){ DependencyManager::get()->releaseFramebuffer(framebuffer); }; - frame->overlay = _applicationOverlay.getOverlayTexture(); - frame->postCompositeBatch = postCompositeBatch; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); @@ -7209,11 +7199,11 @@ glm::uvec2 Application::getUiSize() const { return result; } -QRect Application::getRecommendedOverlayRect() const { +QRect Application::getRecommendedHUDRect() const { auto uiSize = getUiSize(); QRect result(0, 0, uiSize.x, uiSize.y); if (_displayPlugin) { - result = getActiveDisplayPlugin()->getRecommendedOverlayRect(); + result = getActiveDisplayPlugin()->getRecommendedHUDRect(); } return result; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 74e84ae92c..7d84504f70 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -157,7 +157,7 @@ public: QRect getRenderingGeometry() const; glm::uvec2 getUiSize() const; - QRect getRecommendedOverlayRect() const; + QRect getRecommendedHUDRect() const; QSize getDeviceSize() const; bool hasFocus() const; diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 9fbd01817a..5c55e2094b 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -91,8 +91,8 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const { return qApp->getUiSize(); } -QVariant ControllerScriptingInterface::getRecommendedOverlayRect() const { - auto rect = qApp->getRecommendedOverlayRect(); +QVariant ControllerScriptingInterface::getRecommendedHUDRect() const { + auto rect = qApp->getRecommendedHUDRect(); return qRectToVariant(rect); } diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 6d197477bb..8c825a17de 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -66,7 +66,7 @@ public slots: virtual void releaseEntityClickEvents(); virtual glm::vec2 getViewportDimensions() const; - virtual QVariant getRecommendedOverlayRect() const; + virtual QVariant getRecommendedHUDRect() const; signals: void keyPressEvent(const KeyEvent& event); diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index efab178798..34f196ac70 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -29,7 +29,7 @@ int DesktopScriptingInterface::getHeight() { return size.height(); } -void DesktopScriptingInterface::setOverlayAlpha(float alpha) { +void DesktopScriptingInterface::setHUDAlpha(float alpha) { qApp->getApplicationCompositor().setAlpha(alpha); } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 2825065e90..e62a3584d6 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,7 +22,7 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus public: - Q_INVOKABLE void setOverlayAlpha(float alpha); + Q_INVOKABLE void setHUDAlpha(float alpha); Q_INVOKABLE void show(const QString& path, const QString& title); int getWidth(); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 9afab80243..e711767ec2 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -26,7 +26,8 @@ Base3DOverlay::Base3DOverlay() : _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), _ignoreRayIntersection(false), - _drawInFront(false) + _drawInFront(false), + _drawHUDLayer(false) { } @@ -38,6 +39,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isDashedLine(base3DOverlay->_isDashedLine), _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), + _drawHUDLayer(base3DOverlay->_drawHUDLayer), _isGrabbable(base3DOverlay->_isGrabbable) { setTransform(base3DOverlay->getTransform()); @@ -125,13 +127,19 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { bool needRenderItemUpdate = false; auto drawInFront = properties["drawInFront"]; - if (drawInFront.isValid()) { bool value = drawInFront.toBool(); setDrawInFront(value); needRenderItemUpdate = true; } + auto drawHUDLayer = properties["drawHUDLayer"]; + if (drawHUDLayer.isValid()) { + bool value = drawHUDLayer.toBool(); + setDrawHUDLayer(value); + needRenderItemUpdate = true; + } + auto isGrabbable = properties["grabbable"]; if (isGrabbable.isValid()) { setIsGrabbable(isGrabbable.toBool()); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 93a973e60a..190d2f6cc6 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -41,6 +41,7 @@ public: bool getIsSolidLine() const { return !_isDashedLine; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getDrawInFront() const { return _drawInFront; } + bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } void setLineWidth(float lineWidth) { _lineWidth = lineWidth; } @@ -48,6 +49,7 @@ public: void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } void setDrawInFront(bool value) { _drawInFront = value; } + void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } virtual AABox getBounds() const override = 0; @@ -81,6 +83,7 @@ protected: bool _isDashedLine; bool _ignoreRayIntersection; bool _drawInFront; + bool _drawHUDLayer; bool _isGrabbable { false }; mutable bool _renderTransformDirty{ true }; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 4e51844d21..536b2c764f 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -263,7 +263,7 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 0ac9dba34b..b6df1dbc31 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -113,7 +113,7 @@ const render::ShapeKey Cube3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 998cc312eb..d0024d9710 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -132,7 +132,7 @@ void Image3DOverlay::render(RenderArgs* args) { const render::ShapeKey Image3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (_emissive || shouldDrawHUDLayer()) { + if (_emissive) { builder.withUnlit(); } if (isTransparent()) { diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 8b88d1e963..01ad56f20e 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -32,7 +32,6 @@ Overlay::Overlay() : _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), _visible(true), - _drawHUDLayer(false), _anchor(NO_ANCHOR) { } @@ -51,7 +50,6 @@ Overlay::Overlay(const Overlay* overlay) : _colorPulse(overlay->_colorPulse), _color(overlay->_color), _visible(overlay->_visible), - _drawHUDLayer(overlay->_drawHUDLayer), _anchor(overlay->_anchor) { } @@ -90,11 +88,6 @@ void Overlay::setProperties(const QVariantMap& properties) { setColorPulse(properties["colorPulse"].toFloat()); } - if (properties["drawHUDLayer"].isValid()) { - bool drawHUDLayer = properties["drawHUDLayer"].toBool(); - setDrawHUDLayer(drawHUDLayer); - } - if (properties["visible"].isValid()) { bool visible = properties["visible"].toBool(); setVisible(visible); @@ -170,13 +163,6 @@ float Overlay::getAlpha() { return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); } -void Overlay::setDrawHUDLayer(bool drawHUDLayer) { - if (drawHUDLayer != _drawHUDLayer) { - qApp->getOverlays().setOverlayDrawHUDLayer(getOverlayID(), drawHUDLayer); - _drawHUDLayer = drawHUDLayer; - } -} - // pulse travels from min to max, then max to min in one period. float Overlay::updatePulse() { if (_pulsePeriod <= 0.0f) { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index db2979b4d5..1c35f4d8a5 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -58,7 +58,6 @@ public: virtual bool is3D() const = 0; bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } - bool shouldDrawHUDLayer() const { return _drawHUDLayer; } virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; xColor getColor(); float getAlpha(); @@ -74,7 +73,6 @@ public: // setters void setVisible(bool visible) { _visible = visible; } - void setDrawHUDLayer(bool drawHUDLayer); void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } void setAnchor(Anchor anchor) { _anchor = anchor; } @@ -117,7 +115,6 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all - bool _drawHUDLayer; // should the overlay be drawn on the HUD layer Anchor _anchor; unsigned int _stackOrder { 0 }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index c93d225718..0280cf2038 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -37,29 +37,22 @@ #include "Web3DOverlay.h" #include -#include "render/ShapePipeline.h" - Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void Overlays::cleanupAllOverlays() { QMap overlaysHUD; - QMap overlays3DHUD; QMap overlaysWorld; { QMutexLocker locker(&_mutex); overlaysHUD.swap(_overlaysHUD); - overlays3DHUD.swap(_overlays3DHUD); overlaysWorld.swap(_overlaysWorld); } foreach(Overlay::Pointer overlay, overlaysHUD) { _overlaysToDelete.push_back(overlay); } - foreach(Overlay::Pointer overlay, overlays3DHUD) { - _overlaysToDelete.push_back(overlay); - } foreach(Overlay::Pointer overlay, overlaysWorld) { _overlaysToDelete.push_back(overlay); } @@ -73,8 +66,6 @@ void Overlays::init() { #if OVERLAY_PANELS _scriptEngine = new QScriptEngine(); #endif - _shapePlumber = std::make_shared(); - initOverlay3DPipelines(*_shapePlumber, true); } void Overlays::update(float deltatime) { @@ -83,9 +74,6 @@ void Overlays::update(float deltatime) { foreach(const auto& thisOverlay, _overlaysHUD) { thisOverlay->update(deltatime); } - foreach(const auto& thisOverlay, _overlays3DHUD) { - thisOverlay->update(deltatime); - } foreach(const auto& thisOverlay, _overlaysWorld) { thisOverlay->update(deltatime); } @@ -142,23 +130,6 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { } } -void Overlays::render3DHUDOverlays(RenderArgs* renderArgs) { - PROFILE_RANGE(render_overlays, __FUNCTION__); - gpu::Batch& batch = *renderArgs->_batch; - - auto textureCache = DependencyManager::get(); - - QMutexLocker lock(&_mutex); - foreach(Overlay::Pointer thisOverlay, _overlays3DHUD) { - // Reset necessary batch pipeline settings between overlays - batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? - batch.setModelTransform(Transform()); - - renderArgs->_shapePipeline = _shapePlumber->pickPipeline(renderArgs, thisOverlay->getShapeKey()); - thisOverlay->render(renderArgs); - } -} - void Overlays::disable() { _enabled = false; } @@ -173,8 +144,6 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; - } else if (_overlays3DHUD.contains(id)) { - return _overlays3DHUD[id]; } else if (_overlaysWorld.contains(id)) { return _overlaysWorld[id]; } @@ -232,7 +201,7 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { OverlayID thisID = OverlayID(QUuid::createUuid()); overlay->setOverlayID(thisID); overlay->setStackOrder(_stackOrder++); - if (overlay->is3D() && !overlay->shouldDrawHUDLayer()) { + if (overlay->is3D()) { { QMutexLocker locker(&_mutex); _overlaysWorld[thisID] = overlay; @@ -242,9 +211,6 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { render::Transaction transaction; overlay->addToScene(overlay, scene, transaction); scene->enqueueTransaction(transaction); - } else if (overlay->is3D() && overlay->shouldDrawHUDLayer()) { - QMutexLocker locker(&_mutex); - _overlays3DHUD[thisID] = overlay; } else { QMutexLocker locker(&_mutex); _overlaysHUD[thisID] = overlay; @@ -253,28 +219,6 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { return thisID; } -void Overlays::setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer) { - QMutexLocker locker(&_mutex); - if (drawHUDLayer && _overlaysWorld.contains(id)) { - std::shared_ptr overlay = _overlaysWorld.take(id); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - auto itemID = overlay->getRenderItemID(); - if (render::Item::isValidID(itemID)) { - overlay->removeFromScene(overlay, scene, transaction); - scene->enqueueTransaction(transaction); - } - _overlays3DHUD[id] = overlay; - } else if (!drawHUDLayer && _overlays3DHUD.contains(id)) { - std::shared_ptr overlay = _overlays3DHUD.take(id); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - overlay->addToScene(overlay, scene, transaction); - scene->enqueueTransaction(transaction); - _overlaysWorld[id] = overlay; - } -} - OverlayID Overlays::cloneOverlay(OverlayID id) { if (QThread::currentThread() != thread()) { OverlayID result; @@ -361,8 +305,6 @@ void Overlays::deleteOverlay(OverlayID id) { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { overlayToDelete = _overlaysHUD.take(id); - } else if (_overlays3DHUD.contains(id)) { - overlayToDelete = _overlays3DHUD.take(id); } else if (_overlaysWorld.contains(id)) { overlayToDelete = _overlaysWorld.take(id); } else { @@ -771,7 +713,7 @@ bool Overlays::isAddedOverlay(OverlayID id) { } QMutexLocker locker(&_mutex); - return _overlaysHUD.contains(id) || _overlays3DHUD.contains(id) || _overlaysWorld.contains(id); + return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); } void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 1e85562485..732a437eae 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -90,7 +90,6 @@ public: void init(); void update(float deltatime); void renderHUD(RenderArgs* renderArgs); - void render3DHUDOverlays(RenderArgs* renderArgs); void disable(); void enable(); @@ -103,8 +102,6 @@ public: OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(const Overlay::Pointer& overlay); - void setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer); - bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); @@ -334,11 +331,8 @@ private: mutable QMutex _mutex { QMutex::Recursive }; QMap _overlaysHUD; - QMap _overlays3DHUD; QMap _overlaysWorld; - render::ShapePlumberPointer _shapePlumber; - #if OVERLAY_PANELS QMap _panels; #endif diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 887bf7ff8e..7beb96c061 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -34,7 +34,8 @@ namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { auto builder = ItemKey::Builder().withTypeShape(); if (overlay->is3D()) { - if (std::static_pointer_cast(overlay)->getDrawInFront()) { + auto overlay3D = std::static_pointer_cast(overlay); + if (overlay3D->getDrawInFront() || overlay3D->getDrawHUDLayer()) { builder.withLayered(); } if (overlay->isTransparent()) { diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 2c1df478f6..a3b51d40bf 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -55,7 +55,7 @@ const render::ShapeKey Shape3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index f2c9968687..c9fc25b252 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -60,7 +60,7 @@ const render::ShapeKey Sphere3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index b1d2815f65..f448375f0d 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -74,6 +74,7 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } + glm::mat4 getUiTransform() const; float getAlpha() const { return _alpha; } void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } } @@ -122,7 +123,6 @@ protected slots: void sendFakeMouseEvent(); private: - glm::mat4 getUiTransform() const; void updateTooltips(); DisplayPluginPointer _currentDisplayPlugin; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 98f4e04492..0872edcd95 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -307,7 +307,7 @@ bool OpenGLDisplayPlugin::activate() { auto compositorHelper = DependencyManager::get(); connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] { auto compositorHelper = DependencyManager::get(); - auto animation = new QPropertyAnimation(this, "overlayAlpha"); + auto animation = new QPropertyAnimation(this, "hudAlpha"); animation->setDuration(200); animation->setEndValue(compositorHelper->getAlpha()); animation->start(); @@ -415,7 +415,7 @@ void OpenGLDisplayPlugin::customizeContext() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _overlayPipeline = gpu::Pipeline::create(program, state); + _hudPipeline = gpu::Pipeline::create(program, state); } { @@ -437,7 +437,7 @@ void OpenGLDisplayPlugin::customizeContext() { void OpenGLDisplayPlugin::uncustomizeContext() { _presentPipeline.reset(); _cursorPipeline.reset(); - _overlayPipeline.reset(); + _hudPipeline.reset(); _compositeFramebuffer.reset(); withPresentThreadLock([&] { _currentFrame.reset(); @@ -562,12 +562,11 @@ void OpenGLDisplayPlugin::updateFrameData() { }); } -void OpenGLDisplayPlugin::compositeOverlay() { - render([&](gpu::Batch& batch){ +std::function OpenGLDisplayPlugin::getHUDOperator() { + return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.setPipeline(_overlayPipeline); - batch.setResourceTexture(0, _currentFrame->overlay); + batch.setPipeline(_hudPipeline); + batch.setResourceTexture(0, hudTexture); if (isStereo()) { for_each_eye([&](Eye eye) { batch.setViewportTransform(eyeViewport(eye)); @@ -577,7 +576,7 @@ void OpenGLDisplayPlugin::compositeOverlay() { batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } - }); + }; } void OpenGLDisplayPlugin::compositePointer() { @@ -626,29 +625,15 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } - // Clear the depth framebuffer after drawing the scene so that the HUD elements can depth test against each other - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.clearDepthFramebuffer((float) UINT32_MAX); - }); - #ifdef HIFI_ENABLE_NSIGHT_DEBUG - if (false) // do not compositeoverlay if running nsight debug + if (false) // do not draw the HUD if running nsight debug #endif { - PROFILE_RANGE_EX(render_detail, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount()) - compositeOverlay(); - } - - // Only render HUD layer 3D overlays in HMD mode - if (isHmd()) { - PROFILE_RANGE_EX(render_detail, "compositeHUDOverlays", 0xff0077ff, (uint64_t)presentCount()) - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); + PROFILE_RANGE_EX(render_detail, "handleHUDBatch", 0xff0077ff, (uint64_t)presentCount()) + auto hudOperator = getHUDOperator(); + withPresentThreadLock([&] { + _hudOperator = hudOperator; }); - _gpuContext->executeBatch(_currentFrame->postCompositeBatch); } { @@ -656,13 +641,7 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeExtra(); } - // Clear the depth buffer again and draw the pointer last so it's on top of everything - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.clearDepthFramebuffer((float) UINT32_MAX); - }); - + // Draw the pointer last so it's on top of everything auto compositorHelper = DependencyManager::get(); if (compositorHelper->getReticleVisible()) { PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) @@ -844,7 +823,7 @@ void OpenGLDisplayPlugin::assertIsPresentThread() const { bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { withNonPresentThreadLock([&] { - _compositeOverlayAlpha = _overlayAlpha; + _compositeHUDAlpha = _hudAlpha; }); return Parent::beginFrameRender(frameIndex); } @@ -887,8 +866,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = getRecommendedRenderSize(); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); - _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, depthFormat, renderSize.x, renderSize.y)); + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 2080fa5ea6..c6e9b7f5be 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -22,6 +22,8 @@ #include #include +#include + namespace gpu { namespace gl { class GLBackend; @@ -30,7 +32,7 @@ namespace gpu { class OpenGLDisplayPlugin : public DisplayPlugin { Q_OBJECT - Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha) + Q_PROPERTY(float hudAlpha MEMBER _hudAlpha) using Parent = DisplayPlugin; protected: using Mutex = std::mutex; @@ -93,7 +95,7 @@ protected: virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } virtual void compositeLayers(); virtual void compositeScene(); - virtual void compositeOverlay(); + virtual std::function getHUDOperator(); virtual void compositePointer(); virtual void compositeExtra() {}; @@ -137,12 +139,12 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; gpu::FramebufferPointer _compositeFramebuffer; - gpu::PipelinePointer _overlayPipeline; + gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _simplePipeline; gpu::PipelinePointer _presentPipeline; gpu::PipelinePointer _cursorPipeline; gpu::TexturePointer _displayTexture{}; - float _compositeOverlayAlpha { 1.0f }; + float _compositeHUDAlpha { 1.0f }; struct CursorData { QImage image; @@ -176,6 +178,6 @@ protected: // Any resource shared by the main thread and the presentation thread must // be serialized through this mutex mutable Mutex _presentMutex; - float _overlayAlpha{ 1.0f }; + float _hudAlpha{ 1.0f }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 88ec94eefb..6d0134ec23 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,6 +33,9 @@ #include "../Logging.h" #include "../CompositorHelper.h" +#include "render-utils/hmd_ui_vert.h" +#include "render-utils/hmd_ui_frag.h" + static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; @@ -58,7 +61,7 @@ glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; } -QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { +QRect HmdDisplayPlugin::getRecommendedHUDRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } @@ -108,7 +111,7 @@ void HmdDisplayPlugin::internalDeactivate() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); - _overlayRenderer.build(); + _hudRenderer.build(); } void HmdDisplayPlugin::uncustomizeContext() { @@ -121,7 +124,7 @@ void HmdDisplayPlugin::uncustomizeContext() { batch.setFramebuffer(_compositeFramebuffer); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); }); - _overlayRenderer = OverlayRenderer(); + _hudRenderer = HUDRenderer(); _previewTexture.reset(); Parent::uncustomizeContext(); } @@ -171,7 +174,7 @@ float HmdDisplayPlugin::getLeftCenterPixel() const { void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) - // Composite together the scene, overlay and mouse cursor + // Composite together the scene, hud and mouse cursor hmdPresent(); if (_displayTexture) { @@ -344,17 +347,9 @@ void HmdDisplayPlugin::updateFrameData() { auto correction = glm::inverse(batchPose) * currentPose; getGLBackend()->setCameraCorrection(correction); } - - auto compositorHelper = DependencyManager::get(); - glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - - for_each_eye([&](Eye eye) { - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; - _overlayRenderer.mvps[eye] = _eyeProjections[eye] * modelView; - }); } -void HmdDisplayPlugin::OverlayRenderer::build() { +void HmdDisplayPlugin::HUDRenderer::build() { vertices = std::make_shared(); indices = std::make_shared(); @@ -410,38 +405,20 @@ void HmdDisplayPlugin::OverlayRenderer::build() { format = std::make_shared(); // 1 for everyone format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - uniformBuffers[0] = std::make_shared(sizeof(Uniforms), nullptr); - uniformBuffers[1] = std::make_shared(sizeof(Uniforms), nullptr); + uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); updatePipeline(); } -void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { - static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.vert"; - static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.frag"; - -#if LIVE_SHADER_RELOAD - static qint64 vsBuiltAge = 0; - static qint64 fsBuiltAge = 0; - QFileInfo vsInfo(vsFile); - QFileInfo fsInfo(fsFile); - auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); - auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); - if (!pipeline || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { - vsBuiltAge = vsAge; - fsBuiltAge = fsAge; -#else +void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { -#endif - QString vsSource = readFile(vsFile); - QString fsSource = readFile(fsFile); - auto vs = gpu::Shader::createVertex(vsSource.toLocal8Bit().toStdString()); - auto ps = gpu::Shader::createPixel(fsSource.toLocal8Bit().toStdString()); + auto vs = gpu::Shader::createVertex(std::string(hmd_ui_vert)); + auto ps = gpu::Shader::createPixel(std::string(hmd_ui_frag)); auto program = gpu::Shader::createProgram(vs, ps); gpu::gl::GLBackend::makeProgram(*program, gpu::Shader::BindingSet()); - this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer"); + uniformsLocation = program->getUniformBuffers().findLocation("hudBuffer"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false, false, gpu::LESS_EQUAL)); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -450,30 +427,27 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { } } -void HmdDisplayPlugin::OverlayRenderer::render(HmdDisplayPlugin& plugin) { +std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { updatePipeline(); - for_each_eye([&](Eye eye){ - uniforms.mvp = mvps[eye]; - uniformBuffers[eye]->setSubData(0, uniforms); - }); - plugin.render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(plugin._compositeFramebuffer); + return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { batch.setPipeline(pipeline); + batch.setInputFormat(format); gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); batch.setInputBuffer(gpu::Stream::POSITION, posView); batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); batch.setIndexBuffer(gpu::UINT16, indices, 0); - batch.setResourceTexture(0, plugin._currentFrame->overlay); - // FIXME use stereo information input to set both MVPs in the uniforms - for_each_eye([&](Eye eye) { - batch.setUniformBuffer(uniformsLocation, uniformBuffers[eye]); - batch.setViewportTransform(plugin.eyeViewport(eye)); - batch.drawIndexed(gpu::TRIANGLES, indexCount); - }); - }); + + uniformsBuffer->setSubData(0, uniforms); + batch.setUniformBuffer(uniformsLocation, uniformsBuffer); + + auto compositorHelper = DependencyManager::get(); + batch.setModelTransform(compositorHelper->getUiTransform()); + batch.setResourceTexture(0, hudTexture); + + batch.drawIndexed(gpu::TRIANGLES, indexCount); + }; } void HmdDisplayPlugin::compositePointer() { @@ -500,12 +474,8 @@ void HmdDisplayPlugin::compositePointer() { }); } -void HmdDisplayPlugin::compositeOverlay() { - if (!_currentFrame || !_currentFrame->overlay) { - return; - } - - _overlayRenderer.render(*this); +std::function HmdDisplayPlugin::getHUDOperator() { + return _hudRenderer.render(*this); } HmdDisplayPlugin::~HmdDisplayPlugin() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index db4e0ff7a1..a7a6d2938d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -33,7 +33,7 @@ public: glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; } bool isDisplayVisible() const override { return isHmdMounted(); } - QRect getRecommendedOverlayRect() const override final; + QRect getRecommendedHUDRect() const override final; virtual glm::mat4 getHeadPose() const override; @@ -53,7 +53,7 @@ protected: bool internalActivate() override; void internalDeactivate() override; - void compositeOverlay() override; + std::function getHUDOperator() override; void compositePointer() override; void internalPresent() override; void customizeContext() override; @@ -91,7 +91,7 @@ private: gpu::TexturePointer _previewTexture; glm::vec2 _lastWindowSize; - struct OverlayRenderer { + struct HUDRenderer { gpu::Stream::FormatPointer format; gpu::BufferPointer vertices; gpu::BufferPointer indices; @@ -99,12 +99,9 @@ private: gpu::PipelinePointer pipeline; int32_t uniformsLocation { -1 }; - // FIXME this is stupid, use the built in transformation pipeline - std::array uniformBuffers; - std::array mvps; + gpu::BufferPointer uniformsBuffer; struct Uniforms { - mat4 mvp; float alpha { 1.0f }; } uniforms; @@ -119,6 +116,6 @@ private: void build(); void updatePipeline(); - void render(HmdDisplayPlugin& plugin); - } _overlayRenderer; + std::function render(HmdDisplayPlugin& plugin); + } _hudRenderer; }; diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 77d22258b2..fca220c224 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -92,7 +92,7 @@ public: void captureNamedDrawCallInfo(std::string name); Batch(); - explicit Batch(const Batch& batch); + Batch(const Batch& batch); ~Batch(); void clear(); diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 69872906e3..1ed77a26b7 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -32,14 +32,10 @@ namespace gpu { Mat4 pose; /// The collection of batches which make up the frame Batches batches; - /// Single batch containing overlays to be drawn in the composite framebuffer - Batch postCompositeBatch; /// The main thread updates to buffers that are applicable for this frame. BufferUpdates bufferUpdates; /// The destination framebuffer in which the results will be placed FramebufferPointer framebuffer; - /// The destination texture containing the 2D overlay - TexturePointer overlay; /// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE FramebufferRecycler framebufferRecycler; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 20c72159c4..e43d5e76f3 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -33,4 +33,13 @@ void DisplayPlugin::waitForPresent() { break; } } +} + +std::function DisplayPlugin::getHUDOperator() { + std::function hudOperator; + { + QMutexLocker locker(&_presentMutex); + hudOperator = _hudOperator; + } + return hudOperator; } \ No newline at end of file diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index d3054c9bd8..11ca6f754a 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -156,8 +156,8 @@ public: return aspect(getRecommendedRenderSize()); } - // The recommended bounds for primary overlay placement - virtual QRect getRecommendedOverlayRect() const { + // The recommended bounds for primary HUD placement + virtual QRect getRecommendedHUDRect() const { const int DESKTOP_SCREEN_PADDING = 50; auto recommendedSize = getRecommendedUiSize() - glm::uvec2(DESKTOP_SCREEN_PADDING); return QRect(0, 0, recommendedSize.x, recommendedSize.y); @@ -204,8 +204,9 @@ public: void waitForPresent(); - static const QString& MENU_PATH(); + std::function getHUDOperator(); + static const QString& MENU_PATH(); signals: void recommendedFramebufferSizeChanged(const QSize& size); @@ -217,6 +218,8 @@ protected: gpu::ContextPointer _gpuContext; + std::function _hudOperator { std::function() }; + private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index dc419c03c3..942da69dd7 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -385,7 +385,7 @@ ItemKey ModelMeshPartPayload::getKey() const { builder.withInvisible(); } - if (model->isLayeredInFront()) { + if (model->isLayeredInFront() || model->isLayeredInHUD()) { builder.withLayered(); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6d338b4598..395a45b952 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -101,6 +101,7 @@ public: bool isVisible() const { return _isVisible; } bool isLayeredInFront() const { return _isLayeredInFront; } + bool isLayeredInHUD() const { return _isLayeredInHUD; } virtual void updateRenderItems(); void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; } @@ -410,6 +411,7 @@ protected: int _renderInfoHasTransparent { false }; bool _isLayeredInFront { false }; + bool _isLayeredInHUD { false }; private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index c67a1c7875..cb00734edc 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -233,6 +233,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // AA job to be revisited task.addJob("Antialiasing", primaryFramebuffer); + // Composite the HUD + task.addJob("HUD"); + task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); // Blit! @@ -407,6 +410,18 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& } } +void CompositeHUD::run(const RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_context); + + // Grab the HUD texture + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture); + } + }); +} + void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { assert(renderContext->args); assert(renderContext->args->_context); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 865849b579..452420589b 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -155,6 +155,14 @@ protected: bool _opaquePass { true }; }; +class CompositeHUD { +public: + using JobModel = render::Job::Model; + + CompositeHUD() {} + void run(const render::RenderContextPointer& renderContext); +}; + class Blit { public: using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/hmd_ui.slf b/libraries/render-utils/src/hmd_ui.slf new file mode 100644 index 0000000000..959e8d733c --- /dev/null +++ b/libraries/render-utils/src/hmd_ui.slf @@ -0,0 +1,37 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// hmd_ui.frag +// fragment shader +// +// Created by Sam Gondelman on 9/28/17. +// Copyright 2017 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 +// + +uniform sampler2D hudTexture; + +struct HUDData { + float alpha; +}; + +layout(std140) uniform hudBuffer { + HUDData hud; +}; + +in vec2 _texCoord0; + +out vec4 fragColor0; + +void main() { + vec4 color = texture(hudTexture, _texCoord0); + color.a *= hud.alpha; + if (color.a <= 0.0) { + discard; + } + + fragColor0 = color; +} \ No newline at end of file diff --git a/libraries/render-utils/src/hmd_ui.slv b/libraries/render-utils/src/hmd_ui.slv new file mode 100644 index 0000000000..d6e02ff4cb --- /dev/null +++ b/libraries/render-utils/src/hmd_ui.slv @@ -0,0 +1,36 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// hmd_ui.vert +// vertex shader +// +// Created by Sam Gondelman on 9/28/17. +// Copyright 2017 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 gpu/Inputs.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +struct HUDData { + float alpha; +}; + +layout(std140) uniform hudBuffer { + HUDData hud; +}; + +out vec2 _texCoord0; + +void main() { + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> +} diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 7070a4def5..a76b60ffe6 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -121,6 +121,9 @@ namespace render { RenderDetails _details; render::ScenePointer _scene; int8_t _cameraMode { -1 }; + + std::function _hudOperator; + gpu::TexturePointer _hudTexture; }; } diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index 487e491201..9da91417ed 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -31,7 +31,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; var halfEnd = { @@ -40,7 +40,7 @@ color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, alpha: 0.9, ignoreRayIntersection: true, - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, visible: true }; var fullPath = { @@ -52,7 +52,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; var fullEnd = { @@ -61,7 +61,7 @@ color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, alpha: 0.9, ignoreRayIntersection: true, - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, visible: true }; var holdPath = { @@ -73,7 +73,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 351f10e7bd..058910940b 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -370,7 +370,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit return Math.min(Math.max(value, min), max); } - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; that.windowDimensions = recommendedDimmensions; // Controller.getViewportDimensions(); that.origin = { x: recommendedRect.x, y: recommendedRect.y }; @@ -378,7 +378,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit // For example, maybe we want "keep the same percentage to whatever two edges are closest to the edge of screen". // If we change that, the places to do so are onResizeViewport, save (maybe), and the initial move based on Settings, below. that.onResizeViewport = function (newSize) { // Can be overridden or extended by clients. - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; var originRelativeX = (that.x - that.origin.x - that.offset.x); var originRelativeY = (that.y - that.origin.y - that.offset.y); @@ -396,7 +396,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit // code once the new toolbar position is well established with users. this.isNewPositionKey = optionalPersistenceKey + '.isNewPosition'; this.save = function () { - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (screenSize.x > 0 && screenSize.y > 0) { // Guard against invalid screen size that can occur at shut-down. @@ -443,7 +443,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y); }; that.checkResize = function () { // Can be overriden or extended, but usually not. See onResizeViewport. - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var currentWindowSize = { x: recommendedRect.width, y: recommendedRect.height }; if ((currentWindowSize.x !== that.windowDimensions.x) || (currentWindowSize.y !== that.windowDimensions.y)) { @@ -471,7 +471,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit var savedFraction = isNewPosition ? JSON.parse(Settings.getValue(this.fractionKey) || "0") : 0; Settings.setValue(this.isNewPositionKey, true); - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (savedFraction) { // If we have saved data, keep the toolbar at the same proportion of the screen width/height. From fd917917c40004ecfc8125981502bccaad969180 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 29 Sep 2017 14:48:01 -0400 Subject: [PATCH 396/504] include the source basename in Script.print() && QML/Script console.*() debug output --- libraries/script-engine/src/ScriptEngine.cpp | 13 ++++++++----- libraries/shared/src/LogHandler.cpp | 7 +++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2260cea616..f394216357 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -107,10 +107,13 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { } message += context->argument(i).toString(); } - qCDebug(scriptengineScript).noquote() << message; // noquote() so that \n is treated as newline if (ScriptEngine *scriptEngine = qobject_cast(engine)) { scriptEngine->print(message); + // prefix the script engine name to help disambiguate messages in the main debug log + qCDebug(scriptengineScript, "[%s] %s", qUtf8Printable(scriptEngine->getFilename()), qUtf8Printable(message)); + } else { + qCDebug(scriptengineScript, "%s", qUtf8Printable(message)); } return QScriptValue(); @@ -465,22 +468,22 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { } void ScriptEngine::scriptErrorMessage(const QString& message) { - qCCritical(scriptengine) << qPrintable(message); + qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit errorMessage(message, getFilename()); } void ScriptEngine::scriptWarningMessage(const QString& message) { - qCWarning(scriptengine) << qPrintable(message); + qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit warningMessage(message, getFilename()); } void ScriptEngine::scriptInfoMessage(const QString& message) { - qCInfo(scriptengine) << qPrintable(message); + qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit infoMessage(message, getFilename()); } void ScriptEngine::scriptPrintedMessage(const QString& message) { - qCDebug(scriptengine) << qPrintable(message); + qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit printedMessage(message, getFilename()); } diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index e40ef814ea..aa67c14c4b 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -177,6 +177,13 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont prefixString.append(QString(" [%1]").arg(_targetName)); } + // for [qml] console.* messages include an abbreviated source filename + if (context.category && context.file && !strcmp("qml", context.category)) { + if (const char* basename = strrchr(context.file, '/')) { + prefixString.append(QString(" [%1]").arg(basename+1)); + } + } + QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); fprintf(stdout, "%s\n", qPrintable(logMessage)); return logMessage; From 6571aef997acdb466a9bafcd2528806be357d437 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 29 Sep 2017 18:52:39 -0400 Subject: [PATCH 397/504] [Case 6491] makeStretchTool: Fix z undefined issue (details below). z:z appears to have been a typo, as there's no var z. Should likely be z:1. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 183fba1670..efe68fe973 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2833,7 +2833,7 @@ SelectionDisplay = (function() { planeNormal = { x: 0, y: 0, - z: z + z: 1 }; } } From 0daa5012ca2dce766c1c6a5c7cb344bd49448917 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 30 Sep 2017 15:00:15 +1300 Subject: [PATCH 398/504] Enable toolbar and tablet icons to load from local script directory --- interface/resources/qml/hifi/tablet/TabletButton.qml | 2 +- interface/resources/qml/hifi/toolbars/ToolbarButton.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 58091d9fab..6d0fe810b2 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -84,7 +84,7 @@ Item { } function urlHelper(src) { - if (src.match(/\bhttp/)) { + if (src.match(/\bhttp/) || src.match(/\bfile:/)) { return src; } else { return "../../../" + src; diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index bbf2d019fb..63149ad23b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -34,7 +34,7 @@ StateImage { } function urlHelper(src) { - if (src.match(/\bhttp/)) { + if (src.match(/\bhttp/) || src.match(/\bfile:/)) { return src; } else { return "../../../" + src; From b56b3a2e6124106cf59bbb2ee3d13b95cebe939c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 30 Sep 2017 15:01:21 +1300 Subject: [PATCH 399/504] Change app name from "VR EDIT" to "SHAPES" and use new icon --- scripts/vr-edit/assets/shapes-a.svg | 15 +++++++++++++++ scripts/vr-edit/assets/shapes-d.svg | 18 ++++++++++++++++++ scripts/vr-edit/assets/shapes-i.svg | 18 ++++++++++++++++++ scripts/vr-edit/vr-edit.js | 8 ++++---- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 scripts/vr-edit/assets/shapes-a.svg create mode 100644 scripts/vr-edit/assets/shapes-d.svg create mode 100644 scripts/vr-edit/assets/shapes-i.svg diff --git a/scripts/vr-edit/assets/shapes-a.svg b/scripts/vr-edit/assets/shapes-a.svg new file mode 100644 index 0000000000..07918ba294 --- /dev/null +++ b/scripts/vr-edit/assets/shapes-a.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/scripts/vr-edit/assets/shapes-d.svg b/scripts/vr-edit/assets/shapes-d.svg new file mode 100644 index 0000000000..fa64b519b9 --- /dev/null +++ b/scripts/vr-edit/assets/shapes-d.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/scripts/vr-edit/assets/shapes-i.svg b/scripts/vr-edit/assets/shapes-i.svg new file mode 100644 index 0000000000..cc9df9e64a --- /dev/null +++ b/scripts/vr-edit/assets/shapes-i.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/scripts/vr-edit/vr-edit.js b/scripts/vr-edit/vr-edit.js index d78b66f60f..87dfe64020 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/vr-edit/vr-edit.js @@ -12,10 +12,10 @@ "use strict"; - var APP_NAME = "VR EDIT", // TODO: App name. - APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons. - APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg", - APP_ICON_DISABLED = "icons/tablet-icons/edit-disabled.svg", + var APP_NAME = "SHAPES", + APP_ICON_INACTIVE = Script.resolvePath("./assets/shapes-i.svg"), + APP_ICON_ACTIVE = Script.resolvePath("./assets/shapes-a.svg"), + APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"), ENABLED_CAPTION_COLOR_OVERRIDE = "", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", START_DELAY = 2000, // ms From 229c1869013ee94f1a56e5068cabdc103a793053 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 30 Sep 2017 15:09:35 +1300 Subject: [PATCH 400/504] Rename script from vr-edit.js to shapes.js --- scripts/{vr-edit => shapes}/assets/audio/clone.wav | Bin scripts/{vr-edit => shapes}/assets/audio/create.wav | Bin scripts/{vr-edit => shapes}/assets/audio/delete.wav | Bin scripts/{vr-edit => shapes}/assets/audio/drop.wav | Bin scripts/{vr-edit => shapes}/assets/audio/equip.wav | Bin scripts/{vr-edit => shapes}/assets/audio/error.wav | Bin scripts/{vr-edit => shapes}/assets/audio/select.wav | Bin .../{vr-edit => shapes}/assets/blue-header-bar.fbx | Bin .../{vr-edit => shapes}/assets/create/circle.fbx | Bin scripts/{vr-edit => shapes}/assets/create/cone.fbx | Bin .../assets/create/create-heading.svg | 0 scripts/{vr-edit => shapes}/assets/create/cube.fbx | Bin .../{vr-edit => shapes}/assets/create/cylinder.fbx | Bin .../assets/create/dodecahedron.fbx | Bin .../{vr-edit => shapes}/assets/create/hexagon.fbx | Bin .../assets/create/icosahedron.fbx | Bin .../{vr-edit => shapes}/assets/create/octagon.fbx | Bin .../assets/create/octahedron.fbx | Bin scripts/{vr-edit => shapes}/assets/create/prism.fbx | Bin .../{vr-edit => shapes}/assets/create/sphere.fbx | Bin .../assets/create/tetrahedron.fbx | Bin scripts/{vr-edit => shapes}/assets/gray-header.fbx | Bin .../{vr-edit => shapes}/assets/green-header-bar.fbx | Bin scripts/{vr-edit => shapes}/assets/green-header.fbx | Bin .../{vr-edit => shapes}/assets/horizontal-rule.svg | 0 scripts/{vr-edit => shapes}/assets/shapes-a.svg | 0 scripts/{vr-edit => shapes}/assets/shapes-d.svg | 0 scripts/{vr-edit => shapes}/assets/shapes-i.svg | 0 .../assets/tools/back-heading.svg | 0 .../{vr-edit => shapes}/assets/tools/back-icon.svg | 0 .../{vr-edit => shapes}/assets/tools/clone-icon.svg | 0 .../assets/tools/clone-label.svg | 0 .../assets/tools/clone-tool-heading.svg | 0 .../{vr-edit => shapes}/assets/tools/color-icon.svg | 0 .../assets/tools/color-label.svg | 0 .../assets/tools/color-tool-heading.svg | 0 .../assets/tools/color/color-circle-black.png | Bin .../assets/tools/color/color-circle.png | Bin .../assets/tools/color/pick-color-label.svg | 0 .../assets/tools/color/slider-alpha.png | Bin .../assets/tools/color/slider-white.png | Bin .../assets/tools/color/swatches-label.svg | 0 .../assets/tools/common/actions-label.svg | 0 .../assets/tools/common/down-arrow.svg | 0 .../assets/tools/common/finish-label.svg | 0 .../assets/tools/common/info-icon.svg | 0 .../assets/tools/common/up-arrow.svg | 0 .../assets/tools/delete-icon.svg | 0 .../assets/tools/delete-label.svg | 0 .../assets/tools/delete-tool-heading.svg | 0 .../assets/tools/delete/info-text.svg | 0 .../{vr-edit => shapes}/assets/tools/group-icon.svg | 0 .../assets/tools/group-label.svg | 0 .../assets/tools/group-tool-heading.svg | 0 .../assets/tools/group/clear-label.svg | 0 .../assets/tools/group/group-label.svg | 0 .../assets/tools/group/selection-box-label.svg | 0 .../assets/tools/group/ungroup-label.svg | 0 .../assets/tools/physics-icon.svg | 0 .../assets/tools/physics-label.svg | 0 .../assets/tools/physics-tool-heading.svg | 0 .../tools/physics/buttons/collisions-label.svg | 0 .../tools/physics/buttons/grabbable-label.svg | 0 .../assets/tools/physics/buttons/gravity-label.svg | 0 .../assets/tools/physics/buttons/off-label.svg | 0 .../assets/tools/physics/buttons/on-label.svg | 0 .../assets/tools/physics/presets-label.svg | 0 .../assets/tools/physics/presets/balloon-label.svg | 0 .../assets/tools/physics/presets/cotton-label.svg | 0 .../assets/tools/physics/presets/custom-label.svg | 0 .../assets/tools/physics/presets/default-label.svg | 0 .../assets/tools/physics/presets/ice-label.svg | 0 .../assets/tools/physics/presets/lead-label.svg | 0 .../assets/tools/physics/presets/rubber-label.svg | 0 .../tools/physics/presets/tumbleweed-label.svg | 0 .../assets/tools/physics/presets/wood-label.svg | 0 .../assets/tools/physics/presets/zero-g-label.svg | 0 .../assets/tools/physics/properties-label.svg | 0 .../assets/tools/physics/sliders/bounce-label.svg | 0 .../assets/tools/physics/sliders/density-label.svg | 0 .../assets/tools/physics/sliders/friction-label.svg | 0 .../assets/tools/physics/sliders/gravity-label.svg | 0 .../{vr-edit => shapes}/assets/tools/redo-icon.svg | 0 .../{vr-edit => shapes}/assets/tools/redo-label.svg | 0 .../assets/tools/stretch-icon.svg | 0 .../assets/tools/stretch-label.svg | 0 .../assets/tools/stretch-tool-heading.svg | 0 .../assets/tools/stretch/info-text.svg | 0 .../{vr-edit => shapes}/assets/tools/tool-icon.fbx | Bin .../{vr-edit => shapes}/assets/tools/tool-label.svg | 0 .../assets/tools/tools-heading.svg | 0 .../{vr-edit => shapes}/assets/tools/undo-icon.svg | 0 .../{vr-edit => shapes}/assets/tools/undo-label.svg | 0 .../{vr-edit => shapes}/modules/createPalette.js | 0 scripts/{vr-edit => shapes}/modules/feedback.js | 0 scripts/{vr-edit => shapes}/modules/groups.js | 0 scripts/{vr-edit => shapes}/modules/hand.js | 0 scripts/{vr-edit => shapes}/modules/handles.js | 0 scripts/{vr-edit => shapes}/modules/highlights.js | 0 scripts/{vr-edit => shapes}/modules/history.js | 0 scripts/{vr-edit => shapes}/modules/laser.js | 0 scripts/{vr-edit => shapes}/modules/selection.js | 0 scripts/{vr-edit => shapes}/modules/toolIcon.js | 0 scripts/{vr-edit => shapes}/modules/toolsMenu.js | 0 scripts/{vr-edit => shapes}/modules/uit.js | 0 scripts/{vr-edit/vr-edit.js => shapes/shapes.js} | 2 +- scripts/{vr-edit => shapes}/utilities/utilities.js | 0 107 files changed, 1 insertion(+), 1 deletion(-) rename scripts/{vr-edit => shapes}/assets/audio/clone.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/create.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/delete.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/drop.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/equip.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/error.wav (100%) rename scripts/{vr-edit => shapes}/assets/audio/select.wav (100%) rename scripts/{vr-edit => shapes}/assets/blue-header-bar.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/circle.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/cone.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/create-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/create/cube.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/cylinder.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/dodecahedron.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/hexagon.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/icosahedron.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/octagon.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/octahedron.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/prism.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/sphere.fbx (100%) rename scripts/{vr-edit => shapes}/assets/create/tetrahedron.fbx (100%) rename scripts/{vr-edit => shapes}/assets/gray-header.fbx (100%) rename scripts/{vr-edit => shapes}/assets/green-header-bar.fbx (100%) rename scripts/{vr-edit => shapes}/assets/green-header.fbx (100%) rename scripts/{vr-edit => shapes}/assets/horizontal-rule.svg (100%) rename scripts/{vr-edit => shapes}/assets/shapes-a.svg (100%) rename scripts/{vr-edit => shapes}/assets/shapes-d.svg (100%) rename scripts/{vr-edit => shapes}/assets/shapes-i.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/back-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/back-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/clone-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/clone-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/clone-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/color-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/color-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/color-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/color-circle-black.png (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/color-circle.png (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/pick-color-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/slider-alpha.png (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/slider-white.png (100%) rename scripts/{vr-edit => shapes}/assets/tools/color/swatches-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/common/actions-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/common/down-arrow.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/common/finish-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/common/info-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/common/up-arrow.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/delete-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/delete-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/delete-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/delete/info-text.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group/clear-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group/group-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group/selection-box-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/group/ungroup-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/buttons/collisions-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/buttons/grabbable-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/buttons/gravity-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/buttons/off-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/buttons/on-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/balloon-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/cotton-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/custom-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/default-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/ice-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/lead-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/rubber-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/tumbleweed-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/wood-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/presets/zero-g-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/properties-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/sliders/bounce-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/sliders/density-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/sliders/friction-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/physics/sliders/gravity-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/redo-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/redo-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/stretch-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/stretch-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/stretch-tool-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/stretch/info-text.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/tool-icon.fbx (100%) rename scripts/{vr-edit => shapes}/assets/tools/tool-label.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/tools-heading.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/undo-icon.svg (100%) rename scripts/{vr-edit => shapes}/assets/tools/undo-label.svg (100%) rename scripts/{vr-edit => shapes}/modules/createPalette.js (100%) rename scripts/{vr-edit => shapes}/modules/feedback.js (100%) rename scripts/{vr-edit => shapes}/modules/groups.js (100%) rename scripts/{vr-edit => shapes}/modules/hand.js (100%) rename scripts/{vr-edit => shapes}/modules/handles.js (100%) rename scripts/{vr-edit => shapes}/modules/highlights.js (100%) rename scripts/{vr-edit => shapes}/modules/history.js (100%) rename scripts/{vr-edit => shapes}/modules/laser.js (100%) rename scripts/{vr-edit => shapes}/modules/selection.js (100%) rename scripts/{vr-edit => shapes}/modules/toolIcon.js (100%) rename scripts/{vr-edit => shapes}/modules/toolsMenu.js (100%) rename scripts/{vr-edit => shapes}/modules/uit.js (100%) rename scripts/{vr-edit/vr-edit.js => shapes/shapes.js} (99%) rename scripts/{vr-edit => shapes}/utilities/utilities.js (100%) diff --git a/scripts/vr-edit/assets/audio/clone.wav b/scripts/shapes/assets/audio/clone.wav similarity index 100% rename from scripts/vr-edit/assets/audio/clone.wav rename to scripts/shapes/assets/audio/clone.wav diff --git a/scripts/vr-edit/assets/audio/create.wav b/scripts/shapes/assets/audio/create.wav similarity index 100% rename from scripts/vr-edit/assets/audio/create.wav rename to scripts/shapes/assets/audio/create.wav diff --git a/scripts/vr-edit/assets/audio/delete.wav b/scripts/shapes/assets/audio/delete.wav similarity index 100% rename from scripts/vr-edit/assets/audio/delete.wav rename to scripts/shapes/assets/audio/delete.wav diff --git a/scripts/vr-edit/assets/audio/drop.wav b/scripts/shapes/assets/audio/drop.wav similarity index 100% rename from scripts/vr-edit/assets/audio/drop.wav rename to scripts/shapes/assets/audio/drop.wav diff --git a/scripts/vr-edit/assets/audio/equip.wav b/scripts/shapes/assets/audio/equip.wav similarity index 100% rename from scripts/vr-edit/assets/audio/equip.wav rename to scripts/shapes/assets/audio/equip.wav diff --git a/scripts/vr-edit/assets/audio/error.wav b/scripts/shapes/assets/audio/error.wav similarity index 100% rename from scripts/vr-edit/assets/audio/error.wav rename to scripts/shapes/assets/audio/error.wav diff --git a/scripts/vr-edit/assets/audio/select.wav b/scripts/shapes/assets/audio/select.wav similarity index 100% rename from scripts/vr-edit/assets/audio/select.wav rename to scripts/shapes/assets/audio/select.wav diff --git a/scripts/vr-edit/assets/blue-header-bar.fbx b/scripts/shapes/assets/blue-header-bar.fbx similarity index 100% rename from scripts/vr-edit/assets/blue-header-bar.fbx rename to scripts/shapes/assets/blue-header-bar.fbx diff --git a/scripts/vr-edit/assets/create/circle.fbx b/scripts/shapes/assets/create/circle.fbx similarity index 100% rename from scripts/vr-edit/assets/create/circle.fbx rename to scripts/shapes/assets/create/circle.fbx diff --git a/scripts/vr-edit/assets/create/cone.fbx b/scripts/shapes/assets/create/cone.fbx similarity index 100% rename from scripts/vr-edit/assets/create/cone.fbx rename to scripts/shapes/assets/create/cone.fbx diff --git a/scripts/vr-edit/assets/create/create-heading.svg b/scripts/shapes/assets/create/create-heading.svg similarity index 100% rename from scripts/vr-edit/assets/create/create-heading.svg rename to scripts/shapes/assets/create/create-heading.svg diff --git a/scripts/vr-edit/assets/create/cube.fbx b/scripts/shapes/assets/create/cube.fbx similarity index 100% rename from scripts/vr-edit/assets/create/cube.fbx rename to scripts/shapes/assets/create/cube.fbx diff --git a/scripts/vr-edit/assets/create/cylinder.fbx b/scripts/shapes/assets/create/cylinder.fbx similarity index 100% rename from scripts/vr-edit/assets/create/cylinder.fbx rename to scripts/shapes/assets/create/cylinder.fbx diff --git a/scripts/vr-edit/assets/create/dodecahedron.fbx b/scripts/shapes/assets/create/dodecahedron.fbx similarity index 100% rename from scripts/vr-edit/assets/create/dodecahedron.fbx rename to scripts/shapes/assets/create/dodecahedron.fbx diff --git a/scripts/vr-edit/assets/create/hexagon.fbx b/scripts/shapes/assets/create/hexagon.fbx similarity index 100% rename from scripts/vr-edit/assets/create/hexagon.fbx rename to scripts/shapes/assets/create/hexagon.fbx diff --git a/scripts/vr-edit/assets/create/icosahedron.fbx b/scripts/shapes/assets/create/icosahedron.fbx similarity index 100% rename from scripts/vr-edit/assets/create/icosahedron.fbx rename to scripts/shapes/assets/create/icosahedron.fbx diff --git a/scripts/vr-edit/assets/create/octagon.fbx b/scripts/shapes/assets/create/octagon.fbx similarity index 100% rename from scripts/vr-edit/assets/create/octagon.fbx rename to scripts/shapes/assets/create/octagon.fbx diff --git a/scripts/vr-edit/assets/create/octahedron.fbx b/scripts/shapes/assets/create/octahedron.fbx similarity index 100% rename from scripts/vr-edit/assets/create/octahedron.fbx rename to scripts/shapes/assets/create/octahedron.fbx diff --git a/scripts/vr-edit/assets/create/prism.fbx b/scripts/shapes/assets/create/prism.fbx similarity index 100% rename from scripts/vr-edit/assets/create/prism.fbx rename to scripts/shapes/assets/create/prism.fbx diff --git a/scripts/vr-edit/assets/create/sphere.fbx b/scripts/shapes/assets/create/sphere.fbx similarity index 100% rename from scripts/vr-edit/assets/create/sphere.fbx rename to scripts/shapes/assets/create/sphere.fbx diff --git a/scripts/vr-edit/assets/create/tetrahedron.fbx b/scripts/shapes/assets/create/tetrahedron.fbx similarity index 100% rename from scripts/vr-edit/assets/create/tetrahedron.fbx rename to scripts/shapes/assets/create/tetrahedron.fbx diff --git a/scripts/vr-edit/assets/gray-header.fbx b/scripts/shapes/assets/gray-header.fbx similarity index 100% rename from scripts/vr-edit/assets/gray-header.fbx rename to scripts/shapes/assets/gray-header.fbx diff --git a/scripts/vr-edit/assets/green-header-bar.fbx b/scripts/shapes/assets/green-header-bar.fbx similarity index 100% rename from scripts/vr-edit/assets/green-header-bar.fbx rename to scripts/shapes/assets/green-header-bar.fbx diff --git a/scripts/vr-edit/assets/green-header.fbx b/scripts/shapes/assets/green-header.fbx similarity index 100% rename from scripts/vr-edit/assets/green-header.fbx rename to scripts/shapes/assets/green-header.fbx diff --git a/scripts/vr-edit/assets/horizontal-rule.svg b/scripts/shapes/assets/horizontal-rule.svg similarity index 100% rename from scripts/vr-edit/assets/horizontal-rule.svg rename to scripts/shapes/assets/horizontal-rule.svg diff --git a/scripts/vr-edit/assets/shapes-a.svg b/scripts/shapes/assets/shapes-a.svg similarity index 100% rename from scripts/vr-edit/assets/shapes-a.svg rename to scripts/shapes/assets/shapes-a.svg diff --git a/scripts/vr-edit/assets/shapes-d.svg b/scripts/shapes/assets/shapes-d.svg similarity index 100% rename from scripts/vr-edit/assets/shapes-d.svg rename to scripts/shapes/assets/shapes-d.svg diff --git a/scripts/vr-edit/assets/shapes-i.svg b/scripts/shapes/assets/shapes-i.svg similarity index 100% rename from scripts/vr-edit/assets/shapes-i.svg rename to scripts/shapes/assets/shapes-i.svg diff --git a/scripts/vr-edit/assets/tools/back-heading.svg b/scripts/shapes/assets/tools/back-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/back-heading.svg rename to scripts/shapes/assets/tools/back-heading.svg diff --git a/scripts/vr-edit/assets/tools/back-icon.svg b/scripts/shapes/assets/tools/back-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/back-icon.svg rename to scripts/shapes/assets/tools/back-icon.svg diff --git a/scripts/vr-edit/assets/tools/clone-icon.svg b/scripts/shapes/assets/tools/clone-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/clone-icon.svg rename to scripts/shapes/assets/tools/clone-icon.svg diff --git a/scripts/vr-edit/assets/tools/clone-label.svg b/scripts/shapes/assets/tools/clone-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/clone-label.svg rename to scripts/shapes/assets/tools/clone-label.svg diff --git a/scripts/vr-edit/assets/tools/clone-tool-heading.svg b/scripts/shapes/assets/tools/clone-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/clone-tool-heading.svg rename to scripts/shapes/assets/tools/clone-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/color-icon.svg b/scripts/shapes/assets/tools/color-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/color-icon.svg rename to scripts/shapes/assets/tools/color-icon.svg diff --git a/scripts/vr-edit/assets/tools/color-label.svg b/scripts/shapes/assets/tools/color-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/color-label.svg rename to scripts/shapes/assets/tools/color-label.svg diff --git a/scripts/vr-edit/assets/tools/color-tool-heading.svg b/scripts/shapes/assets/tools/color-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/color-tool-heading.svg rename to scripts/shapes/assets/tools/color-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/color/color-circle-black.png b/scripts/shapes/assets/tools/color/color-circle-black.png similarity index 100% rename from scripts/vr-edit/assets/tools/color/color-circle-black.png rename to scripts/shapes/assets/tools/color/color-circle-black.png diff --git a/scripts/vr-edit/assets/tools/color/color-circle.png b/scripts/shapes/assets/tools/color/color-circle.png similarity index 100% rename from scripts/vr-edit/assets/tools/color/color-circle.png rename to scripts/shapes/assets/tools/color/color-circle.png diff --git a/scripts/vr-edit/assets/tools/color/pick-color-label.svg b/scripts/shapes/assets/tools/color/pick-color-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/color/pick-color-label.svg rename to scripts/shapes/assets/tools/color/pick-color-label.svg diff --git a/scripts/vr-edit/assets/tools/color/slider-alpha.png b/scripts/shapes/assets/tools/color/slider-alpha.png similarity index 100% rename from scripts/vr-edit/assets/tools/color/slider-alpha.png rename to scripts/shapes/assets/tools/color/slider-alpha.png diff --git a/scripts/vr-edit/assets/tools/color/slider-white.png b/scripts/shapes/assets/tools/color/slider-white.png similarity index 100% rename from scripts/vr-edit/assets/tools/color/slider-white.png rename to scripts/shapes/assets/tools/color/slider-white.png diff --git a/scripts/vr-edit/assets/tools/color/swatches-label.svg b/scripts/shapes/assets/tools/color/swatches-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/color/swatches-label.svg rename to scripts/shapes/assets/tools/color/swatches-label.svg diff --git a/scripts/vr-edit/assets/tools/common/actions-label.svg b/scripts/shapes/assets/tools/common/actions-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/common/actions-label.svg rename to scripts/shapes/assets/tools/common/actions-label.svg diff --git a/scripts/vr-edit/assets/tools/common/down-arrow.svg b/scripts/shapes/assets/tools/common/down-arrow.svg similarity index 100% rename from scripts/vr-edit/assets/tools/common/down-arrow.svg rename to scripts/shapes/assets/tools/common/down-arrow.svg diff --git a/scripts/vr-edit/assets/tools/common/finish-label.svg b/scripts/shapes/assets/tools/common/finish-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/common/finish-label.svg rename to scripts/shapes/assets/tools/common/finish-label.svg diff --git a/scripts/vr-edit/assets/tools/common/info-icon.svg b/scripts/shapes/assets/tools/common/info-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/common/info-icon.svg rename to scripts/shapes/assets/tools/common/info-icon.svg diff --git a/scripts/vr-edit/assets/tools/common/up-arrow.svg b/scripts/shapes/assets/tools/common/up-arrow.svg similarity index 100% rename from scripts/vr-edit/assets/tools/common/up-arrow.svg rename to scripts/shapes/assets/tools/common/up-arrow.svg diff --git a/scripts/vr-edit/assets/tools/delete-icon.svg b/scripts/shapes/assets/tools/delete-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/delete-icon.svg rename to scripts/shapes/assets/tools/delete-icon.svg diff --git a/scripts/vr-edit/assets/tools/delete-label.svg b/scripts/shapes/assets/tools/delete-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/delete-label.svg rename to scripts/shapes/assets/tools/delete-label.svg diff --git a/scripts/vr-edit/assets/tools/delete-tool-heading.svg b/scripts/shapes/assets/tools/delete-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/delete-tool-heading.svg rename to scripts/shapes/assets/tools/delete-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/delete/info-text.svg b/scripts/shapes/assets/tools/delete/info-text.svg similarity index 100% rename from scripts/vr-edit/assets/tools/delete/info-text.svg rename to scripts/shapes/assets/tools/delete/info-text.svg diff --git a/scripts/vr-edit/assets/tools/group-icon.svg b/scripts/shapes/assets/tools/group-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group-icon.svg rename to scripts/shapes/assets/tools/group-icon.svg diff --git a/scripts/vr-edit/assets/tools/group-label.svg b/scripts/shapes/assets/tools/group-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group-label.svg rename to scripts/shapes/assets/tools/group-label.svg diff --git a/scripts/vr-edit/assets/tools/group-tool-heading.svg b/scripts/shapes/assets/tools/group-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group-tool-heading.svg rename to scripts/shapes/assets/tools/group-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/group/clear-label.svg b/scripts/shapes/assets/tools/group/clear-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group/clear-label.svg rename to scripts/shapes/assets/tools/group/clear-label.svg diff --git a/scripts/vr-edit/assets/tools/group/group-label.svg b/scripts/shapes/assets/tools/group/group-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group/group-label.svg rename to scripts/shapes/assets/tools/group/group-label.svg diff --git a/scripts/vr-edit/assets/tools/group/selection-box-label.svg b/scripts/shapes/assets/tools/group/selection-box-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group/selection-box-label.svg rename to scripts/shapes/assets/tools/group/selection-box-label.svg diff --git a/scripts/vr-edit/assets/tools/group/ungroup-label.svg b/scripts/shapes/assets/tools/group/ungroup-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/group/ungroup-label.svg rename to scripts/shapes/assets/tools/group/ungroup-label.svg diff --git a/scripts/vr-edit/assets/tools/physics-icon.svg b/scripts/shapes/assets/tools/physics-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics-icon.svg rename to scripts/shapes/assets/tools/physics-icon.svg diff --git a/scripts/vr-edit/assets/tools/physics-label.svg b/scripts/shapes/assets/tools/physics-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics-label.svg rename to scripts/shapes/assets/tools/physics-label.svg diff --git a/scripts/vr-edit/assets/tools/physics-tool-heading.svg b/scripts/shapes/assets/tools/physics-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics-tool-heading.svg rename to scripts/shapes/assets/tools/physics-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/physics/buttons/collisions-label.svg b/scripts/shapes/assets/tools/physics/buttons/collisions-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/buttons/collisions-label.svg rename to scripts/shapes/assets/tools/physics/buttons/collisions-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/buttons/grabbable-label.svg b/scripts/shapes/assets/tools/physics/buttons/grabbable-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/buttons/grabbable-label.svg rename to scripts/shapes/assets/tools/physics/buttons/grabbable-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/buttons/gravity-label.svg b/scripts/shapes/assets/tools/physics/buttons/gravity-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/buttons/gravity-label.svg rename to scripts/shapes/assets/tools/physics/buttons/gravity-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/buttons/off-label.svg b/scripts/shapes/assets/tools/physics/buttons/off-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/buttons/off-label.svg rename to scripts/shapes/assets/tools/physics/buttons/off-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/buttons/on-label.svg b/scripts/shapes/assets/tools/physics/buttons/on-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/buttons/on-label.svg rename to scripts/shapes/assets/tools/physics/buttons/on-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets-label.svg b/scripts/shapes/assets/tools/physics/presets-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets-label.svg rename to scripts/shapes/assets/tools/physics/presets-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/balloon-label.svg b/scripts/shapes/assets/tools/physics/presets/balloon-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/balloon-label.svg rename to scripts/shapes/assets/tools/physics/presets/balloon-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/cotton-label.svg b/scripts/shapes/assets/tools/physics/presets/cotton-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/cotton-label.svg rename to scripts/shapes/assets/tools/physics/presets/cotton-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/custom-label.svg b/scripts/shapes/assets/tools/physics/presets/custom-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/custom-label.svg rename to scripts/shapes/assets/tools/physics/presets/custom-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/default-label.svg b/scripts/shapes/assets/tools/physics/presets/default-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/default-label.svg rename to scripts/shapes/assets/tools/physics/presets/default-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/ice-label.svg b/scripts/shapes/assets/tools/physics/presets/ice-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/ice-label.svg rename to scripts/shapes/assets/tools/physics/presets/ice-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/lead-label.svg b/scripts/shapes/assets/tools/physics/presets/lead-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/lead-label.svg rename to scripts/shapes/assets/tools/physics/presets/lead-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/rubber-label.svg b/scripts/shapes/assets/tools/physics/presets/rubber-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/rubber-label.svg rename to scripts/shapes/assets/tools/physics/presets/rubber-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/tumbleweed-label.svg b/scripts/shapes/assets/tools/physics/presets/tumbleweed-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/tumbleweed-label.svg rename to scripts/shapes/assets/tools/physics/presets/tumbleweed-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/wood-label.svg b/scripts/shapes/assets/tools/physics/presets/wood-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/wood-label.svg rename to scripts/shapes/assets/tools/physics/presets/wood-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/presets/zero-g-label.svg b/scripts/shapes/assets/tools/physics/presets/zero-g-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/presets/zero-g-label.svg rename to scripts/shapes/assets/tools/physics/presets/zero-g-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/properties-label.svg b/scripts/shapes/assets/tools/physics/properties-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/properties-label.svg rename to scripts/shapes/assets/tools/physics/properties-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/sliders/bounce-label.svg b/scripts/shapes/assets/tools/physics/sliders/bounce-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/sliders/bounce-label.svg rename to scripts/shapes/assets/tools/physics/sliders/bounce-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/sliders/density-label.svg b/scripts/shapes/assets/tools/physics/sliders/density-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/sliders/density-label.svg rename to scripts/shapes/assets/tools/physics/sliders/density-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/sliders/friction-label.svg b/scripts/shapes/assets/tools/physics/sliders/friction-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/sliders/friction-label.svg rename to scripts/shapes/assets/tools/physics/sliders/friction-label.svg diff --git a/scripts/vr-edit/assets/tools/physics/sliders/gravity-label.svg b/scripts/shapes/assets/tools/physics/sliders/gravity-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/physics/sliders/gravity-label.svg rename to scripts/shapes/assets/tools/physics/sliders/gravity-label.svg diff --git a/scripts/vr-edit/assets/tools/redo-icon.svg b/scripts/shapes/assets/tools/redo-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/redo-icon.svg rename to scripts/shapes/assets/tools/redo-icon.svg diff --git a/scripts/vr-edit/assets/tools/redo-label.svg b/scripts/shapes/assets/tools/redo-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/redo-label.svg rename to scripts/shapes/assets/tools/redo-label.svg diff --git a/scripts/vr-edit/assets/tools/stretch-icon.svg b/scripts/shapes/assets/tools/stretch-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/stretch-icon.svg rename to scripts/shapes/assets/tools/stretch-icon.svg diff --git a/scripts/vr-edit/assets/tools/stretch-label.svg b/scripts/shapes/assets/tools/stretch-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/stretch-label.svg rename to scripts/shapes/assets/tools/stretch-label.svg diff --git a/scripts/vr-edit/assets/tools/stretch-tool-heading.svg b/scripts/shapes/assets/tools/stretch-tool-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/stretch-tool-heading.svg rename to scripts/shapes/assets/tools/stretch-tool-heading.svg diff --git a/scripts/vr-edit/assets/tools/stretch/info-text.svg b/scripts/shapes/assets/tools/stretch/info-text.svg similarity index 100% rename from scripts/vr-edit/assets/tools/stretch/info-text.svg rename to scripts/shapes/assets/tools/stretch/info-text.svg diff --git a/scripts/vr-edit/assets/tools/tool-icon.fbx b/scripts/shapes/assets/tools/tool-icon.fbx similarity index 100% rename from scripts/vr-edit/assets/tools/tool-icon.fbx rename to scripts/shapes/assets/tools/tool-icon.fbx diff --git a/scripts/vr-edit/assets/tools/tool-label.svg b/scripts/shapes/assets/tools/tool-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/tool-label.svg rename to scripts/shapes/assets/tools/tool-label.svg diff --git a/scripts/vr-edit/assets/tools/tools-heading.svg b/scripts/shapes/assets/tools/tools-heading.svg similarity index 100% rename from scripts/vr-edit/assets/tools/tools-heading.svg rename to scripts/shapes/assets/tools/tools-heading.svg diff --git a/scripts/vr-edit/assets/tools/undo-icon.svg b/scripts/shapes/assets/tools/undo-icon.svg similarity index 100% rename from scripts/vr-edit/assets/tools/undo-icon.svg rename to scripts/shapes/assets/tools/undo-icon.svg diff --git a/scripts/vr-edit/assets/tools/undo-label.svg b/scripts/shapes/assets/tools/undo-label.svg similarity index 100% rename from scripts/vr-edit/assets/tools/undo-label.svg rename to scripts/shapes/assets/tools/undo-label.svg diff --git a/scripts/vr-edit/modules/createPalette.js b/scripts/shapes/modules/createPalette.js similarity index 100% rename from scripts/vr-edit/modules/createPalette.js rename to scripts/shapes/modules/createPalette.js diff --git a/scripts/vr-edit/modules/feedback.js b/scripts/shapes/modules/feedback.js similarity index 100% rename from scripts/vr-edit/modules/feedback.js rename to scripts/shapes/modules/feedback.js diff --git a/scripts/vr-edit/modules/groups.js b/scripts/shapes/modules/groups.js similarity index 100% rename from scripts/vr-edit/modules/groups.js rename to scripts/shapes/modules/groups.js diff --git a/scripts/vr-edit/modules/hand.js b/scripts/shapes/modules/hand.js similarity index 100% rename from scripts/vr-edit/modules/hand.js rename to scripts/shapes/modules/hand.js diff --git a/scripts/vr-edit/modules/handles.js b/scripts/shapes/modules/handles.js similarity index 100% rename from scripts/vr-edit/modules/handles.js rename to scripts/shapes/modules/handles.js diff --git a/scripts/vr-edit/modules/highlights.js b/scripts/shapes/modules/highlights.js similarity index 100% rename from scripts/vr-edit/modules/highlights.js rename to scripts/shapes/modules/highlights.js diff --git a/scripts/vr-edit/modules/history.js b/scripts/shapes/modules/history.js similarity index 100% rename from scripts/vr-edit/modules/history.js rename to scripts/shapes/modules/history.js diff --git a/scripts/vr-edit/modules/laser.js b/scripts/shapes/modules/laser.js similarity index 100% rename from scripts/vr-edit/modules/laser.js rename to scripts/shapes/modules/laser.js diff --git a/scripts/vr-edit/modules/selection.js b/scripts/shapes/modules/selection.js similarity index 100% rename from scripts/vr-edit/modules/selection.js rename to scripts/shapes/modules/selection.js diff --git a/scripts/vr-edit/modules/toolIcon.js b/scripts/shapes/modules/toolIcon.js similarity index 100% rename from scripts/vr-edit/modules/toolIcon.js rename to scripts/shapes/modules/toolIcon.js diff --git a/scripts/vr-edit/modules/toolsMenu.js b/scripts/shapes/modules/toolsMenu.js similarity index 100% rename from scripts/vr-edit/modules/toolsMenu.js rename to scripts/shapes/modules/toolsMenu.js diff --git a/scripts/vr-edit/modules/uit.js b/scripts/shapes/modules/uit.js similarity index 100% rename from scripts/vr-edit/modules/uit.js rename to scripts/shapes/modules/uit.js diff --git a/scripts/vr-edit/vr-edit.js b/scripts/shapes/shapes.js similarity index 99% rename from scripts/vr-edit/vr-edit.js rename to scripts/shapes/shapes.js index 87dfe64020..da82baa947 100644 --- a/scripts/vr-edit/vr-edit.js +++ b/scripts/shapes/shapes.js @@ -1,5 +1,5 @@ // -// vr-edit.js +// shapes.js // // Created by David Rowe on 27 Jun 2017. // Copyright 2017 High Fidelity, Inc. diff --git a/scripts/vr-edit/utilities/utilities.js b/scripts/shapes/utilities/utilities.js similarity index 100% rename from scripts/vr-edit/utilities/utilities.js rename to scripts/shapes/utilities/utilities.js From 659b2d8a99889cef8034a192d0c37b71482f2990 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 30 Sep 2017 15:14:07 +1300 Subject: [PATCH 401/504] Disable debug log info --- scripts/shapes/shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index da82baa947..2154262229 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -71,7 +71,7 @@ button, DOMAIN_CHANGED_MESSAGE = "Toolbar-DomainChanged", - DEBUG = true; + DEBUG = false; // Utilities Script.include("./utilities/utilities.js"); From 03e0e21ce0a609784002eb6641a238d2117c7814 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 1 Oct 2017 13:19:54 +1300 Subject: [PATCH 402/504] JSLint ==> ESLint --- scripts/shapes/modules/createPalette.js | 20 +- scripts/shapes/modules/feedback.js | 34 +- scripts/shapes/modules/groups.js | 6 +- scripts/shapes/modules/hand.js | 10 +- scripts/shapes/modules/handles.js | 6 +- scripts/shapes/modules/highlights.js | 4 +- scripts/shapes/modules/history.js | 14 +- scripts/shapes/modules/laser.js | 20 +- scripts/shapes/modules/selection.js | 39 +- scripts/shapes/modules/toolIcon.js | 19 +- scripts/shapes/modules/toolsMenu.js | 883 +++++++++++---------- scripts/shapes/modules/uit.js | 26 +- scripts/shapes/shapes.js | 975 ++++++++++++------------ scripts/shapes/utilities/utilities.js | 8 +- 14 files changed, 1031 insertions(+), 1033 deletions(-) diff --git a/scripts/shapes/modules/createPalette.js b/scripts/shapes/modules/createPalette.js index 968be03ed4..505853f482 100644 --- a/scripts/shapes/modules/createPalette.js +++ b/scripts/shapes/modules/createPalette.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global App, CreatePalette */ +/* global CreatePalette: true, App, Feedback, History, UIT */ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. @@ -51,7 +51,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_HEADER_HEADING_PROPERTIES = { url: Script.resolvePath("../assets/gray-header.fbx"), - dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. + dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y / 2, @@ -66,7 +66,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { PALETTE_HEADER_BAR_PROPERTIES = { url: Script.resolvePath("../assets/blue-header-bar.fbx"), - dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. + dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y - UIT.dimensions.headerBar.y / 2, @@ -111,14 +111,14 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { ENTITY_CREATION_COLOR = { red: 192, green: 192, blue: 192 }, PALETTE_ITEM = { - overlay: "cube", // Invisible cube for hit area. + overlay: "cube", // Invisible cube for hit area. properties: { dimensions: UIT.dimensions.itemCollisionZone, localRotation: Quat.ZERO, - alpha: 0.0, // Invisible. + alpha: 0.0, // Invisible. solid: true, ignoreRayIntersection: false, - visible: true // So that laser intersects. + visible: true // So that laser intersects. }, hoverButton: { // Relative to root overlay. @@ -129,7 +129,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { localRotation: Quat.ZERO, color: UIT.colors.blueHighlight, alpha: 1.0, - emissive: true, // TODO: This has no effect. + emissive: true, // TODO: This has no effect. solid: true, ignoreRayIntersection: true, visible: false @@ -142,7 +142,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: UIT.dimensions.paletteItemIconDimensions, localPosition: UIT.dimensions.paletteItemIconOffset, localRotation: Quat.ZERO, - emissive: true, // TODO: This has no effect. + emissive: true, // TODO: This has no effect. ignoreRayIntersection: true } }, @@ -327,7 +327,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { // References. controlHand; - if (!this instanceof CreatePalette) { + if (!(this instanceof CreatePalette)) { return new CreatePalette(); } @@ -518,7 +518,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { if (!isDisplaying) { return; } - Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. + Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. paletteItemOverlays = []; paletteItemHoverOverlays = []; iconOverlays = []; diff --git a/scripts/shapes/modules/feedback.js b/scripts/shapes/modules/feedback.js index bf9b00ebc1..0d45ae2019 100644 --- a/scripts/shapes/modules/feedback.js +++ b/scripts/shapes/modules/feedback.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Feedback */ +/* global Feedback:true */ Feedback = (function () { // Provide audio and haptic user feedback. @@ -27,24 +27,24 @@ Feedback = (function () { REDO_SOUND = DROP_SOUND, FEEDBACK_PARAMETERS = { - DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, - DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, - SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. - CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, - CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, - HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. - HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. - EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, - APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, - APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, - UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, - REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, - GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } + DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, + DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, + SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. + CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, + CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, + HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. + HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. + EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, + APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, + APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, + UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, + REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, + GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } }, - VOLUME_MULTPLIER = 0.5, // Resulting volume range should be within 0.0 - 1.0. - HAPTIC_STRENGTH_MULTIPLIER = 1.3, // Resulting strength range should be within 0.0 - 1.0. - HAPTIC_LENGTH_MULTIPLIER = 75.0; // Resulting length range should be within 0 - 50, say. + VOLUME_MULTPLIER = 0.5, // Resulting volume range should be within 0.0 - 1.0. + HAPTIC_STRENGTH_MULTIPLIER = 1.3, // Resulting strength range should be within 0.0 - 1.0. + HAPTIC_LENGTH_MULTIPLIER = 75.0; // Resulting length range should be within 0 - 50, say. function play(side, item) { var parameters = FEEDBACK_PARAMETERS[item]; diff --git a/scripts/shapes/modules/groups.js b/scripts/shapes/modules/groups.js index 9331071fe2..6147df98af 100644 --- a/scripts/shapes/modules/groups.js +++ b/scripts/shapes/modules/groups.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Groups */ +/* global Groups:true, App, History */ Groups = function () { // Groups and ungroups trees of entities. @@ -19,7 +19,7 @@ Groups = function () { selections = [], entitiesSelectedCount = 0; - if (!this instanceof Groups) { + if (!(this instanceof Groups)) { return new Groups(); } @@ -200,7 +200,7 @@ Groups = function () { updateGroupInformation(); } } - childrenIndexes.push(selections[0].length); // Special extra item at end to aid updating selection. + childrenIndexes.push(selections[0].length); // Special extra item at end to aid updating selection. updateGroupInformation(); // Unlink children. diff --git a/scripts/shapes/modules/hand.js b/scripts/shapes/modules/hand.js index bd899c4724..1e8af70e57 100644 --- a/scripts/shapes/modules/hand.js +++ b/scripts/shapes/modules/hand.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Hand */ +/* global Hand:true */ Hand = function (side) { @@ -27,11 +27,11 @@ Hand = function (side) { isTriggerPressed, isTriggerClicked, - TRIGGER_ON_VALUE = 0.15, // Per controllerDispatcherUtils.js. - TRIGGER_OFF_VALUE = 0.1, // Per controllerDispatcherUtils.js. + TRIGGER_ON_VALUE = 0.15, // Per controllerDispatcherUtils.js. + TRIGGER_OFF_VALUE = 0.1, // Per controllerDispatcherUtils.js. TRIGGER_CLICKED_VALUE = 1.0, - NEAR_GRAB_RADIUS = 0.05, // Different from controllerDispatcherUtils.js. + NEAR_GRAB_RADIUS = 0.05, // Different from controllerDispatcherUtils.js. NEAR_HOVER_RADIUS = 0.025, LEFT_HAND = 0, @@ -45,7 +45,7 @@ Hand = function (side) { handleOverlayIDs = [], intersection = {}; - if (!this instanceof Hand) { + if (!(this instanceof Hand)) { return new Hand(side); } diff --git a/scripts/shapes/modules/handles.js b/scripts/shapes/modules/handles.js index 0318b21efb..c1d41b6066 100644 --- a/scripts/shapes/modules/handles.js +++ b/scripts/shapes/modules/handles.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Handles */ +/* global Handles:true */ Handles = function (side) { // Draws scaling handles. @@ -48,7 +48,7 @@ Handles = function (side) { i; - if (!this instanceof Handles) { + if (!(this instanceof Handles)) { return new Handles(side); } @@ -112,7 +112,7 @@ Handles = function (side) { function handleOffset(overlayID) { // Distance from overlay position to entity surface. if (isCornerHandle(overlayID)) { - return 0; // Corner overlays are centered on the corner. + return 0; // Corner overlays are centered on the corner. } return faceHandleOffsets.y / 2; } diff --git a/scripts/shapes/modules/highlights.js b/scripts/shapes/modules/highlights.js index d893e69f10..1af309a38e 100644 --- a/scripts/shapes/modules/highlights.js +++ b/scripts/shapes/modules/highlights.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Highlights */ +/* global Highlights: true */ Highlights = function (side) { // Draws highlights on selected entities. @@ -28,7 +28,7 @@ Highlights = function (side) { HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, LEFT_HAND = 0; - if (!this instanceof Highlights) { + if (!(this instanceof Highlights)) { return new Highlights(); } diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 88537710b9..6451000f04 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global History */ +/* global History: true */ History = (function () { // Provides undo facility. @@ -46,14 +46,14 @@ History = (function () { */ ], MAX_HISTORY_ITEMS = 1000, - undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. + undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. undoData = {}, redoData = {}; function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. - DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; properties = Entities.getEntityProperties(entityID, ["type", "dynamic", "gravity", "velocity"]); @@ -65,10 +65,12 @@ History = (function () { function kickPhysics(entityID) { // Gives entities a small kick to start off physics, if necessary. - var KICK_DELAY = 500; // ms + var KICK_DELAY = 500; // ms // Give physics a chance to catch up. Avoids some erratic behavior. - Script.setTimeout(function () { doKick(entityID); }, KICK_DELAY); + Script.setTimeout(function () { + doKick(entityID); + }, KICK_DELAY); } function prePush(undo, redo) { diff --git a/scripts/shapes/modules/laser.js b/scripts/shapes/modules/laser.js index 39ce0b713b..29434a17f9 100644 --- a/scripts/shapes/modules/laser.js +++ b/scripts/shapes/modules/laser.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Laser */ +/* global Laser:true */ Laser = function (side) { // Draws hand lasers. @@ -25,18 +25,18 @@ Laser = function (side) { searchDistance = 0.0, - SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. + SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. MINUMUM_SEARCH_SPHERE_SIZE = 0.006, - SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. - COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. - COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. + SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, - BRIGHT_POW = 0.06, // Per handControllerGrab.js. + BRIGHT_POW = 0.06, // Per handControllerGrab.js. - GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. - PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. + PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. PRECISION_PICKING = true, NO_INCLUDE_IDS = [], NO_EXCLUDE_IDS = [], @@ -52,11 +52,11 @@ Laser = function (side) { intersection; - if (!this instanceof Laser) { + if (!(this instanceof Laser)) { return new Laser(side); } - function colorPow(color, power) { // Per handControllerGrab.js. + function colorPow(color, power) { // Per handControllerGrab.js. return { red: Math.pow(color.red / 255, power) * 255, green: Math.pow(color.green / 255, power) * 255, diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index 371d424cd7..faa514d641 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -8,24 +8,22 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global SelectionManager */ +/* global SelectionManager:true, App, History */ SelectionManager = function (side) { // Manages set of selected entities. Currently supports just one set of linked entities. "use strict"; - var selection = [], // Subset of properties to provide externally. + var selection = [], // Subset of properties to provide externally. selectionIDs = [], - selectionProperties = [], // Full set of properties for history. + selectionProperties = [], // Full set of properties for history. intersectedEntityID = null, intersectedEntityIndex, rootEntityID = null, rootPosition, rootOrientation, scaleFactor, - scalePosition, - scaleOrientation, scaleRootOffset, scaleRootOrientation, startPosition, @@ -35,11 +33,9 @@ SelectionManager = function (side) { ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine"], ENTITY_TYPES_2D = ["Text", "Web"], MIN_HISTORY_MOVE_DISTANCE = 0.005, - MIN_HISTORY_ROTATE_ANGLE = 0.017453; // Radians = 1 degree. + MIN_HISTORY_ROTATE_ANGLE = 0.017453; // Radians = 1 degree. - - - if (!this instanceof SelectionManager) { + if (!(this instanceof SelectionManager)) { return new SelectionManager(side); } @@ -217,8 +213,8 @@ SelectionManager = function (side) { function doKick(entityID) { var properties, - NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. - DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; if (entityID === rootEntityID && isEditing) { @@ -235,10 +231,12 @@ SelectionManager = function (side) { function kickPhysics(entityID) { // Gives entities a small kick to start off physics, if necessary. - var KICK_DELAY = 500; // ms + var KICK_DELAY = 500; // ms // Give physics a chance to catch up. Avoids some erratic behavior. - Script.setTimeout(function () { doKick(entityID); }, KICK_DELAY); + Script.setTimeout(function () { + doKick(entityID); + }, KICK_DELAY); } function startEditing() { @@ -249,13 +247,12 @@ SelectionManager = function (side) { startOrientation = selection[0].rotation; // Disable entity set's physics. - //for (i = 0, count = selection.length; i < count; i += 1) { for (i = selection.length - 1; i >= 0; i -= 1) { Entities.editEntity(selection[i].id, { - dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. - collisionless: true, // So that entity doesn't bump us about as we resize the entity. - velocity: Vec3.ZERO, // So that entity doesn't drift if we've grabbed a set while it was moving. - angularVelocity: Vec3.ZERO // "" + dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. + collisionless: true, // So that entity doesn't bump us about as we resize the entity. + velocity: Vec3.ZERO, // So that entity doesn't drift if we've grabbed a set while it was moving. + angularVelocity: Vec3.ZERO // "" }); } @@ -483,10 +480,8 @@ SelectionManager = function (side) { }); } - // Save most recent scale parameters. + // Save most recent scale factor. scaleFactor = factor; - scalePosition = position; - scaleOrientation = orientation; } function finishHandleScaling() { @@ -756,7 +751,7 @@ SelectionManager = function (side) { function deleteEntities() { if (rootEntityID) { History.push({ createEntities: selectionProperties }, { deleteEntities: [{ entityID: rootEntityID }] }); - Entities.deleteEntity(rootEntityID); // Children are automatically deleted. + Entities.deleteEntity(rootEntityID); // Children are automatically deleted. clear(); } } diff --git a/scripts/shapes/modules/toolIcon.js b/scripts/shapes/modules/toolIcon.js index a71dfdc156..1571a6b037 100644 --- a/scripts/shapes/modules/toolIcon.js +++ b/scripts/shapes/modules/toolIcon.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global App, ToolIcon */ +/* global ToolIcon:true, App, UIT */ ToolIcon = function (side) { // Tool icon displayed on non-dominant hand. @@ -17,10 +17,10 @@ ToolIcon = function (side) { var LEFT_HAND = 0, - MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. - MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. - MODEL_POSITION_LEFT_HAND = { x: -0.025, y: 0.03, z: 0 }, // x raises in thumb direction; y moves in fingers direction. - MODEL_POSITION_RIGHT_HAND = { x: 0.025, y: 0.03, z: 0 }, // "" + MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. + MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. + MODEL_POSITION_LEFT_HAND = { x: -0.025, y: 0.03, z: 0 }, // x raises in thumb direction; y moves in fingers direction. + MODEL_POSITION_RIGHT_HAND = { x: 0.025, y: 0.03, z: 0 }, // "" MODEL_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 0, z: 100 }), MODEL_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -100 }), @@ -46,8 +46,9 @@ ToolIcon = function (side) { }, ICON_PROPERTIES = { - localPosition: { x: 0.020, y: 0.069, z: 0 }, // Relative to model overlay. - color: UIT.colors.lightGrayText // x is in fingers direction; y is in thumb direction. + // Relative to model overlay. x is in fingers direction; y is in thumb direction. + localPosition: { x: 0.020, y: 0.069, z: 0 }, + color: UIT.colors.lightGrayText }, LABEL_PROPERTIES = { localPosition: { x: -0.040, y: 0.067, z: 0 }, @@ -67,7 +68,7 @@ ToolIcon = function (side) { modelOverlay = null; - if (!this instanceof ToolIcon) { + if (!(this instanceof ToolIcon)) { return new ToolIcon(); } @@ -89,7 +90,7 @@ ToolIcon = function (side) { function clear() { // Deletes current tool model. if (modelOverlay) { - Overlays.deleteOverlay(modelOverlay); // Child overlays are automatically deleted. + Overlays.deleteOverlay(modelOverlay); // Child overlays are automatically deleted. modelOverlay = null; } } diff --git a/scripts/shapes/modules/toolsMenu.js b/scripts/shapes/modules/toolsMenu.js index 182b53179f..6bd7d4a77b 100644 --- a/scripts/shapes/modules/toolsMenu.js +++ b/scripts/shapes/modules/toolsMenu.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global App, ToolsMenu */ +/* global ToolsMenu: true, App, Feedback, UIT */ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Tool menu displayed on top of forearm. @@ -36,11 +36,11 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuEnabled = [], optionsOverlays = [], - optionsOverlaysIDs = [], // Text ids (names) of options overlays. - optionsOverlaysLabels = [], // Overlay IDs of labels for optionsOverlays. - optionsOverlaysSublabels = [], // Overlay IDs of sublabels for optionsOverlays. - optionsSliderData = [], // Uses same index values as optionsOverlays. - optionsColorData = [], // Uses same index values as optionsOverlays. + optionsOverlaysIDs = [], // Text ids (names) of options overlays. + optionsOverlaysLabels = [], // Overlay IDs of labels for optionsOverlays. + optionsOverlaysSublabels = [], // Overlay IDs of sublabels for optionsOverlays. + optionsSliderData = [], // Uses same index values as optionsOverlays. + optionsColorData = [], // Uses same index values as optionsOverlays. optionsExtraOverlays = [], optionsEnabled = [], optionsSettings = { @@ -51,7 +51,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // For reliable access, use the values from optionsSettings rather than doing Settings.getValue() - Settings values // are not necessarily updated instantaneously. }, - optionsToggles = {}, // Cater for toggle buttons without a setting. + optionsToggles = {}, // Cater for toggle buttons without a setting. footerOverlays = [], footerHoverOverlays = [], @@ -92,23 +92,23 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_HEADER_PROPERTIES = { // Invisible box to catch laser intersections while menu heading and bar move inside. - dimensions: Vec3.sum(UIT.dimensions.header, MENU_HEADER_HOVER_OFFSET), // Keep the laser on top when hover. + dimensions: Vec3.sum(UIT.dimensions.header, MENU_HEADER_HOVER_OFFSET), // Keep the laser on top when hover. localPosition: { x: 0, y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.header.y / 2, z: UIT.dimensions.header.z / 2 + MENU_HEADER_HOVER_OFFSET.z / 2 }, localRotation: Quat.ZERO, - alpha: 0.0, // Invisible + alpha: 0.0, // Invisible solid: true, ignoreRayIntersection: false, - visible: true // Catch laser intersections. + visible: true // Catch laser intersections. }, MENU_HEADER_HEADING_PROPERTIES = { url: Script.resolvePath("../assets/gray-header.fbx"), highlightURL: Script.resolvePath("../assets/green-header.fbx"), - dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. + dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: UIT.dimensions.headerBar.y / 2, z: -MENU_HEADER_HOVER_OFFSET.z / 2 }, localRotation: Quat.ZERO, alpha: 1.0, @@ -119,7 +119,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_HEADER_BAR_PROPERTIES = { url: Script.resolvePath("../assets/green-header-bar.fbx"), - dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. + dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. localPosition: { x: 0, y: -UIT.dimensions.headerHeading.y / 2 - UIT.dimensions.headerBar.y / 2, z: 0 }, localRotation: Quat.ZERO, alpha: 1.0, @@ -173,9 +173,9 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_ICON_PROPERTIES = { - url: Script.resolvePath("../assets/tools/color-icon.svg"), // Initial value so that the overlay is initialized OK. - dimensions: { x: 0.01, y: 0.01 }, // "" - localPosition: Vec3.ZERO, // "" + url: Script.resolvePath("../assets/tools/color-icon.svg"), // Initial value so that the overlay is initialized OK. + dimensions: { x: 0.01, y: 0.01 }, // Ditto. + localPosition: Vec3.ZERO, // Ditto. localRotation: Quat.ZERO, color: UIT.colors.lightGrayText, alpha: 1.0, @@ -201,14 +201,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { UI_ELEMENTS = { "menuButton": { - overlay: "cube", // Invisible cube for hit area. + overlay: "cube", // Invisible cube for hit area. properties: { dimensions: UIT.dimensions.itemCollisionZone, localRotation: Quat.ZERO, - alpha: 0.0, // Invisible. + alpha: 0.0, // Invisible. solid: true, ignoreRayIntersection: false, - visible: true // So that laser intersects. + visible: true // So that laser intersects. }, hoverButton: { overlay: "shape", @@ -223,7 +223,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: -90 }), color: UIT.colors.greenHighlight, alpha: 1.0, - emissive: true, // TODO: This has no effect. + emissive: true, // TODO: This has no effect. solid: true, ignoreRayIntersection: true, visible: false @@ -334,7 +334,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { dimensions: { x: 0.0294, y: UIT.dimensions.buttonDimensions.z, z: 0.0294 }, localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: -90 }), color: EMPTY_SWATCH_COLOR, - emissive: true, // TODO: This has no effect. + emissive: true, // TODO: This has no effect. alpha: 1.0, solid: true, ignoreRayIntersection: false, @@ -353,10 +353,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: UIT.dimensions.buttonDimensions.z, z: 0.0294 + SWATCH_HIGHLIGHT_DELTA }, - localRotation: Quat.ZERO, // Relative to swatch. + localRotation: Quat.ZERO, // Relative to swatch. localPosition: { x: 0, y: -0.0001, z: 0 }, color: UIT.colors.faintGray, - emissive: true, // TODO: This has no effect. + emissive: true, // TODO: This has no effect. alpha: 1.0, solid: true, ignoreRayIntersection: true, @@ -365,7 +365,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, "square": { - overlay: "cube", // Emulate a 2D square with a cube. + overlay: "cube", // Emulate a 2D square with a cube. properties: { localRotation: Quat.ZERO, color: UIT.colors.baseGrayShadow, @@ -420,10 +420,10 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { dimensions: { x: 0.02, y: 0.1, z: 0.01 }, localRotation: Quat.ZERO, - alpha: 0.0, // Invisible. + alpha: 0.0, // Invisible. solid: true, ignoreRayIntersection: false, - visible: true // Catch laser intersections. + visible: true // Catch laser intersections. }, label: { // Relative to barSlider. @@ -470,7 +470,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } }, - "imageSlider": { // Values range between 0.0 and 1.0. + "imageSlider": { // Values range between 0.0 and 1.0. overlay: "cube", properties: { dimensions: { x: 0.0160, y: 0.1229, z: UIT.dimensions.buttonDimensions.z }, @@ -542,7 +542,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white } }, - "picklistItem": { // Note: When using, declare before picklist item that it's being used in. + "picklistItem": { // Note: When using, declare before picklist item that it's being used in. overlay: "cube", properties: { dimensions: { x: 0.1416, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, @@ -571,21 +571,21 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { PICKLIST_HOVER_DELTA = { x: 0, y: 0, z: 0.006 }, BUTTON_PRESS_DELTA = { x: 0, y: 0, z: -0.002 }, - MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. + MIN_BAR_SLIDER_DIMENSION = 0.0001, // Avoid visual artifact for 0 slider values. PHYSICS_SLIDER_PRESETS = { // Slider values in the range 0.0 to 1.0. // Note: Friction values give the desired linear and angular damping values but friction values are a somewhat out, // especially for the balloon. - presetDefault: { gravity: 0.5, bounce: 0.5, friction: 0.5, density: 0.5 }, - presetLead: { gravity: 0.5, bounce: 0.0, friction: 0.5, density: 1.0 }, - presetWood: { gravity: 0.5, bounce: 0.4, friction: 0.5, density: 0.5 }, - presetIce: { gravity: 0.5, bounce: 0.99, friction: 0.151004, density: 0.349485 }, - presetRubber: { gravity: 0.5, bounce: 0.99, friction: 0.5, density: 0.5 }, - presetCotton: { gravity: 0.587303, bounce: 0.0, friction: 0.931878, density: 0.0 }, - presetTumbleweed: { gravity: 0.595893, bounce: 0.7, friction: 0.5, density: 0.0 }, - presetZeroG: { gravity: 0.596844, bounce: 0.5, friction: 0.5, density: 0.5 }, - presetBalloon: { gravity: 0.606313, bounce: 0.99, friction: 0.151004, density: 0.0 } + presetDefault: { gravity: 0.5, bounce: 0.5, friction: 0.5, density: 0.5 }, + presetLead: { gravity: 0.5, bounce: 0.0, friction: 0.5, density: 1.0 }, + presetWood: { gravity: 0.5, bounce: 0.4, friction: 0.5, density: 0.5 }, + presetIce: { gravity: 0.5, bounce: 0.99, friction: 0.151004, density: 0.349485 }, + presetRubber: { gravity: 0.5, bounce: 0.99, friction: 0.5, density: 0.5 }, + presetCotton: { gravity: 0.587303, bounce: 0.0, friction: 0.931878, density: 0.0 }, + presetTumbleweed: { gravity: 0.595893, bounce: 0.7, friction: 0.5, density: 0.0 }, + presetZeroG: { gravity: 0.596844, bounce: 0.5, friction: 0.5, density: 0.5 }, + presetBalloon: { gravity: 0.606313, bounce: 0.99, friction: 0.151004, density: 0.0 } }, OPTONS_PANELS = { @@ -893,7 +893,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, - color: UIT.colors.white // Icon SVG is already lightGray color. + color: UIT.colors.white // Icon SVG is already lightGray color. } }, { @@ -902,7 +902,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: Script.resolvePath("../assets/tools/stretch/info-text.svg"), scale: 0.1340, - localPosition: { // Vertically center on info icon. + localPosition: { // Vertically center on info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset @@ -1146,7 +1146,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, - color: UIT.colors.white // SVG has gray color. + color: UIT.colors.white // SVG has gray color. }, onSublabel: { url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), @@ -1194,7 +1194,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, - color: UIT.colors.white // SVG has gray color. + color: UIT.colors.white // SVG has gray color. }, onSublabel: { url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), @@ -1242,7 +1242,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, url: Script.resolvePath("../assets/tools/physics/buttons/off-label.svg"), scale: 0.0104, - color: UIT.colors.white // SVG has gray color. + color: UIT.colors.white // SVG has gray color. }, onSublabel: { url: Script.resolvePath("../assets/tools/physics/buttons/on-label.svg"), @@ -1445,7 +1445,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: 0, z: UIT.dimensions.buttonDimensions.z / 2 + UIT.dimensions.imageOverlayOffset }, - color: UIT.colors.white // SVG is colored baseGrayHighlight + color: UIT.colors.white // SVG is colored baseGrayHighlight }, customLabel: { url: Script.resolvePath("../assets/tools/physics/presets/custom-label.svg"), @@ -1659,7 +1659,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0320 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset }, - color: UIT.colors.white // Icon SVG is already lightGray color. + color: UIT.colors.white // Icon SVG is already lightGray color. } }, { @@ -1668,7 +1668,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { url: Script.resolvePath("../assets/tools/delete/info-text.svg"), scale: 0.1416, - localPosition: { // Vertically off-center w.r.t. info icon. + localPosition: { // Vertically off-center w.r.t. info icon. x: -UIT.dimensions.panel.x / 2 + 0.0679 + 0.1340 / 2, y: -UIT.dimensions.panel.y / 2 + 0.0200 + 0.0240 / 2 + 0.0063 / 2 + UIT.dimensions.footerHeight, z: UIT.dimensions.panel.z / 2 + UIT.dimensions.imageOverlayOffset @@ -1913,7 +1913,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } ], - COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. + COLOR_TOOL = 0, // Indexes of corresponding MENU_ITEMS item. SCALE_TOOL = 1, CLONE_TOOL = 2, GROUP_TOOL = 3, @@ -1938,7 +1938,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { properties: { localPosition: { x: MENU_ITEM_XS[2], - y: MENU_ITEM_YS[3] - 0.008, // Allow space for horizontal rule and Line up with Create palette row. + y: MENU_ITEM_YS[3] - 0.008, // Allow space for horizontal rule and Line up with Create palette row. z: UIT.dimensions.panel.z / 2 + UI_ELEMENTS.menuButton.properties.dimensions.z / 2 } }, @@ -2033,8 +2033,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Forward declarations. doCommand; - - if (!this instanceof ToolsMenu) { + if (!(this instanceof ToolsMenu)) { return new ToolsMenu(); } @@ -2232,7 +2231,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } } else if (optionsItems[i].type === "toggleButton") { - optionsToggles[optionsItems[i].id] = false; // Default to off. + optionsToggles[optionsItems[i].id] = false; // Default to off. } optionsOverlays.push(Overlays.addOverlay(UI_ELEMENTS[optionsItems[i].type].overlay, properties)); @@ -2307,7 +2306,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = optionsItems[i].imageURL; childProperties.dimensions = { x: properties.dimensions.x, y: properties.dimensions.y }; - imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. + imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. if (optionsItems[i].useBaseColor) { childProperties.color = properties.color; } @@ -2358,7 +2357,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { childProperties = Object.clone(UI_ELEMENTS.image.properties); childProperties.url = optionsItems[i].imageURL; childProperties.scale = properties.dimensions.x; - imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. + imageOffset += 2 * IMAGE_OFFSET; // Double offset to prevent clipping against background. childProperties.localPosition = { x: 0, y: properties.dimensions.y / 2 + imageOffset, z: 0 }; childProperties.localRotation = Quat.fromVec3Degrees({ x: -90, y: 90, z: 0 }); childProperties.parentID = optionsOverlays[optionsOverlays.length - 1]; @@ -2690,252 +2689,252 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { switch (command) { - case "setPickColor": - setColorPicker(parameter); - break; + case "setPickColor": + setColorPicker(parameter); + break; - case "setColorPerCircle": - hsvControl.hsv.h = parameter.h; - hsvControl.hsv.s = parameter.s; - updateColorSlider(); - value = hsvToRGB(hsvControl.hsv); - setCurrentColor(value); - uiCommandCallback("setColor", value); - break; - - case "setColorPerSlider": - hsvControl.hsv.v = parameter; - updateColorCircle(); - value = hsvToRGB(hsvControl.hsv); - setCurrentColor(value); - uiCommandCallback("setColor", value); - break; - - case "setColorPerSwatch": - index = optionsOverlaysIDs.indexOf(parameter); - value = optionsSettings[parameter].value; - if (value !== "") { - // Set current color to swatch color. + case "setColorPerCircle": + hsvControl.hsv.h = parameter.h; + hsvControl.hsv.s = parameter.s; + updateColorSlider(); + value = hsvToRGB(hsvControl.hsv); setCurrentColor(value); - setColorPicker(value); uiCommandCallback("setColor", value); - } else { - // Swatch has no color; set swatch color to current color. - value = optionsSettings.currentColor.value; + break; + + case "setColorPerSlider": + hsvControl.hsv.v = parameter; + updateColorCircle(); + value = hsvToRGB(hsvControl.hsv); + setCurrentColor(value); + uiCommandCallback("setColor", value); + break; + + case "setColorPerSwatch": + index = optionsOverlaysIDs.indexOf(parameter); + value = optionsSettings[parameter].value; + if (value !== "") { + // Set current color to swatch color. + setCurrentColor(value); + setColorPicker(value); + uiCommandCallback("setColor", value); + } else { + // Swatch has no color; set swatch color to current color. + value = optionsSettings.currentColor.value; + Overlays.editOverlay(optionsOverlays[index], { + color: value + }); + optionsSettings[parameter].value = value; + Settings.setValue(optionsSettings[parameter].key, value); + } + break; + + case "togglePickColor": + optionsToggles.pickColor = !optionsToggles.pickColor; + index = optionsOverlaysIDs.indexOf("pickColor"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsToggles.pickColor + ? UI_ELEMENTS[optionsItems[index].type].onHoverColor + : UI_ELEMENTS[optionsItems[index].type].offHoverColor + }); + uiCommandCallback("pickColorTool", optionsToggles.pickColor); + break; + + case "setColorFromPick": + optionsToggles.pickColor = false; + index = optionsOverlaysIDs.indexOf("pickColor"); + Overlays.editOverlay(optionsOverlays[index], { + color: UI_ELEMENTS[optionsItems[index].type].offColor + }); + setCurrentColor(parameter); + setColorPicker(parameter); + break; + + case "toggleGroupSelectionBox": + optionsToggles.groupSelectionBoxButton = !optionsToggles.groupSelectionBoxButton; + index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); + Overlays.editOverlay(optionsOverlays[index], { + color: optionsToggles.groupSelectionBoxButton + ? UI_ELEMENTS[optionsItems[index].type].onHoverColor + : UI_ELEMENTS[optionsItems[index].type].offHoverColor + }); + uiCommandCallback("toggleGroupSelectionBoxTool", optionsToggles.groupSelectionBoxButton); + break; + + case "clearGroupSelection": + index = clearGroupingButtonIndex; + Overlays.editOverlay(optionsOverlays[index], { + color: optionsItems[index].properties.color + }); + Overlays.editOverlay(optionsOverlaysLabels[index], { + color: optionsItems[index].label.color + }); + optionsEnabled[index] = false; + uiCommandCallback("clearGroupSelectionTool"); + break; + + case "setGravityOn": + case "setGrabOn": + case "setCollideOn": + value = !optionsSettings[parameter].value; + optionsSettings[parameter].value = value; + optionsToggles[parameter] = value; + Settings.setValue(optionsSettings[parameter].key, value); + index = optionsOverlaysIDs.indexOf(parameter); Overlays.editOverlay(optionsOverlays[index], { color: value + ? UI_ELEMENTS[optionsItems[index].type].onHoverColor + : UI_ELEMENTS[optionsItems[index].type].offHoverColor }); - optionsSettings[parameter].value = value; - Settings.setValue(optionsSettings[parameter].key, value); - } - break; + properties = Object.clone(value ? optionsItems[index].onSublabel : optionsItems[index].offSublabel); + Overlays.editOverlay(optionsOverlaysSublabels[index], properties); + uiCommandCallback(command, value); + break; - case "togglePickColor": - optionsToggles.pickColor = !optionsToggles.pickColor; - index = optionsOverlaysIDs.indexOf("pickColor"); - Overlays.editOverlay(optionsOverlays[index], { - color: optionsToggles.pickColor - ? UI_ELEMENTS[optionsItems[index].type].onHoverColor - : UI_ELEMENTS[optionsItems[index].type].offHoverColor - }); - uiCommandCallback("pickColorTool", optionsToggles.pickColor); - break; + case "togglePhysicsPresets": + if (isPicklistOpen) { + // Close picklist. + index = optionsOverlaysIDs.indexOf("physicsPresets"); - case "setColorFromPick": - optionsToggles.pickColor = false; - index = optionsOverlaysIDs.indexOf("pickColor"); - Overlays.editOverlay(optionsOverlays[index], { - color: UI_ELEMENTS[optionsItems[index].type].offColor - }); - setCurrentColor(parameter); - setColorPicker(parameter); - break; + // Lower picklist. + isHighlightingPicklist = hoveredElementType === "picklist"; + Overlays.editOverlay(optionsOverlays[index], { + localPosition: isHighlightingPicklist + ? Vec3.sum(optionsItems[index].properties.localPosition, OPTION_HOVER_DELTA) + : optionsItems[index].properties.localPosition, + color: isHighlightingPicklist + ? UIT.colors.highlightColor + : UI_ELEMENTS.picklist.properties.color + }); + Overlays.editOverlay(optionsOverlaysSublabels[index], { + url: PICKLIST_DOWN_ARROW + }); - case "toggleGroupSelectionBox": - optionsToggles.groupSelectionBoxButton = !optionsToggles.groupSelectionBoxButton; - index = optionsOverlaysIDs.indexOf("groupSelectionBoxButton"); - Overlays.editOverlay(optionsOverlays[index], { - color: optionsToggles.groupSelectionBoxButton - ? UI_ELEMENTS[optionsItems[index].type].onHoverColor - : UI_ELEMENTS[optionsItems[index].type].offHoverColor - }); - uiCommandCallback("toggleGroupSelectionBoxTool", optionsToggles.groupSelectionBoxButton); - break; + // Hide options. + items = optionsItems[index].items; + for (i = 0, length = items.length; i < length; i += 1) { + index = optionsOverlaysIDs.indexOf(items[i]); + Overlays.editOverlay(optionsOverlays[index], { + localPosition: Vec3.ZERO, + visible: false + }); + Overlays.editOverlay(optionsOverlaysLabels[index], { + visible: false + }); + } + } - case "clearGroupSelection": - index = clearGroupingButtonIndex; - Overlays.editOverlay(optionsOverlays[index], { - color: optionsItems[index].properties.color - }); - Overlays.editOverlay(optionsOverlaysLabels[index], { - color: optionsItems[index].label.color - }); - optionsEnabled[index] = false; - uiCommandCallback("clearGroupSelectionTool"); - break; + isPicklistOpen = !isPicklistOpen; - case "setGravityOn": - case "setGrabOn": - case "setCollideOn": - value = !optionsSettings[parameter].value; - optionsSettings[parameter].value = value; - optionsToggles[parameter] = value; - Settings.setValue(optionsSettings[parameter].key, value); - index = optionsOverlaysIDs.indexOf(parameter); - Overlays.editOverlay(optionsOverlays[index], { - color: value - ? UI_ELEMENTS[optionsItems[index].type].onHoverColor - : UI_ELEMENTS[optionsItems[index].type].offHoverColor - }); - properties = Object.clone(value ? optionsItems[index].onSublabel : optionsItems[index].offSublabel); - Overlays.editOverlay(optionsOverlaysSublabels[index], properties); - uiCommandCallback(command, value); - break; + if (isPicklistOpen) { + // Open picklist. + index = optionsOverlaysIDs.indexOf("physicsPresets"); + parentID = optionsOverlays[index]; - case "togglePhysicsPresets": - if (isPicklistOpen) { + // Raise picklist. + Overlays.editOverlay(parentID, { + localPosition: Vec3.sum(optionsItems[index].properties.localPosition, PICKLIST_HOVER_DELTA) + }); + Overlays.editOverlay(optionsOverlaysSublabels[index], { + url: PICKLIST_UP_ARROW + }); + + // Show options. + items = optionsItems[index].items; + for (i = 0, length = items.length; i < length; i += 1) { + index = optionsOverlaysIDs.indexOf(items[i]); + Overlays.editOverlay(optionsOverlays[index], { + parentID: parentID, + localPosition: { x: 0, y: (i + 1) * UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, + visible: true + }); + Overlays.editOverlay(optionsOverlaysLabels[index], { + visible: true + }); + } + } + break; + + case "pickPhysicsPreset": // Close picklist. - index = optionsOverlaysIDs.indexOf("physicsPresets"); + doCommand("togglePhysicsPresets"); - // Lower picklist. - isHighlightingPicklist = hoveredElementType === "picklist"; - Overlays.editOverlay(optionsOverlays[index], { - localPosition: isHighlightingPicklist - ? Vec3.sum(optionsItems[index].properties.localPosition, OPTION_HOVER_DELTA) - : optionsItems[index].properties.localPosition, - color: isHighlightingPicklist - ? UIT.colors.highlightColor - : UI_ELEMENTS.picklist.properties.color - }); - Overlays.editOverlay(optionsOverlaysSublabels[index], { - url: PICKLIST_DOWN_ARROW + // Update picklist label. + label = optionsItems[optionsOverlaysIDs.indexOf(parameter)].label; + Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("physicsPresets")], { + url: label.url, + scale: label.scale, + localPosition: label.localPosition }); + optionsSettings.physicsPresets.value = parameter; + Settings.setValue(optionsSettings.physicsPresets.key, parameter); - // Hide options. - items = optionsItems[index].items; - for (i = 0, length = items.length; i < length; i += 1) { - index = optionsOverlaysIDs.indexOf(items[i]); - Overlays.editOverlay(optionsOverlays[index], { - localPosition: Vec3.ZERO, - visible: false - }); - Overlays.editOverlay(optionsOverlaysLabels[index], { - visible: false - }); - } - } + // Update sliders. + values = PHYSICS_SLIDER_PRESETS[parameter]; + setBarSliderValue(optionsOverlaysIDs.indexOf("gravitySlider"), values.gravity); + Settings.setValue(optionsSettings.gravitySlider.key, values.gravity); + uiCommandCallback("setGravity", values.gravity); + setBarSliderValue(optionsOverlaysIDs.indexOf("bounceSlider"), values.bounce); + Settings.setValue(optionsSettings.bounceSlider.key, values.bounce); + uiCommandCallback("setBounce", values.bounce); + setBarSliderValue(optionsOverlaysIDs.indexOf("frictionSlider"), values.friction); + Settings.setValue(optionsSettings.frictionSlider.key, values.friction); + uiCommandCallback("setFriction", values.friction); + setBarSliderValue(optionsOverlaysIDs.indexOf("densitySlider"), values.density); + Settings.setValue(optionsSettings.densitySlider.key, values.density); + uiCommandCallback("setDensity", values.density); - isPicklistOpen = !isPicklistOpen; + break; - if (isPicklistOpen) { - // Open picklist. - index = optionsOverlaysIDs.indexOf("physicsPresets"); - parentID = optionsOverlays[index]; + case "setGravity": + setPresetsLabelToCustom(); + Settings.setValue(optionsSettings.gravitySlider.key, parameter); + uiCommandCallback("setGravity", parameter); + break; + case "setBounce": + setPresetsLabelToCustom(); + Settings.setValue(optionsSettings.bounceSlider.key, parameter); + uiCommandCallback("setBounce", parameter); + break; + case "setFriction": + setPresetsLabelToCustom(); + Settings.setValue(optionsSettings.frictionSlider.key, parameter); + uiCommandCallback("setFriction", parameter); + break; + case "setDensity": + setPresetsLabelToCustom(); + Settings.setValue(optionsSettings.densitySlider.key, parameter); + uiCommandCallback("setDensity", parameter); + break; - // Raise picklist. - Overlays.editOverlay(parentID, { - localPosition: Vec3.sum(optionsItems[index].properties.localPosition, PICKLIST_HOVER_DELTA) - }); - Overlays.editOverlay(optionsOverlaysSublabels[index], { - url: PICKLIST_UP_ARROW - }); + case "closeOptions": + closeOptions(); + openMenu(); + break; - // Show options. - items = optionsItems[index].items; - for (i = 0, length = items.length; i < length; i += 1) { - index = optionsOverlaysIDs.indexOf(items[i]); - Overlays.editOverlay(optionsOverlays[index], { - parentID: parentID, - localPosition: { x: 0, y: (i + 1) * UI_ELEMENTS.picklistItem.properties.dimensions.y, z: 0 }, - visible: true - }); - Overlays.editOverlay(optionsOverlaysLabels[index], { - visible: true - }); - } - } - break; + case "clearTool": + uiCommandCallback("clearTool"); + break; - case "pickPhysicsPreset": - // Close picklist. - doCommand("togglePhysicsPresets"); - - // Update picklist label. - label = optionsItems[optionsOverlaysIDs.indexOf(parameter)].label; - Overlays.editOverlay(optionsOverlaysLabels[optionsOverlaysIDs.indexOf("physicsPresets")], { - url: label.url, - scale: label.scale, - localPosition: label.localPosition - }); - optionsSettings.physicsPresets.value = parameter; - Settings.setValue(optionsSettings.physicsPresets.key, parameter); - - // Update sliders. - values = PHYSICS_SLIDER_PRESETS[parameter]; - setBarSliderValue(optionsOverlaysIDs.indexOf("gravitySlider"), values.gravity); - Settings.setValue(optionsSettings.gravitySlider.key, values.gravity); - uiCommandCallback("setGravity", values.gravity); - setBarSliderValue(optionsOverlaysIDs.indexOf("bounceSlider"), values.bounce); - Settings.setValue(optionsSettings.bounceSlider.key, values.bounce); - uiCommandCallback("setBounce", values.bounce); - setBarSliderValue(optionsOverlaysIDs.indexOf("frictionSlider"), values.friction); - Settings.setValue(optionsSettings.frictionSlider.key, values.friction); - uiCommandCallback("setFriction", values.friction); - setBarSliderValue(optionsOverlaysIDs.indexOf("densitySlider"), values.density); - Settings.setValue(optionsSettings.densitySlider.key, values.density); - uiCommandCallback("setDensity", values.density); - - break; - - case "setGravity": - setPresetsLabelToCustom(); - Settings.setValue(optionsSettings.gravitySlider.key, parameter); - uiCommandCallback("setGravity", parameter); - break; - case "setBounce": - setPresetsLabelToCustom(); - Settings.setValue(optionsSettings.bounceSlider.key, parameter); - uiCommandCallback("setBounce", parameter); - break; - case "setFriction": - setPresetsLabelToCustom(); - Settings.setValue(optionsSettings.frictionSlider.key, parameter); - uiCommandCallback("setFriction", parameter); - break; - case "setDensity": - setPresetsLabelToCustom(); - Settings.setValue(optionsSettings.densitySlider.key, parameter); - uiCommandCallback("setDensity", parameter); - break; - - case "closeOptions": - closeOptions(); - openMenu(); - break; - - case "clearTool": - uiCommandCallback("clearTool"); - break; - - default: - App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); + default: + App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); } }; function doGripClicked(command, parameter) { switch (command) { - case "clearSwatch": - // Remove highlight ring and change swatch to current color. - Overlays.editOverlay(swatchHighlightOverlay, { visible: false }); - Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameter)], { - color: optionsSettings.currentColor.value, - dimensions: UI_ELEMENTS.swatch.properties.dimensions - }); - optionsSettings[parameter].value = ""; // Emulate Settings.getValue() returning "" for nonexistent setting. - Settings.setValue(optionsSettings[parameter].key, null); // Delete settings value. - break; - default: - App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); + case "clearSwatch": + // Remove highlight ring and change swatch to current color. + Overlays.editOverlay(swatchHighlightOverlay, { visible: false }); + Overlays.editOverlay(optionsOverlays[optionsOverlaysIDs.indexOf(parameter)], { + color: optionsSettings.currentColor.value, + dimensions: UI_ELEMENTS.swatch.properties.dimensions + }); + optionsSettings[parameter].value = ""; // Emulate Settings.getValue() returning "" for nonexistent setting. + Settings.setValue(optionsSettings[parameter].key, null); // Delete settings value. + break; + default: + App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command); } } @@ -3044,7 +3043,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(menuHeaderHeadingOverlay, { url: MENU_HEADER_HEADING_PROPERTIES.highlightURL, localPosition: Vec3.sum(MENU_HEADER_HEADING_PROPERTIES.localPosition, MENU_HEADER_HOVER_OFFSET), - emissive: true // TODO: This has no effect. + emissive: true // TODO: This has no effect. }); Overlays.editOverlay(menuHeaderBackOverlay, { color: UIT.colors.white @@ -3112,83 +3111,83 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { if (hoveredItem !== NONE) { // Unhover old item. switch (hoveredElementType) { - case "menuButton": - if (hoveredSourceOverlays === menuOverlays) { - menuButtonHoverOverlays = menuHoverOverlays; - menuButtonIconOverlays = menuIconOverlays; - } else { - menuButtonHoverOverlays = footerHoverOverlays; - menuButtonIconOverlays = footerIconOverlays; - } - Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { - localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - visible: false - }); - Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.properties.color - }); - break; - case "button": - if (hoveredSourceItems[hoveredItem].enabledColor !== undefined && optionsEnabled[hoveredItem]) { - color = hoveredSourceItems[hoveredItem].enabledColor; - } else { - color = hoveredSourceItems[hoveredItem].properties.color !== undefined - ? hoveredSourceItems[hoveredItem].properties.color - : UI_ELEMENTS.button.properties.color; - } - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - color: color, - localPosition: hoveredSourceItems[hoveredItem].properties.localPosition - }); - break; - case "toggleButton": - color = optionsToggles[hoveredSourceItems[hoveredItem].id] - ? UI_ELEMENTS.toggleButton.onColor - : UI_ELEMENTS.toggleButton.offColor; - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - color: color, - localPosition: hoveredSourceItems[hoveredItem].properties.localPosition - }); - break; - case "swatch": - Overlays.editOverlay(swatchHighlightOverlay, { visible: false }); - color = optionsSettings[hoveredSourceItems[hoveredItem].id].value; - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - dimensions: UI_ELEMENTS.swatch.properties.dimensions, - color: color === "" ? EMPTY_SWATCH_COLOR : color, - localPosition: hoveredSourceItems[hoveredItem].properties.localPosition - }); - break; - case "barSlider": - case "imageSlider": - case "colorCircle": - // Lower old slider or color circle. - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - localPosition: hoveredSourceItems[hoveredItem].properties.localPosition - }); - break; - case "picklist": - if (hoveredSourceItems[hoveredItem].type !== "picklistItem" && !isPicklistOpen) { - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - localPosition: hoveredSourceItems[hoveredItem].properties.localPosition, - color: UI_ELEMENTS.picklist.properties.color + case "menuButton": + if (hoveredSourceOverlays === menuOverlays) { + menuButtonHoverOverlays = menuHoverOverlays; + menuButtonIconOverlays = menuIconOverlays; + } else { + menuButtonHoverOverlays = footerHoverOverlays; + menuButtonIconOverlays = footerIconOverlays; + } + Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { + localPosition: UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + visible: false }); - } else { - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - color: UIT.colors.darkGray + Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.properties.color }); - } - break; - case "picklistItem": - Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { - color: UI_ELEMENTS.picklistItem.properties.color - }); - break; - case null: - // Nothing to do. - break; - default: - App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); + break; + case "button": + if (hoveredSourceItems[hoveredItem].enabledColor !== undefined && optionsEnabled[hoveredItem]) { + color = hoveredSourceItems[hoveredItem].enabledColor; + } else { + color = hoveredSourceItems[hoveredItem].properties.color !== undefined + ? hoveredSourceItems[hoveredItem].properties.color + : UI_ELEMENTS.button.properties.color; + } + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + color: color, + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition + }); + break; + case "toggleButton": + color = optionsToggles[hoveredSourceItems[hoveredItem].id] + ? UI_ELEMENTS.toggleButton.onColor + : UI_ELEMENTS.toggleButton.offColor; + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + color: color, + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition + }); + break; + case "swatch": + Overlays.editOverlay(swatchHighlightOverlay, { visible: false }); + color = optionsSettings[hoveredSourceItems[hoveredItem].id].value; + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + dimensions: UI_ELEMENTS.swatch.properties.dimensions, + color: color === "" ? EMPTY_SWATCH_COLOR : color, + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition + }); + break; + case "barSlider": + case "imageSlider": + case "colorCircle": + // Lower old slider or color circle. + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition + }); + break; + case "picklist": + if (hoveredSourceItems[hoveredItem].type !== "picklistItem" && !isPicklistOpen) { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + localPosition: hoveredSourceItems[hoveredItem].properties.localPosition, + color: UI_ELEMENTS.picklist.properties.color + }); + } else { + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + color: UIT.colors.darkGray + }); + } + break; + case "picklistItem": + Overlays.editOverlay(hoveredSourceOverlays[hoveredItem], { + color: UI_ELEMENTS.picklistItem.properties.color + }); + break; + case null: + // Nothing to do. + break; + default: + App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); } // Update status variables. @@ -3208,105 +3207,105 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Hover new item. switch (hoveredElementType) { - case "menuButton": - Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); - if (intersectionOverlays === menuOverlays) { - menuButtonHoverOverlays = menuHoverOverlays; - menuButtonIconOverlays = menuIconOverlays; - } else { - menuButtonHoverOverlays = footerHoverOverlays; - menuButtonIconOverlays = footerIconOverlays; - } - Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { - localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, - MENU_HOVER_DELTA), - visible: true - }); - Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { - color: UI_ELEMENTS.menuButton.icon.highlightColor - }); - break; - case "button": - if (intersectionEnabled[hoveredItem]) { - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[hoveredItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - color: intersectionItems[hoveredItem].highlightColor !== undefined - ? intersectionItems[hoveredItem].highlightColor - : UIT.colors.greenHighlight, - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) - }); - } - break; - case "toggleButton": - if (intersectionEnabled[hoveredItem]) { - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[hoveredItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - color: optionsToggles[intersectionItems[hoveredItem].id] - ? UI_ELEMENTS.toggleButton.onHoverColor - : UI_ELEMENTS.toggleButton.offHoverColor, - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) - }); - } - break; - case "swatch": - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[hoveredItem].properties.localPosition; - if (optionsSettings[intersectionItems[hoveredItem].id].value === "") { - // Swatch is empty; highlight it with current color. - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - color: optionsSettings.currentColor.value, - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) - }); - } else { - // Swatch is full; highlight it with ring. - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - dimensions: Vec3.subtract(UI_ELEMENTS.swatch.properties.dimensions, - { x: SWATCH_HIGHLIGHT_DELTA, y: 0, z: SWATCH_HIGHLIGHT_DELTA }), - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) - }); - Overlays.editOverlay(swatchHighlightOverlay, { - parentID: intersectionOverlays[hoveredItem], - localPosition: UI_ELEMENTS.swatchHighlight.properties.localPosition, + case "menuButton": + Feedback.play(otherSide, Feedback.HOVER_MENU_ITEM); + if (intersectionOverlays === menuOverlays) { + menuButtonHoverOverlays = menuHoverOverlays; + menuButtonIconOverlays = menuIconOverlays; + } else { + menuButtonHoverOverlays = footerHoverOverlays; + menuButtonIconOverlays = footerIconOverlays; + } + Overlays.editOverlay(menuButtonHoverOverlays[hoveredItem], { + localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, + MENU_HOVER_DELTA), visible: true }); - } - break; - case "barSlider": - case "imageSlider": - case "colorCircle": - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - localPosition = intersectionItems[hoveredItem].properties.localPosition; - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) - }); - break; - case "picklist": - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - if (!isPicklistOpen) { + Overlays.editOverlay(menuButtonIconOverlays[hoveredItem], { + color: UI_ELEMENTS.menuButton.icon.highlightColor + }); + break; + case "button": + if (intersectionEnabled[hoveredItem]) { + Feedback.play(otherSide, Feedback.HOVER_BUTTON); + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: intersectionItems[hoveredItem].highlightColor !== undefined + ? intersectionItems[hoveredItem].highlightColor + : UIT.colors.greenHighlight, + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) + }); + } + break; + case "toggleButton": + if (intersectionEnabled[hoveredItem]) { + Feedback.play(otherSide, Feedback.HOVER_BUTTON); + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: optionsToggles[intersectionItems[hoveredItem].id] + ? UI_ELEMENTS.toggleButton.onHoverColor + : UI_ELEMENTS.toggleButton.offHoverColor, + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) + }); + } + break; + case "swatch": + Feedback.play(otherSide, Feedback.HOVER_BUTTON); + localPosition = intersectionItems[hoveredItem].properties.localPosition; + if (optionsSettings[intersectionItems[hoveredItem].id].value === "") { + // Swatch is empty; highlight it with current color. + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: optionsSettings.currentColor.value, + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) + }); + } else { + // Swatch is full; highlight it with ring. + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + dimensions: Vec3.subtract(UI_ELEMENTS.swatch.properties.dimensions, + { x: SWATCH_HIGHLIGHT_DELTA, y: 0, z: SWATCH_HIGHLIGHT_DELTA }), + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) + }); + Overlays.editOverlay(swatchHighlightOverlay, { + parentID: intersectionOverlays[hoveredItem], + localPosition: UI_ELEMENTS.swatchHighlight.properties.localPosition, + visible: true + }); + } + break; + case "barSlider": + case "imageSlider": + case "colorCircle": + Feedback.play(otherSide, Feedback.HOVER_BUTTON); localPosition = intersectionItems[hoveredItem].properties.localPosition; Overlays.editOverlay(intersectionOverlays[hoveredItem], { - localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA), - color: UIT.colors.greenHighlight + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA) }); - } else { + break; + case "picklist": + Feedback.play(otherSide, Feedback.HOVER_BUTTON); + if (!isPicklistOpen) { + localPosition = intersectionItems[hoveredItem].properties.localPosition; + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA), + color: UIT.colors.greenHighlight + }); + } else { + Overlays.editOverlay(intersectionOverlays[hoveredItem], { + color: UIT.colors.greenHighlight + }); + } + break; + case "picklistItem": + Feedback.play(otherSide, Feedback.HOVER_BUTTON); Overlays.editOverlay(intersectionOverlays[hoveredItem], { color: UIT.colors.greenHighlight }); - } - break; - case "picklistItem": - Feedback.play(otherSide, Feedback.HOVER_BUTTON); - Overlays.editOverlay(intersectionOverlays[hoveredItem], { - color: UIT.colors.greenHighlight - }); - break; - case null: - // Nothing to do. - break; - default: - App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); + break; + case null: + // Nothing to do. + break; + default: + App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType); } } @@ -3469,8 +3468,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }); if (intersectionItems[intersectedItem].command) { // Cartesian planar coordinates. - x = -delta.z; // Coordinates based on rotate cylinder entity. TODO: Use FBX model instead of cylinder entity. - y = -delta.x; // "" + x = -delta.z; // Coordinates based on rotate cylinder entity. TODO: Use FBX model instead of cylinder entity. + y = -delta.x; // "" s = Math.sqrt(x * x + y * y) / hsvControl.circle.radius; h = Math.atan2(y, x) / (2 * Math.PI); if (h < 0) { @@ -3648,7 +3647,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { closeOptions(); } - Overlays.deleteOverlay(menuOriginOverlay); // Automatically deletes all other overlays because they're children. + Overlays.deleteOverlay(menuOriginOverlay); // Automatically deletes all other overlays because they're children. menuOverlays = []; menuHoverOverlays = []; menuIconOverlays = []; diff --git a/scripts/shapes/modules/uit.js b/scripts/shapes/modules/uit.js index 7ad6e67c56..f79be9fc0c 100644 --- a/scripts/shapes/modules/uit.js +++ b/scripts/shapes/modules/uit.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global UIT */ +/* global UIT:true */ UIT = (function () { // User Interface Toolkit. Global object. @@ -32,10 +32,10 @@ UIT = (function () { // Coordinate system: UI lies in x-y plane with the front surface being +z. // Offsets are relative to parents' centers. dimensions: { - canvas: { x: 0.24, y: 0.296 }, // Overall UI size. - canvasSeparation: 0.004, // Gap between Tools menu and Create panel. - handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. - handLateralOffset: 0.01, // Offset of UI in direction of palm normal. + canvas: { x: 0.24, y: 0.296 }, // Overall UI size. + canvasSeparation: 0.004, // Gap between Tools menu and Create panel. + handOffset: 0.085, // Distance from hand (wrist) joint to center of canvas. + handLateralOffset: 0.01, // Offset of UI in direction of palm normal. topMargin: 0.010, leftMargin: 0.0118, @@ -47,24 +47,24 @@ UIT = (function () { panel: { x: 0.24, y: 0.236, z: 0.008 }, footerHeight: 0.056, - itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0040 }, // Cursor intersection zone for Tools and Create items. + itemCollisionZone: { x: 0.0481, y: 0.0480, z: 0.0040 }, // Cursor intersection zone for Tools and Create items. - buttonDimensions: { x: 0.2164, y: 0.0840, z: 0.0040 }, // Default size of large single options button. + buttonDimensions: { x: 0.2164, y: 0.0840, z: 0.0040 }, // Default size of large single options button. menuButtonDimensions: { x: 0.0267, y: 0.0267, z: 0.0040 }, - menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0040 }, // Non-hovered position relative to itemCollisionZone. - menuButtonLabelYOffset: -0.00915, // Relative to itemCollisionZone. - menuButtonSublabelYOffset: -0.01775, // Relative to itemCollisionZone. + menuButtonIconOffset: { x: 0, y: 0.00935, z: -0.0040 }, // Non-hovered position relative to itemCollisionZone. + menuButtonLabelYOffset: -0.00915, // Relative to itemCollisionZone. + menuButtonSublabelYOffset: -0.01775, // Relative to itemCollisionZone. paletteItemButtonDimensions: { x: 0.0481, y: 0.0480, z: 0.0020 }, - paletteItemButtonOffset: { x: 0, y: 0, z: -0.0020 }, // Non-hovered position relative to itemCollisionZone. + paletteItemButtonOffset: { x: 0, y: 0, z: -0.0020 }, // Non-hovered position relative to itemCollisionZone. paletteItemButtonHoveredOffset: { x: 0, y: 0, z: -0.0010 }, paletteItemIconDimensions: { x: 0.024, y: 0.024, z: 0.024 }, - paletteItemIconOffset: { x: 0, y: 0, z: 0.015 }, // Non-hovered position relative to palette button. + paletteItemIconOffset: { x: 0, y: 0, z: 0.015 }, // Non-hovered position relative to palette button. horizontalRuleHeight : 0.0004, - imageOverlayOffset: 0.001 // Raise image above surface. + imageOverlayOffset: 0.001 // Raise image above surface. } }; }()); diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index 2154262229..e0a066b3c7 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -8,6 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* global Feedback, History */ + (function () { "use strict"; @@ -18,7 +20,7 @@ APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"), ENABLED_CAPTION_COLOR_OVERRIDE = "", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", - START_DELAY = 2000, // ms + START_DELAY = 2000, // ms // Application state isAppActive, @@ -132,7 +134,7 @@ intersection = {}; - if (!this instanceof Inputs) { + if (!(this instanceof Inputs)) { return new Inputs(); } @@ -225,10 +227,9 @@ isDisplaying = false, - getIntersection; // Function. + getIntersection; // Function. - - if (!this instanceof UI) { + if (!(this instanceof UI)) { return new UI(); } @@ -343,17 +344,17 @@ handles, // References. - otherEditor, // Other hand's Editor object. + otherEditor, // Other hand's Editor object. hand, laser, // Editor states. EDITOR_IDLE = 0, EDITOR_SEARCHING = 1, - EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). + EDITOR_HIGHLIGHTING = 2, // Highlighting an entity (not hovering a handle). EDITOR_GRABBING = 3, - EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. - EDITOR_HANDLE_SCALING = 5, // "" + EDITOR_DIRECT_SCALING = 4, // Scaling data are sent to other editor's EDITOR_GRABBING state. + EDITOR_HANDLE_SCALING = 5, // "" EDITOR_CLONING = 6, EDITOR_GROUPING = 7, EDITOR_STATE_STRINGS = ["EDITOR_IDLE", "EDITOR_SEARCHING", "EDITOR_HIGHLIGHTING", "EDITOR_GRABBING", @@ -362,8 +363,8 @@ // State machine. STATE_MACHINE, - intersectedEntityID = null, // Intersected entity of highlighted entity set. - rootEntityID = null, // Root entity of highlighted entity set. + intersectedEntityID = null, // Intersected entity of highlighted entity set. + rootEntityID = null, // Root entity of highlighted entity set. wasScaleTool = false, isOtherEditorEditingEntityID = false, isTriggerClicked = false, @@ -380,8 +381,8 @@ // Scaling values. isScalingWithHand = false, - isDirectScaling = false, // Modifies EDITOR_GRABBING state. - isHandleScaling = false, // "" + isDirectScaling = false, // Modifies EDITOR_GRABBING state. + isHandleScaling = false, // "" initialTargetsSeparation, initialtargetsDirection, otherTargetPosition, @@ -394,12 +395,12 @@ MIN_SCALE = 0.001, MIN_SCALE_HANDLE_DISTANCE = 0.0001, - getIntersection, // Function. + getIntersection, // Function. intersection, isUIVisible = true; - if (!this instanceof Editor) { + if (!(this instanceof Editor)) { return new Editor(); } @@ -408,12 +409,12 @@ handles = new Handles(side); function setReferences(inputs, editor) { - hand = inputs.hand(); // Object. - laser = inputs.laser(); // Object. - getIntersection = inputs.intersection; // Function. - otherEditor = editor; // Object. + hand = inputs.hand(); // Object. + laser = inputs.laser(); // Object. + getIntersection = inputs.intersection; // Function. + otherEditor = editor; // Object. - laserOffset = laser.handOffset(); // Value. + laserOffset = laser.handOffset(); // Value. highlights.setHandHighlightRadius(hand.getNearGrabRadius()); } @@ -546,8 +547,8 @@ // Initial handle offset from center of selection. selectionPositionAndOrientation = selection.getPositionAndOrientation(); - handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. - handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. + handleUnitScaleAxis = handles.scalingAxis(overlayID); // Unit vector in direction of scaling. + handleScaleDirections = handles.scalingDirections(overlayID); // Which axes to scale the selection on. scaleAxis = Vec3.multiplyQbyV(selectionPositionAndOrientation.orientation, handleUnitScaleAxis); handleTargetOffset = handles.handleOffset(overlayID) + Vec3.dot(Vec3.subtract(otherTargetPosition, Overlays.getProperty(overlayID, "position")), scaleAxis); @@ -571,7 +572,7 @@ if (isHandleScaling) { handles.finishScaling(); selection.finishHandleScaling(); - handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. + handles.grab(null); // Stop highlighting grabbed handle and resume displaying all handles. isHandleScaling = false; } } @@ -733,7 +734,7 @@ } function enterEditorGrabbing() { - selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. if (intersection.laserIntersected) { laser.setLength(laser.length()); } else { @@ -769,7 +770,7 @@ } function enterEditorDirectScaling() { - selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. + selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -790,7 +791,7 @@ } function enterEditorHandleScaling() { - selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. + selection.select(intersectedEntityID); // In case need to transition to EDITOR_GRABBING. isScalingWithHand = intersection.handIntersected; if (intersection.laserIntersected) { laser.setLength(laser.length()); @@ -812,7 +813,7 @@ function enterEditorCloning() { Feedback.play(side, Feedback.CLONE_ENTITY); - selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. + selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING. selection.cloneEntities(); intersectedEntityID = selection.intersectedEntityID(); rootEntityID = selection.rootEntityID(); @@ -938,290 +939,290 @@ // State update. switch (editorState) { - case EDITOR_IDLE: - if (!hand.valid()) { - // No transition. + case EDITOR_IDLE: + if (!hand.valid()) { + // No transition. + break; + } + setState(EDITOR_SEARCHING); break; - } - setState(EDITOR_SEARCHING); - break; - case EDITOR_SEARCHING: - if (hand.valid() - && !(intersection.overlayID && !wasTriggerClicked && isTriggerClicked - && otherEditor.isHandle(intersection.overlayID)) - && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + case EDITOR_SEARCHING: + if (hand.valid() + && !(intersection.overlayID && !wasTriggerClicked && isTriggerClicked + && otherEditor.isHandle(intersection.overlayID)) + && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) + && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked)) { + // No transition. + updateState(); + updateTool(); + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked + && otherEditor.isHandle(intersection.overlayID)) { + intersectedEntityID = otherEditor.intersectedEntityID(); + rootEntityID = otherEditor.rootEntityID(); + setState(EDITOR_HANDLE_SCALING); + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) - && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked)) { - // No transition. - updateState(); - updateTool(); - break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked - && otherEditor.isHandle(intersection.overlayID)) { - intersectedEntityID = otherEditor.intersectedEntityID(); - rootEntityID = otherEditor.rootEntityID(); - setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - setState(EDITOR_HIGHLIGHTING); - } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - if (otherEditor.isEditing(rootEntityID)) { - if (toolSelected !== TOOL_SCALE) { - setState(EDITOR_DIRECT_SCALING); - } - } else if (toolSelected === TOOL_CLONE) { - setState(EDITOR_CLONING); - } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { - setState(EDITOR_GROUPING); - } else if (toolSelected === TOOL_COLOR) { - setState(EDITOR_HIGHLIGHTING); - if (selection.applyColor(colorToolColor, false)) { - Feedback.play(side, Feedback.APPLY_PROPERTY); - } else { - Feedback.play(side, Feedback.APPLY_ERROR); - } - } else if (toolSelected === TOOL_PICK_COLOR) { - color = selection.getColor(intersection.entityID); - if (color) { - colorToolColor = color; - ui.doPickColor(colorToolColor); - } else { - Feedback.play(side, Feedback.APPLY_ERROR); - } - toolSelected = TOOL_COLOR; - ui.setToolIcon(ui.COLOR_TOOL); - } else if (toolSelected === TOOL_PHYSICS) { - setState(EDITOR_HIGHLIGHTING); - selection.applyPhysics(physicsToolPhysics); - } else if (toolSelected === TOOL_DELETE) { - setState(EDITOR_HIGHLIGHTING); - Feedback.play(side, Feedback.DELETE_ENTITY); - selection.deleteEntities(); - setState(EDITOR_SEARCHING); - } else { - setState(EDITOR_GRABBING); - } - } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_SEARCHING!"); - } - break; - case EDITOR_HIGHLIGHTING: - if (hand.valid() - && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition())) - && !(!wasTriggerClicked && isTriggerClicked - && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) - && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID - && otherEditor.isHandle(intersection.overlayID))) { - // No transition. - doUpdateState = false; - if (otherEditor.isEditing(rootEntityID) !== isOtherEditorEditingEntityID) { - doUpdateState = true; - } - if (Entities.rootOf(intersection.entityID) !== rootEntityID) { + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); - doUpdateState = true; - } - if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { - wasScaleTool = toolSelected === TOOL_SCALE; - doUpdateState = true; - } - if (toolSelected === TOOL_COLOR && intersection.entityID !== intersectedEntityID) { + setState(EDITOR_HIGHLIGHTING); + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { intersectedEntityID = intersection.entityID; - doUpdateState = true; - } - if ((toolSelected === TOOL_COLOR || toolSelected === TOOL_PHYSICS) - && intersection.entityID !== intersectedEntityID) { - intersectedEntityID = intersection.entityID; - doUpdateState = true; - } - if (doUpdateState) { - updateState(); - } - updateTool(); - break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked - && otherEditor.isHandle(intersection.overlayID)) { - intersectedEntityID = otherEditor.intersectedEntityID(); - rootEntityID = otherEditor.rootEntityID(); - setState(EDITOR_HANDLE_SCALING); - } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && !wasTriggerClicked && isTriggerClicked) { - intersectedEntityID = intersection.entityID; // May be a different entityID. - rootEntityID = Entities.rootOf(intersectedEntityID); - if (otherEditor.isEditing(rootEntityID)) { - if (toolSelected !== TOOL_SCALE) { - setState(EDITOR_DIRECT_SCALING); - } else { - log(side, "ERROR: Editor: Unexpected condition A in EDITOR_HIGHLIGHTING!"); - } - } else if (toolSelected === TOOL_CLONE) { - setState(EDITOR_CLONING); - } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { - setState(EDITOR_GROUPING); - } else if (toolSelected === TOOL_COLOR) { - if (selection.applyColor(colorToolColor, false)) { - Feedback.play(side, Feedback.APPLY_PROPERTY); - } else { - Feedback.play(side, Feedback.APPLY_ERROR); - } - } else if (toolSelected === TOOL_PICK_COLOR) { - color = selection.getColor(intersection.entityID); - if (color) { - colorToolColor = color; - ui.doPickColor(colorToolColor); + rootEntityID = Entities.rootOf(intersectedEntityID); + if (otherEditor.isEditing(rootEntityID)) { + if (toolSelected !== TOOL_SCALE) { + setState(EDITOR_DIRECT_SCALING); + } + } else if (toolSelected === TOOL_CLONE) { + setState(EDITOR_CLONING); + } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { + setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_COLOR) { + setState(EDITOR_HIGHLIGHTING); + if (selection.applyColor(colorToolColor, false)) { + Feedback.play(side, Feedback.APPLY_PROPERTY); + } else { + Feedback.play(side, Feedback.APPLY_ERROR); + } + } else if (toolSelected === TOOL_PICK_COLOR) { + color = selection.getColor(intersection.entityID); + if (color) { + colorToolColor = color; + ui.doPickColor(colorToolColor); + } else { + Feedback.play(side, Feedback.APPLY_ERROR); + } toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); + } else if (toolSelected === TOOL_PHYSICS) { + setState(EDITOR_HIGHLIGHTING); + selection.applyPhysics(physicsToolPhysics); + } else if (toolSelected === TOOL_DELETE) { + setState(EDITOR_HIGHLIGHTING); + Feedback.play(side, Feedback.DELETE_ENTITY); + selection.deleteEntities(); + setState(EDITOR_SEARCHING); } else { - Feedback.play(side, Feedback.APPLY_ERROR); + setState(EDITOR_GRABBING); } - } else if (toolSelected === TOOL_PHYSICS) { - selection.applyPhysics(physicsToolPhysics); - } else if (toolSelected === TOOL_DELETE) { - Feedback.play(side, Feedback.DELETE_ENTITY); - selection.deleteEntities(); + } else { + log(side, "ERROR: Editor: Unexpected condition in EDITOR_SEARCHING!"); + } + break; + case EDITOR_HIGHLIGHTING: + if (hand.valid() + && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition())) + && !(!wasTriggerClicked && isTriggerClicked + && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) + && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID + && otherEditor.isHandle(intersection.overlayID))) { + // No transition. + doUpdateState = false; + if (otherEditor.isEditing(rootEntityID) !== isOtherEditorEditingEntityID) { + doUpdateState = true; + } + if (Entities.rootOf(intersection.entityID) !== rootEntityID) { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + doUpdateState = true; + } + if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { + wasScaleTool = toolSelected === TOOL_SCALE; + doUpdateState = true; + } + if (toolSelected === TOOL_COLOR && intersection.entityID !== intersectedEntityID) { + intersectedEntityID = intersection.entityID; + doUpdateState = true; + } + if ((toolSelected === TOOL_COLOR || toolSelected === TOOL_PHYSICS) + && intersection.entityID !== intersectedEntityID) { + intersectedEntityID = intersection.entityID; + doUpdateState = true; + } + if (doUpdateState) { + updateState(); + } + updateTool(); + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (intersection.overlayID && !wasTriggerClicked && isTriggerClicked + && otherEditor.isHandle(intersection.overlayID)) { + intersectedEntityID = otherEditor.intersectedEntityID(); + rootEntityID = otherEditor.rootEntityID(); + setState(EDITOR_HANDLE_SCALING); + } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) + && !wasTriggerClicked && isTriggerClicked) { + intersectedEntityID = intersection.entityID; // May be a different entityID. + rootEntityID = Entities.rootOf(intersectedEntityID); + if (otherEditor.isEditing(rootEntityID)) { + if (toolSelected !== TOOL_SCALE) { + setState(EDITOR_DIRECT_SCALING); + } else { + log(side, "ERROR: Editor: Unexpected condition A in EDITOR_HIGHLIGHTING!"); + } + } else if (toolSelected === TOOL_CLONE) { + setState(EDITOR_CLONING); + } else if (toolSelected === TOOL_GROUP || toolSelected === TOOL_GROUP_BOX) { + setState(EDITOR_GROUPING); + } else if (toolSelected === TOOL_COLOR) { + if (selection.applyColor(colorToolColor, false)) { + Feedback.play(side, Feedback.APPLY_PROPERTY); + } else { + Feedback.play(side, Feedback.APPLY_ERROR); + } + } else if (toolSelected === TOOL_PICK_COLOR) { + color = selection.getColor(intersection.entityID); + if (color) { + colorToolColor = color; + ui.doPickColor(colorToolColor); + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + } else { + Feedback.play(side, Feedback.APPLY_ERROR); + } + } else if (toolSelected === TOOL_PHYSICS) { + selection.applyPhysics(physicsToolPhysics); + } else if (toolSelected === TOOL_DELETE) { + Feedback.play(side, Feedback.DELETE_ENTITY); + selection.deleteEntities(); + setState(EDITOR_SEARCHING); + } else { + setState(EDITOR_GRABBING); + } + } else if (!intersection.entityID || !intersection.editableEntity + || (!isTriggerPressed && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { setState(EDITOR_SEARCHING); } else { - setState(EDITOR_GRABBING); + log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); } - } else if (!intersection.entityID || !intersection.editableEntity - || (!isTriggerPressed && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { - setState(EDITOR_SEARCHING); - } else { - log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); - } - break; - case EDITOR_GRABBING: - if (hand.valid() && isTriggerClicked && !isGripClicked) { - // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. - // No transition. - if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { + break; + case EDITOR_GRABBING: + if (hand.valid() && isTriggerClicked && !isGripClicked) { + // Don't test intersection.intersected because when scaling with handles intersection may lag behind. + // No transition. + if ((toolSelected === TOOL_SCALE) !== wasScaleTool) { + updateState(); + wasScaleTool = toolSelected === TOOL_SCALE; + } + // updateTool(); Don't updateTool() because grip button is used to delete grabbed entity. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!isTriggerClicked) { + if (intersection.entityID && intersection.editableEntity) { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + setState(EDITOR_HIGHLIGHTING); + } else { + setState(EDITOR_SEARCHING); + } + } else if (isGripClicked) { + if (!wasGripClicked) { + Feedback.play(side, Feedback.DELETE_ENTITY); + selection.deleteEntities(); + setState(EDITOR_SEARCHING); + } + } else { + log(side, "ERROR: Editor: Unexpected condition in EDITOR_GRABBING!"); + } + break; + case EDITOR_DIRECT_SCALING: + if (hand.valid() && isTriggerClicked + && (otherEditor.isEditing(rootEntityID) || otherEditor.isHandle(intersection.overlayID))) { + // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. + // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while + // scaling with two hands. + // No transition. updateState(); - wasScaleTool = toolSelected === TOOL_SCALE; + // updateTool(); Don't updateTool() because this hand is currently using the scaling tool. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!isTriggerClicked) { + if (!intersection.entityID || !intersection.editableEntity) { + setState(EDITOR_SEARCHING); + } else { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + setState(EDITOR_HIGHLIGHTING); + } + } else if (!otherEditor.isEditing(rootEntityID)) { + // Grab highlightEntityID that was scaling and has already been set. + setState(EDITOR_GRABBING); + } else { + log(side, "ERROR: Editor: Unexpected condition in EDITOR_DIRECT_SCALING!"); } - //updateTool(); Don't updateTool() because grip button is used to delete grabbed entity. break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (!isTriggerClicked) { - if (intersection.entityID && intersection.editableEntity) { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - setState(EDITOR_HIGHLIGHTING); + case EDITOR_HANDLE_SCALING: + if (hand.valid() && isTriggerClicked && otherEditor.isEditing(rootEntityID)) { + // Don't test intersection.intersected because when scaling with handles intersection may lag behind. + // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed + // while scaling with two hands. + // No transition. + updateState(); + updateTool(); + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!isTriggerClicked) { + if (!intersection.entityID || !intersection.editableEntity) { + setState(EDITOR_SEARCHING); + } else { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + setState(EDITOR_HIGHLIGHTING); + } + } else if (!otherEditor.isEditing(rootEntityID)) { + // Grab highlightEntityID that was scaling and has already been set. + setState(EDITOR_GRABBING); + } else { + log(side, "ERROR: Editor: Unexpected condition in EDITOR_HANDLE_SCALING!"); + } + break; + case EDITOR_CLONING: + // Immediate transition out of state after cloning entities during state entry. + if (hand.valid() && isTriggerClicked) { + setState(EDITOR_GRABBING); + } else if (!hand.valid()) { + setState(EDITOR_IDLE); + } else if (!isTriggerClicked) { + if (intersection.entityID && intersection.editableEntity) { + intersectedEntityID = intersection.entityID; + rootEntityID = Entities.rootOf(intersectedEntityID); + setState(EDITOR_HIGHLIGHTING); + } else { + setState(EDITOR_SEARCHING); + } + } else { + log(side, "ERROR: Editor: Unexpected condition in EDITOR_CLONING!"); + } + break; + case EDITOR_GROUPING: + // Immediate transition out of state after updating group data during state entry. + if (hand.valid() && isTriggerClicked) { + // No transition. + break; + } + if (!hand.valid()) { + setState(EDITOR_IDLE); } else { setState(EDITOR_SEARCHING); } - } else if (isGripClicked) { - if (!wasGripClicked) { - Feedback.play(side, Feedback.DELETE_ENTITY); - selection.deleteEntities(); - setState(EDITOR_SEARCHING); - } - } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_GRABBING!"); - } - break; - case EDITOR_DIRECT_SCALING: - if (hand.valid() && isTriggerClicked - && (otherEditor.isEditing(rootEntityID) || otherEditor.isHandle(intersection.overlayID))) { - // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. - // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while - // scaling with two hands. - // No transition. - updateState(); - // updateTool(); Don't updateTool() because this hand is currently using the scaling tool. break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (!isTriggerClicked) { - if (!intersection.entityID || !intersection.editableEntity) { - setState(EDITOR_SEARCHING); - } else { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - setState(EDITOR_HIGHLIGHTING); - } - } else if (!otherEditor.isEditing(rootEntityID)) { - // Grab highlightEntityID that was scaling and has already been set. - setState(EDITOR_GRABBING); - } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_DIRECT_SCALING!"); - } - break; - case EDITOR_HANDLE_SCALING: - if (hand.valid() && isTriggerClicked && otherEditor.isEditing(rootEntityID)) { - // Don't test for intersection.intersected because when scaling with handles intersection may lag behind. - // Don't test toolSelected === TOOL_SCALE because this is a UI element and so not able to be changed while - // scaling with two hands. - // No transition. - updateState(); - updateTool(); - break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (!isTriggerClicked) { - if (!intersection.entityID || !intersection.editableEntity) { - setState(EDITOR_SEARCHING); - } else { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - setState(EDITOR_HIGHLIGHTING); - } - } else if (!otherEditor.isEditing(rootEntityID)) { - // Grab highlightEntityID that was scaling and has already been set. - setState(EDITOR_GRABBING); - } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_HANDLE_SCALING!"); - } - break; - case EDITOR_CLONING: - // Immediate transition out of state after cloning entities during state entry. - if (hand.valid() && isTriggerClicked) { - setState(EDITOR_GRABBING); - } else if (!hand.valid()) { - setState(EDITOR_IDLE); - } else if (!isTriggerClicked) { - if (intersection.entityID && intersection.editableEntity) { - intersectedEntityID = intersection.entityID; - rootEntityID = Entities.rootOf(intersectedEntityID); - setState(EDITOR_HIGHLIGHTING); - } else { - setState(EDITOR_SEARCHING); - } - } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_CLONING!"); - } - break; - case EDITOR_GROUPING: - // Immediate transition out of state after updating group data during state entry. - if (hand.valid() && isTriggerClicked) { - // No transition. - break; - } - if (!hand.valid()) { - setState(EDITOR_IDLE); - } else { - setState(EDITOR_SEARCHING); - } - break; } wasTriggerClicked = isTriggerClicked; @@ -1235,15 +1236,15 @@ function apply() { switch (editorState) { - case EDITOR_GRABBING: - if (isDirectScaling) { - applyDirectScale(); - } else if (isHandleScaling) { - applyHandleScale(); - } else { - applyGrab(); - } - break; + case EDITOR_GRABBING: + if (isDirectScaling) { + applyDirectScale(); + } else if (isHandleScaling) { + applyHandleScale(); + } else { + applyGrab(); + } + break; } } @@ -1302,8 +1303,8 @@ var groups, highlights, - selectInBoxSelection, // Selection of all entities selected. - groupSelection, // New group to add to selection. + selectInBoxSelection, // Selection of all entities selected. + groupSelection, // New group to add to selection. exludedLeftRootEntityID = null, exludedrightRootEntityID = null, excludedRootEntityIDs = [], @@ -1311,7 +1312,7 @@ hasSelectionChanged = false, isSelectInBox = false; - if (!this instanceof Grouping) { + if (!(this instanceof Grouping)) { return new Grouping(); } @@ -1611,201 +1612,201 @@ function onUICommand(command, parameter) { switch (command) { - case "scaleTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - grouping.clear(); - toolSelected = TOOL_SCALE; - ui.setToolIcon(ui.SCALE_TOOL); - ui.updateUIOverlays(); - break; - case "cloneTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - grouping.clear(); - toolSelected = TOOL_CLONE; - ui.setToolIcon(ui.CLONE_TOOL); - ui.updateUIOverlays(); - break; - case "groupTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - toolSelected = TOOL_GROUP; - ui.setToolIcon(ui.GROUP_TOOL); - ui.updateUIOverlays(); - break; - case "colorTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - grouping.clear(); - toolSelected = TOOL_COLOR; - ui.setToolIcon(ui.COLOR_TOOL); - colorToolColor = parameter; - ui.updateUIOverlays(); - break; - case "pickColorTool": - if (parameter) { + case "scaleTool": + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); grouping.clear(); - toolSelected = TOOL_PICK_COLOR; + toolSelected = TOOL_SCALE; + ui.setToolIcon(ui.SCALE_TOOL); ui.updateUIOverlays(); - } else { + break; + case "cloneTool": + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); + grouping.clear(); + toolSelected = TOOL_CLONE; + ui.setToolIcon(ui.CLONE_TOOL); + ui.updateUIOverlays(); + break; + case "groupTool": + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); + toolSelected = TOOL_GROUP; + ui.setToolIcon(ui.GROUP_TOOL); + ui.updateUIOverlays(); + break; + case "colorTool": Feedback.play(dominantHand, Feedback.EQUIP_TOOL); grouping.clear(); - toolSelected = TOOL_COLOR; - ui.updateUIOverlays(); - } - break; - case "physicsTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - grouping.clear(); - toolSelected = TOOL_PHYSICS; - ui.setToolIcon(ui.PHYSICS_TOOL); - ui.updateUIOverlays(); - break; - case "deleteTool": - Feedback.play(dominantHand, Feedback.EQUIP_TOOL); - grouping.clear(); - toolSelected = TOOL_DELETE; - ui.setToolIcon(ui.DELETE_TOOL); - ui.updateUIOverlays(); - break; - case "clearTool": - Feedback.play(dominantHand, Feedback.DROP_TOOL); - grouping.clear(); - toolSelected = TOOL_NONE; - ui.clearTool(); - ui.updateUIOverlays(); - break; - - case "groupButton": - Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); - grouping.group(); - grouping.clear(); - toolSelected = TOOL_NONE; - ui.clearTool(); - ui.updateUIOverlays(); - break; - case "ungroupButton": - Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); - grouping.ungroup(); - grouping.clear(); - toolSelected = TOOL_NONE; - ui.clearTool(); - ui.updateUIOverlays(); - break; - case "toggleGroupSelectionBoxTool": - toolSelected = parameter ? TOOL_GROUP_BOX : TOOL_GROUP; - if (toolSelected === TOOL_GROUP_BOX) { - grouping.startSelectInBox(); - } else { - grouping.stopSelectInBox(); - } - break; - case "clearGroupSelectionTool": - if (grouping.groupsCount() > 0) { - Feedback.play(dominantHand, Feedback.SELECT_ENTITY); - } - grouping.clear(); - if (toolSelected === TOOL_GROUP_BOX) { - grouping.startSelectInBox(); - } - break; - - case "setColor": - if (toolSelected === TOOL_PICK_COLOR) { toolSelected = TOOL_COLOR; ui.setToolIcon(ui.COLOR_TOOL); - } - colorToolColor = parameter; - break; - - case "setGravityOn": - // Dynamic is true if the entity has gravity or is grabbable. - if (parameter) { - physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; - physicsToolPhysics.dynamic = true; - } else { - physicsToolPhysics.gravity = Vec3.ZERO; - physicsToolPhysics.dynamic = physicsToolPhysics.userData.grabbableKey.grabbable === true; - } - break; - case "setGrabOn": - // Dynamic is true if the entity has gravity or is grabbable. - physicsToolPhysics.userData.grabbableKey.grabbable = parameter; - physicsToolPhysics.dynamic = parameter - || (physicsToolPhysics.gravity && Vec3.length(physicsToolPhysics.gravity) > 0); - break; - case "setCollideOn": - if (parameter) { - physicsToolPhysics.collisionless = false; - physicsToolPhysics.collidesWith = "static,dynamic,kinematic,myAvatar,otherAvatar"; - } else { - physicsToolPhysics.collisionless = true; - physicsToolPhysics.collidesWith = ""; - } - break; - - case "setGravity": - if (parameter !== undefined) { - // Power range 0.0, 0.5, 1.0 maps to -50.0, -9.80665, 50.0. - physicsToolGravity = 82.36785162 * Math.pow(2.214065901, parameter) - 132.36785; - if (physicsToolPhysics.dynamic === true) { // Only apply if gravity is turned on. - physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; + colorToolColor = parameter; + ui.updateUIOverlays(); + break; + case "pickColorTool": + if (parameter) { + grouping.clear(); + toolSelected = TOOL_PICK_COLOR; + ui.updateUIOverlays(); + } else { + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); + grouping.clear(); + toolSelected = TOOL_COLOR; + ui.updateUIOverlays(); } - } - break; - case "setBounce": - if (parameter !== undefined) { - // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; - physicsToolPhysics.restitution = parameter; - } - break; - case "setFriction": - if (parameter !== undefined) { - // Power range 0.0, 0.5, 1.0 maps to 0, 0.39, 1.0. - physicsToolPhysics.damping = 0.69136364 * Math.pow(2.446416831, parameter) - 0.691364; - // Power range 0.0, 0.5, 1.0 maps to 0, 0.3935, 1.0. - physicsToolPhysics.angularDamping = 0.72695892 * Math.pow(2.375594, parameter) - 0.726959; - // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; - physicsToolPhysics.friction = parameter; - } - break; - case "setDensity": - if (parameter !== undefined) { - // Power range 0.0, 0.5, 1.0 maps to 100, 1000, 10000. - physicsToolPhysics.density = Math.pow(10, 2 + 2 * parameter); - } - break; + break; + case "physicsTool": + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); + grouping.clear(); + toolSelected = TOOL_PHYSICS; + ui.setToolIcon(ui.PHYSICS_TOOL); + ui.updateUIOverlays(); + break; + case "deleteTool": + Feedback.play(dominantHand, Feedback.EQUIP_TOOL); + grouping.clear(); + toolSelected = TOOL_DELETE; + ui.setToolIcon(ui.DELETE_TOOL); + ui.updateUIOverlays(); + break; + case "clearTool": + Feedback.play(dominantHand, Feedback.DROP_TOOL); + grouping.clear(); + toolSelected = TOOL_NONE; + ui.clearTool(); + ui.updateUIOverlays(); + break; - case "autoGrab": - if (dominantHand === LEFT_HAND) { - editors[LEFT_HAND].enableAutoGrab(); - } else { - editors[RIGHT_HAND].enableAutoGrab(); - } - break; + case "groupButton": + Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); + grouping.group(); + grouping.clear(); + toolSelected = TOOL_NONE; + ui.clearTool(); + ui.updateUIOverlays(); + break; + case "ungroupButton": + Feedback.play(dominantHand, Feedback.APPLY_PROPERTY); + grouping.ungroup(); + grouping.clear(); + toolSelected = TOOL_NONE; + ui.clearTool(); + ui.updateUIOverlays(); + break; + case "toggleGroupSelectionBoxTool": + toolSelected = parameter ? TOOL_GROUP_BOX : TOOL_GROUP; + if (toolSelected === TOOL_GROUP_BOX) { + grouping.startSelectInBox(); + } else { + grouping.stopSelectInBox(); + } + break; + case "clearGroupSelectionTool": + if (grouping.groupsCount() > 0) { + Feedback.play(dominantHand, Feedback.SELECT_ENTITY); + } + grouping.clear(); + if (toolSelected === TOOL_GROUP_BOX) { + grouping.startSelectInBox(); + } + break; - case "undoAction": - if (History.hasUndo()) { - Feedback.play(dominantHand, Feedback.UNDO_ACTION); - History.undo(); - } else { - Feedback.play(dominantHand, Feedback.GENERAL_ERROR); - } - break; - case "redoAction": - if (History.hasRedo()) { - Feedback.play(dominantHand, Feedback.REDO_ACTION); - History.redo(); - } else { - Feedback.play(dominantHand, Feedback.GENERAL_ERROR); - } - break; + case "setColor": + if (toolSelected === TOOL_PICK_COLOR) { + toolSelected = TOOL_COLOR; + ui.setToolIcon(ui.COLOR_TOOL); + } + colorToolColor = parameter; + break; - default: - log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); + case "setGravityOn": + // Dynamic is true if the entity has gravity or is grabbable. + if (parameter) { + physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; + physicsToolPhysics.dynamic = true; + } else { + physicsToolPhysics.gravity = Vec3.ZERO; + physicsToolPhysics.dynamic = physicsToolPhysics.userData.grabbableKey.grabbable === true; + } + break; + case "setGrabOn": + // Dynamic is true if the entity has gravity or is grabbable. + physicsToolPhysics.userData.grabbableKey.grabbable = parameter; + physicsToolPhysics.dynamic = parameter + || (physicsToolPhysics.gravity && Vec3.length(physicsToolPhysics.gravity) > 0); + break; + case "setCollideOn": + if (parameter) { + physicsToolPhysics.collisionless = false; + physicsToolPhysics.collidesWith = "static,dynamic,kinematic,myAvatar,otherAvatar"; + } else { + physicsToolPhysics.collisionless = true; + physicsToolPhysics.collidesWith = ""; + } + break; + + case "setGravity": + if (parameter !== undefined) { + // Power range 0.0, 0.5, 1.0 maps to -50.0, -9.80665, 50.0. + physicsToolGravity = 82.36785162 * Math.pow(2.214065901, parameter) - 132.36785; + if (physicsToolPhysics.dynamic === true) { // Only apply if gravity is turned on. + physicsToolPhysics.gravity = { x: 0, y: physicsToolGravity, z: 0 }; + } + } + break; + case "setBounce": + if (parameter !== undefined) { + // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; + physicsToolPhysics.restitution = parameter; + } + break; + case "setFriction": + if (parameter !== undefined) { + // Power range 0.0, 0.5, 1.0 maps to 0, 0.39, 1.0. + physicsToolPhysics.damping = 0.69136364 * Math.pow(2.446416831, parameter) - 0.691364; + // Power range 0.0, 0.5, 1.0 maps to 0, 0.3935, 1.0. + physicsToolPhysics.angularDamping = 0.72695892 * Math.pow(2.375594, parameter) - 0.726959; + // Linear range from 0.0, 0.5, 1.0 maps to 0.0, 0.5, 1.0; + physicsToolPhysics.friction = parameter; + } + break; + case "setDensity": + if (parameter !== undefined) { + // Power range 0.0, 0.5, 1.0 maps to 100, 1000, 10000. + physicsToolPhysics.density = Math.pow(10, 2 + 2 * parameter); + } + break; + + case "autoGrab": + if (dominantHand === LEFT_HAND) { + editors[LEFT_HAND].enableAutoGrab(); + } else { + editors[RIGHT_HAND].enableAutoGrab(); + } + break; + + case "undoAction": + if (History.hasUndo()) { + Feedback.play(dominantHand, Feedback.UNDO_ACTION); + History.undo(); + } else { + Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + } + break; + case "redoAction": + if (History.hasRedo()) { + Feedback.play(dominantHand, Feedback.REDO_ACTION); + History.redo(); + } else { + Feedback.play(dominantHand, Feedback.GENERAL_ERROR); + } + break; + + default: + log("ERROR: Unexpected command in onUICommand(): " + command + ", " + parameter); } } function startApp() { ui.display(); - update(); // Start main update loop. + update(); // Start main update loop. } function stopApp() { @@ -1823,9 +1824,9 @@ function onAppButtonClicked() { var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications", - EDIT_ERROR = 4, // Per notifications.js. + EDIT_ERROR = 4, // Per notifications.js. INSUFFICIENT_PERMISSIONS_ERROR_MSG = - "You do not have the necessary permissions to edit on this domain."; // Same as edit.js. + "You do not have the necessary permissions to edit on this domain."; // Same as edit.js. // Application tablet/toolbar button clicked. if (!isAppActive && !(Entities.canRez() || Entities.canRezTmp())) { @@ -2030,6 +2031,6 @@ tablet = null; } - Script.setTimeout(setUp, START_DELAY); // Delay start so that Entities.canRez() work; button is enabled correctly. + Script.setTimeout(setUp, START_DELAY); // Delay start so that Entities.canRez() work; button is enabled correctly. Script.scriptEnding.connect(tearDown); }()); diff --git a/scripts/shapes/utilities/utilities.js b/scripts/shapes/utilities/utilities.js index 198e45c256..71986455d4 100644 --- a/scripts/shapes/utilities/utilities.js +++ b/scripts/shapes/utilities/utilities.js @@ -40,7 +40,7 @@ if (typeof Uuid.SELF !== "string") { if (typeof Entities.rootOf !== "function") { Entities.rootOfCache = { - CACHE_ENTRY_EXPIRY_TIME: 1000 // ms + CACHE_ENTRY_EXPIRY_TIME: 1000 // ms }; Entities.rootOf = function (entityID) { @@ -72,7 +72,7 @@ if (typeof Entities.rootOf !== "function") { if (typeof Entities.hasEditableRoot !== "function") { Entities.hasEditableRootCache = { - CACHE_ENTRY_EXPIRY_TIME: 5000 // ms + CACHE_ENTRY_EXPIRY_TIME: 5000 // ms }; Entities.hasEditableRoot = function (entityID) { @@ -114,10 +114,10 @@ if (typeof Object.merge !== "function") { var a = JSON.stringify(objectA), b = JSON.stringify(objectB); if (a === "{}") { - return JSON.parse(b); // Always return a new object. + return JSON.parse(b); // Always return a new object. } if (b === "{}") { - return JSON.parse(a); // "" + return JSON.parse(a); // "" } return JSON.parse(a.slice(0, -1) + "," + b.slice(1)); }; From 680235a7b1f4c8ac0b0407de2320ab5dee8c1e8f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 1 Oct 2017 13:22:48 +1300 Subject: [PATCH 403/504] += 1 ==> ++ --- scripts/shapes/modules/createPalette.js | 6 ++-- scripts/shapes/modules/groups.js | 20 ++++++------ scripts/shapes/modules/hand.js | 4 +-- scripts/shapes/modules/handles.js | 18 +++++------ scripts/shapes/modules/highlights.js | 6 ++-- scripts/shapes/modules/history.js | 22 ++++++------- scripts/shapes/modules/selection.js | 28 ++++++++--------- scripts/shapes/modules/toolsMenu.js | 42 ++++++++++++------------- scripts/shapes/shapes.js | 10 +++--- 9 files changed, 78 insertions(+), 78 deletions(-) diff --git a/scripts/shapes/modules/createPalette.js b/scripts/shapes/modules/createPalette.js index 505853f482..af22c4e4ff 100644 --- a/scripts/shapes/modules/createPalette.js +++ b/scripts/shapes/modules/createPalette.js @@ -351,12 +351,12 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { var i, length; - for (i = 0, length = staticOverlays.length; i < length; i += 1) { + for (i = 0, length = staticOverlays.length; i < length; i++) { Overlays.editOverlay(staticOverlays[i], { visible: visible }); } if (!visible) { - for (i = 0, length = paletteItemHoverOverlays.length; i < length; i += 1) { + for (i = 0, length = paletteItemHoverOverlays.length; i < length; i++) { Overlays.editOverlay(paletteItemHoverOverlays[i], { visible: false }); } } @@ -486,7 +486,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { palettePanelOverlay = Overlays.addOverlay("cube", properties); // Palette items. - for (i = 0, length = PALETTE_ITEMS.length; i < length; i += 1) { + for (i = 0, length = PALETTE_ITEMS.length; i < length; i++) { // Collision overlay. properties = Object.clone(PALETTE_ITEM.properties); properties.parentID = palettePanelOverlay; diff --git a/scripts/shapes/modules/groups.js b/scripts/shapes/modules/groups.js index 6147df98af..223992d646 100644 --- a/scripts/shapes/modules/groups.js +++ b/scripts/shapes/modules/groups.js @@ -56,9 +56,9 @@ Groups = function () { j, lengthJ; - for (i = 0, lengthI = rootEntityIDs.length; i < lengthI; i += 1) { + for (i = 0, lengthI = rootEntityIDs.length; i < lengthI; i++) { if (excludes.indexOf(rootEntityIDs[i]) === -1) { - for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { result.push(selections[i][j]); } } @@ -98,8 +98,8 @@ Groups = function () { // collisionless. (Don't need to worry about other groups physics properties because only those of the first entity in // the linkset are used by High Fidelity.) See Selection.applyPhysics(). if (selections[0][0].dynamic) { - for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { - for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + for (i = 1, lengthI = selections.length; i < lengthI; i++) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { undoData.push({ entityID: selections[i][j].id, properties: { @@ -120,7 +120,7 @@ Groups = function () { // Make the first entity in the first group the root and link the first entities of all other groups to it. rootID = rootEntityIDs[0]; - for (i = 1, lengthI = rootEntityIDs.length; i < lengthI; i += 1) { + for (i = 1, lengthI = rootEntityIDs.length; i < lengthI; i++) { undoData.push({ entityID: rootEntityIDs[i], properties: { parentID: Uuid.NULL } @@ -136,7 +136,7 @@ Groups = function () { // Update selection. rootEntityIDs.splice(1, rootEntityIDs.length - 1); - for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { + for (i = 1, lengthI = selections.length; i < lengthI; i++) { selections[i][0].parentID = rootID; selections[0] = selections[0].concat(selections[i]); } @@ -193,7 +193,7 @@ Groups = function () { // Compile information on immediate children. rootID = rootEntityIDs[0]; - for (i = 1, lengthI = selections[0].length; i < lengthI; i += 1) { + for (i = 1, lengthI = selections[0].length; i < lengthI; i++) { if (selections[0][i].parentID === rootID) { childrenIDs.push(selections[0][i].id); childrenIndexes.push(i); @@ -205,7 +205,7 @@ Groups = function () { // Unlink children. isUngroupAll = hasSoloChildren !== hasGroupChildren; - for (i = childrenIDs.length - 1; i >= 0; i -= 1) { + for (i = childrenIDs.length - 1; i >= 0; i--) { if (isUngroupAll || childrenIndexIsGroup[i]) { undoData.push({ entityID: childrenIDs[i], @@ -227,8 +227,8 @@ Groups = function () { // If root group has physics, reset child groups to defaults for dynamic and collisionless. See // Selection.applyPhysics(). if (selections[0][0].dynamic) { - for (i = 1, lengthI = selections.length; i < lengthI; i += 1) { - for (j = 0, lengthJ = selections[i].length; j < lengthJ; j += 1) { + for (i = 1, lengthI = selections.length; i < lengthI; i++) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { undoData.push({ entityID: selections[i][j].id, properties: { diff --git a/scripts/shapes/modules/hand.js b/scripts/shapes/modules/hand.js index 1e8af70e57..29ed6bfd96 100644 --- a/scripts/shapes/modules/hand.js +++ b/scripts/shapes/modules/hand.js @@ -160,7 +160,7 @@ Hand = function (side) { if (overlayIDs.length > 1) { // Find closest overlay. overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); - for (i = 1, length = overlayIDs.length; i < length; i += 1) { + for (i = 1, length = overlayIDs.length; i < length; i++) { distance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); if (distance > overlayDistance) { @@ -191,7 +191,7 @@ Hand = function (side) { if (entityIDs.length > 1) { // Find smallest, editable entity. entitySize = HALF_TREE_SCALE; - for (i = 0, length = entityIDs.length; i < length; i += 1) { + for (i = 0, length = entityIDs.length; i < length; i++) { if (Entities.hasEditableRoot(entityIDs[i])) { size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); if (size < entitySize) { diff --git a/scripts/shapes/modules/handles.js b/scripts/shapes/modules/handles.js index c1d41b6066..d7627e8626 100644 --- a/scripts/shapes/modules/handles.js +++ b/scripts/shapes/modules/handles.js @@ -187,7 +187,7 @@ Handles = function (side) { // At right-most and opposite corners of bounding box. cameraUp = Quat.getUp(Camera.orientation); maxCrossProductScale = 0; - for (i = 0; i < NUM_CORNERS; i += 1) { + for (i = 0; i < NUM_CORNERS; i++) { cornerPosition = Vec3.sum(boundingBoxCenter, Vec3.multiplyQbyV(boundingBoxOrientation, Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions))); @@ -202,7 +202,7 @@ Handles = function (side) { cornerIndexes[0] = leftCornerIndex; cornerIndexes[1] = rightCornerIndex; cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + for (i = 0; i < NUM_CORNER_HANDLES; i++) { cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { parentID: rootEntityID, localPosition: Vec3.sum(boundingBoxLocalCenter, @@ -224,7 +224,7 @@ Handles = function (side) { if (!isMultipleEntities) { faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + for (i = 0; i < NUM_FACE_HANDLES; i++) { if (!isSuppressZAxis || FACE_HANDLE_OVERLAY_AXES[i].z === 0) { faceHandleOverlays[i] = Overlays.addOverlay("shape", { parentID: rootEntityID, @@ -263,7 +263,7 @@ Handles = function (side) { }); // Corner scale handles. - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + for (i = 0; i < NUM_CORNER_HANDLES; i++) { Overlays.editOverlay(cornerHandleOverlays[i], { localPosition: Vec3.sum(scalingBoundingBoxDimensions, Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter)) @@ -272,7 +272,7 @@ Handles = function (side) { // Face scale handles. if (faceHandleOverlays.length > 0) { - for (i = 0; i < NUM_FACE_HANDLES; i += 1) { + for (i = 0; i < NUM_FACE_HANDLES; i++) { Overlays.editOverlay(faceHandleOverlays[i], { localPosition: Vec3.sum(scalingBoundingBoxDimensions, Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], @@ -314,7 +314,7 @@ Handles = function (side) { i, length; - for (i = 0, length = cornerHandleOverlays.length; i < length; i += 1) { + for (i = 0, length = cornerHandleOverlays.length; i < length; i++) { overlay = cornerHandleOverlays[i]; Overlays.editOverlay(overlay, { visible: isVisible && (isShowAll || overlay === overlayID), @@ -323,7 +323,7 @@ Handles = function (side) { }); } - for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { + for (i = 0, length = faceHandleOverlays.length; i < length; i++) { overlay = faceHandleOverlays[i]; Overlays.editOverlay(overlay, { visible: isVisible && (isShowAll || overlay === overlayID), @@ -338,10 +338,10 @@ Handles = function (side) { length; Overlays.deleteOverlay(boundingBoxOverlay); - for (i = 0; i < NUM_CORNER_HANDLES; i += 1) { + for (i = 0; i < NUM_CORNER_HANDLES; i++) { Overlays.deleteOverlay(cornerHandleOverlays[i]); } - for (i = 0, length = faceHandleOverlays.length; i < length; i += 1) { + for (i = 0, length = faceHandleOverlays.length; i < length; i++) { Overlays.deleteOverlay(faceHandleOverlays[i]); } diff --git a/scripts/shapes/modules/highlights.js b/scripts/shapes/modules/highlights.js index 1af309a38e..98c200a808 100644 --- a/scripts/shapes/modules/highlights.js +++ b/scripts/shapes/modules/highlights.js @@ -104,14 +104,14 @@ Highlights = function (side) { editEntityOverlay(0, selection[entityIndex], overlayColor); } else { // Add/edit entity overlays for all entities in selection. - for (i = 0, length = selection.length; i < length; i += 1) { + for (i = 0, length = selection.length; i < length; i++) { maybeAddEntityOverlay(i); editEntityOverlay(i, selection[i], overlayColor); } } // Delete extra entity overlays. - for (i = entityOverlays.length - 1, length = selection.length; i >= length; i -= 1) { + for (i = entityOverlays.length - 1, length = selection.length; i >= length; i--) { Overlays.deleteOverlay(entityOverlays[i]); entityOverlays.splice(i, 1); } @@ -141,7 +141,7 @@ Highlights = function (side) { Overlays.editOverlay(boundingBoxOverlay, { visible: false }); // Delete entity overlays. - for (i = 0, length = entityOverlays.length; i < length; i += 1) { + for (i = 0, length = entityOverlays.length; i < length; i++) { Overlays.deleteOverlay(entityOverlays[i]); } entityOverlays = []; diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 6451000f04..4c1b7857b8 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -96,7 +96,7 @@ History = (function () { } history.push({ undoData: undoData, redoData: redoData }); - undoPosition += 1; + undoPosition++; undoData = {}; redoData = {}; @@ -112,7 +112,7 @@ History = (function () { length; if (properties) { - for (i = 0, length = properties.length; i < length; i += 1) { + for (i = 0, length = properties.length; i < length; i++) { if (properties[i].entityID === oldEntityID) { properties[i].entityID = newEntityID; } @@ -123,7 +123,7 @@ History = (function () { } } - for (i = 0, length = history.length; i < length; i += 1) { + for (i = 0, length = history.length; i < length; i++) { if (history[i].undoData) { updateEntityIDsInProperty(history[i].undoData.setProperties); updateEntityIDsInProperty(history[i].undoData.createEntities); @@ -155,14 +155,14 @@ History = (function () { undoData = history[undoPosition].undoData; if (undoData.createEntities) { - for (i = 0, length = undoData.createEntities.length; i < length; i += 1) { + for (i = 0, length = undoData.createEntities.length; i < length; i++) { entityID = Entities.addEntity(undoData.createEntities[i].properties); updateEntityIDs(undoData.createEntities[i].entityID, entityID); } } if (undoData.setProperties) { - for (i = 0, length = undoData.setProperties.length; i < length; i += 1) { + for (i = 0, length = undoData.setProperties.length; i < length; i++) { Entities.editEntity(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); if (undoData.setProperties[i].properties.gravity) { kickPhysics(undoData.setProperties[i].entityID); @@ -171,12 +171,12 @@ History = (function () { } if (undoData.deleteEntities) { - for (i = 0, length = undoData.deleteEntities.length; i < length; i += 1) { + for (i = 0, length = undoData.deleteEntities.length; i < length; i++) { Entities.deleteEntity(undoData.deleteEntities[i].entityID); } } - undoPosition -= 1; + undoPosition--; } } @@ -191,14 +191,14 @@ History = (function () { redoData = history[undoPosition + 1].redoData; if (redoData.createEntities) { - for (i = 0, length = redoData.createEntities.length; i < length; i += 1) { + for (i = 0, length = redoData.createEntities.length; i < length; i++) { entityID = Entities.addEntity(redoData.createEntities[i].properties); updateEntityIDs(redoData.createEntities[i].entityID, entityID); } } if (redoData.setProperties) { - for (i = 0, length = redoData.setProperties.length; i < length; i += 1) { + for (i = 0, length = redoData.setProperties.length; i < length; i++) { Entities.editEntity(redoData.setProperties[i].entityID, redoData.setProperties[i].properties); if (redoData.setProperties[i].properties.gravity) { kickPhysics(redoData.setProperties[i].entityID); @@ -207,12 +207,12 @@ History = (function () { } if (redoData.deleteEntities) { - for (i = 0, length = redoData.deleteEntities.length; i < length; i += 1) { + for (i = 0, length = redoData.deleteEntities.length; i < length; i++) { Entities.deleteEntity(redoData.deleteEntities[i].entityID); } } - undoPosition += 1; + undoPosition++; } } diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index faa514d641..5c59c20aec 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -70,7 +70,7 @@ SelectionManager = function (side) { } children = Entities.getChildrenIDs(id); - for (i = 0, length = children.length; i < length; i += 1) { + for (i = 0, length = children.length; i < length; i++) { if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { traverseEntityTree(children[i], selection, selectionIDs, selectionProperties); } @@ -164,7 +164,7 @@ SelectionManager = function (side) { min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); inverseOrientation = Quat.inverse(rootOrientation); - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { registration = selection[i].registrationPoint; corners[0] = { x: -registration.x, y: -registration.y, z: -registration.z }; @@ -180,7 +180,7 @@ SelectionManager = function (side) { rotation = selection[i].rotation; dimensions = selection[i].dimensions; - for (j = 0; j < NUM_CORNERS; j += 1) { + for (j = 0; j < NUM_CORNERS; j++) { // Corner position in world coordinates. corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); // Corner position in root entity coordinates. @@ -247,7 +247,7 @@ SelectionManager = function (side) { startOrientation = selection[0].rotation; // Disable entity set's physics. - for (i = selection.length - 1; i >= 0; i -= 1) { + for (i = selection.length - 1; i >= 0; i--) { Entities.editEntity(selection[i].id, { dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. collisionless: true, // So that entity doesn't bump us about as we resize the entity. @@ -267,7 +267,7 @@ SelectionManager = function (side) { // Restore entity set's physics. // Note: Need to apply children-first in order to avoid children's relative positions sometimes drifting. - for (i = selection.length - 1; i >= 0; i -= 1) { + for (i = selection.length - 1; i >= 0; i--) { Entities.editEntity(selection[i].id, { dynamic: selection[i].dynamic, collisionless: selection[i].collisionless @@ -360,7 +360,7 @@ SelectionManager = function (side) { }); // Scale and position children. - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { Entities.editEntity(selection[i].id, { dimensions: Vec3.multiply(factor, selection[i].dimensions), localPosition: Vec3.multiply(factor, selection[i].localPosition) @@ -400,7 +400,7 @@ SelectionManager = function (side) { }); // Final scale and position of children. - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { undoData.push({ entityID: selection[i].id, properties: { @@ -473,7 +473,7 @@ SelectionManager = function (side) { // Scale and position children. // Only corner handles are used for scaling multiple entities so scale factor is the same in all dimensions. // Therefore don't need to take into account orientation relative to parent when scaling local position. - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { Entities.editEntity(selection[i].id, { dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) @@ -513,7 +513,7 @@ SelectionManager = function (side) { }); // Final scale and position of children. - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { undoData.push({ entityID: selection[i].id, properties: { @@ -555,12 +555,12 @@ SelectionManager = function (side) { length; // Map parent IDs; find intersectedEntityID's index. - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { if (selection[i].id === intersectedEntityID) { intersectedEntityIndex = i; } parentID = selection[i].parentID; - for (j = 0; j < i; j += 1) { + for (j = 0; j < i; j++) { if (parentID === selection[j].id) { parentIDIndexes[i] = j; break; @@ -569,7 +569,7 @@ SelectionManager = function (side) { } // Clone entities. - for (i = 0, length = selection.length; i < length; i += 1) { + for (i = 0, length = selection.length; i < length; i++) { properties = Entities.getEntityProperties(selection[i].id); if (i > 0) { properties.parentID = selection[parentIDIndexes[i]].id; @@ -600,7 +600,7 @@ SelectionManager = function (side) { length; if (isApplyToAll) { - for (i = 0, length = selection.length; i < length; i += 1) { + for (i = 0, length = selection.length; i < length; i++) { properties = Entities.getEntityProperties(selection[i].id, ["type", "color"]); if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { Entities.editEntity(selection[i].id, { @@ -680,7 +680,7 @@ SelectionManager = function (side) { dynamic: physicsProperties.dynamic, collisionless: physicsProperties.dynamic || physicsProperties.collisionless }; - for (i = 1, length = selection.length; i < length; i += 1) { + for (i = 1, length = selection.length; i < length; i++) { undoData.push({ entityID: selection[i].id, properties: { diff --git a/scripts/shapes/modules/toolsMenu.js b/scripts/shapes/modules/toolsMenu.js index 6bd7d4a77b..08476f9251 100644 --- a/scripts/shapes/modules/toolsMenu.js +++ b/scripts/shapes/modules/toolsMenu.js @@ -2094,7 +2094,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { Overlays.editOverlay(menuHeaderIconOverlay, { visible: false }); // Display menu items. - for (i = 0, length = MENU_ITEMS.length; i < length; i += 1) { + for (i = 0, length = MENU_ITEMS.length; i < length; i++) { properties = Object.clone(UI_ELEMENTS[MENU_ITEMS[i].type].properties); properties = Object.merge(properties, MENU_ITEMS[i].properties); properties.visible = isVisible; @@ -2144,7 +2144,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { var i, length; - for (i = 0, length = menuOverlays.length; i < length; i += 1) { + for (i = 0, length = menuOverlays.length; i < length; i++) { Overlays.deleteOverlay(menuOverlays[i]); } @@ -2192,7 +2192,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Open specified options panel. optionsItems = OPTONS_PANELS[menuItem.toolOptions]; parentID = menuPanelOverlay; - for (i = 0, length = optionsItems.length; i < length; i += 1) { + for (i = 0, length = optionsItems.length; i < length; i++) { properties = Object.clone(UI_ELEMENTS[optionsItems[i].type].properties); if (optionsItems[i].properties) { properties = Object.merge(properties, optionsItems[i].properties); @@ -2447,7 +2447,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { swatchHighlightOverlay = null; } - for (i = 0, length = optionsOverlays.length; i < length; i += 1) { + for (i = 0, length = optionsOverlays.length; i < length; i++) { Overlays.deleteOverlay(optionsOverlays[i]); } optionsOverlays = []; @@ -2477,7 +2477,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { length; // Display footer items. - for (i = 0, length = FOOTER_ITEMS.length; i < length; i += 1) { + for (i = 0, length = FOOTER_ITEMS.length; i < length; i++) { properties = Object.clone(UI_ELEMENTS[FOOTER_ITEMS[i].type].properties); properties = Object.merge(properties, FOOTER_ITEMS[i].properties); properties.visible = isVisible; @@ -2812,7 +2812,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Hide options. items = optionsItems[index].items; - for (i = 0, length = items.length; i < length; i += 1) { + for (i = 0, length = items.length; i < length; i++) { index = optionsOverlaysIDs.indexOf(items[i]); Overlays.editOverlay(optionsOverlays[index], { localPosition: Vec3.ZERO, @@ -2841,7 +2841,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Show options. items = optionsItems[index].items; - for (i = 0, length = items.length; i < length; i += 1) { + for (i = 0, length = items.length; i < length; i++) { index = optionsOverlaysIDs.indexOf(items[i]); Overlays.editOverlay(optionsOverlays[index], { parentID: parentID, @@ -2947,53 +2947,53 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { var i, length; - for (i = 0, length = staticOverlays.length; i < length; i += 1) { + for (i = 0, length = staticOverlays.length; i < length; i++) { Overlays.editOverlay(staticOverlays[i], { visible: visible }); } if (isOptionsOpen) { Overlays.editOverlay(menuHeaderBackOverlay, { visible: visible }); Overlays.editOverlay(menuHeaderIconOverlay, { visible: visible }); - for (i = 0, length = optionsOverlays.length; i < length; i += 1) { + for (i = 0, length = optionsOverlays.length; i < length; i++) { Overlays.editOverlay(optionsOverlays[i], { visible: visible }); } - for (i = 0, length = optionsOverlaysLabels.length; i < length; i += 1) { + for (i = 0, length = optionsOverlaysLabels.length; i < length; i++) { Overlays.editOverlay(optionsOverlaysLabels[i], { visible: visible }); } - for (i = 0, length = optionsOverlaysSublabels.length; i < length; i += 1) { + for (i = 0, length = optionsOverlaysSublabels.length; i < length; i++) { Overlays.editOverlay(optionsOverlaysSublabels[i], { visible: visible }); } - for (i = 0, length = optionsExtraOverlays.length; i < length; i += 1) { + for (i = 0, length = optionsExtraOverlays.length; i < length; i++) { Overlays.editOverlay(optionsExtraOverlays[i], { visible: visible }); } } else { - for (i = 0, length = menuOverlays.length; i < length; i += 1) { + for (i = 0, length = menuOverlays.length; i < length; i++) { Overlays.editOverlay(menuOverlays[i], { visible: visible }); } - for (i = 0, length = menuIconOverlays.length; i < length; i += 1) { + for (i = 0, length = menuIconOverlays.length; i < length; i++) { Overlays.editOverlay(menuIconOverlays[i], { visible: visible }); } - for (i = 0, length = menuLabelOverlays.length; i < length; i += 1) { + for (i = 0, length = menuLabelOverlays.length; i < length; i++) { Overlays.editOverlay(menuLabelOverlays[i], { visible: visible }); } if (!visible) { - for (i = 0, length = menuHoverOverlays.length; i < length; i += 1) { + for (i = 0, length = menuHoverOverlays.length; i < length; i++) { Overlays.editOverlay(menuHoverOverlays[i], { visible: false }); } } } - for (i = 0, length = footerOverlays.length; i < length; i += 1) { + for (i = 0, length = footerOverlays.length; i < length; i++) { Overlays.editOverlay(footerOverlays[i], { visible: visible }); } - for (i = 0, length = footerIconOverlays.length; i < length; i += 1) { + for (i = 0, length = footerIconOverlays.length; i < length; i++) { Overlays.editOverlay(footerIconOverlays[i], { visible: visible }); } - for (i = 0, length = footerLabelOverlays.length; i < length; i += 1) { + for (i = 0, length = footerLabelOverlays.length; i < length; i++) { Overlays.editOverlay(footerLabelOverlays[i], { visible: visible }); } if (!visible) { - for (i = 0, length = footerHoverOverlays.length; i < length; i += 1) { + for (i = 0, length = footerHoverOverlays.length; i < length; i++) { Overlays.editOverlay(footerHoverOverlays[i], { visible: false }); } } @@ -3621,7 +3621,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { isGroupButtonEnabled = false; isUngroupButtonEnabled = false; isClearGroupingButtonEnabled = false; - for (i = 0, length = OPTONS_PANELS.groupOptions.length; i < length; i += 1) { + for (i = 0, length = OPTONS_PANELS.groupOptions.length; i < length; i++) { id = OPTONS_PANELS.groupOptions[i].id; if (id === "groupButton") { groupButtonIndex = i; diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index e0a066b3c7..573a9445e7 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -1330,7 +1330,7 @@ i, length; children = Entities.getChildrenIDs(id); - for (i = 0, length = children.length; i < length; i += 1) { + for (i = 0, length = children.length; i < length; i++) { if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { childrenIDs.push(children[i]); traverseEntityTree(children[i]); @@ -1381,7 +1381,7 @@ isInside = Math.abs(cornerPosition.x) <= boundingBoxHalfDimensions.x && Math.abs(cornerPosition.y) <= boundingBoxHalfDimensions.y && Math.abs(cornerPosition.z) <= boundingBoxHalfDimensions.z; - i += 1; + i++; } return isInside; @@ -1424,7 +1424,7 @@ if (selectInBoxSelection.count() > 1) { boundingBox = selectInBoxSelection.boundingBox(); entityIDs = Entities.findEntities(boundingBox.center, Vec3.length(boundingBox.dimensions) / 2); - for (i = 0, lengthI = entityIDs.length; i < lengthI; i += 1) { + for (i = 0, lengthI = entityIDs.length; i < lengthI; i++) { entityID = entityIDs[i]; if (checkedEntityIDs.indexOf(entityID) === -1) { rootID = Entities.rootOf(entityID); @@ -1435,7 +1435,7 @@ lengthJ = groupIDs.length; while (doIncludeGroup && j < lengthJ) { doIncludeGroup = isInsideBoundingBox(groupIDs[j], boundingBox); - j += 1; + j++; } checkedEntityIDs = checkedEntityIDs.concat(groupIDs); if (doIncludeGroup) { @@ -1465,7 +1465,7 @@ rootEntityIDs = groups.rootEntityIDs(); if (rootEntityIDs.length > 0) { selectInBoxSelection.select(rootEntityIDs[0]); - for (i = 1, length = rootEntityIDs.length; i < length; i += 1) { + for (i = 1, length = rootEntityIDs.length; i < length; i++) { selectInBoxSelection.append(rootEntityIDs[i]); } } From 0bd67d494d60623dce8438f11e3a862fa581cb37 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 2 Oct 2017 19:27:26 +1300 Subject: [PATCH 404/504] Fix UI sometimes turning off/on/off as hand enters entity --- scripts/shapes/modules/hand.js | 4 ++++ scripts/shapes/shapes.js | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/shapes/modules/hand.js b/scripts/shapes/modules/hand.js index 29ed6bfd96..73efed8017 100644 --- a/scripts/shapes/modules/hand.js +++ b/scripts/shapes/modules/hand.js @@ -204,6 +204,10 @@ Hand = function (side) { } if (entityID) { intersectionPosition = Entities.getEntityProperties(entityID, "position").position; + if (Vec3.distance(palmPosition, intersectionPosition) > NEAR_GRAB_RADIUS) { + intersectionPosition = Vec3.sum(palmPosition, + Vec3.multiply(NEAR_GRAB_RADIUS, Vec3.normalize(Vec3.subtract(intersectionPosition, palmPosition)))); + } } } diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index 573a9445e7..b4e822c233 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -930,7 +930,7 @@ // Hide UI if hand is intersecting entity and camera is outside entity, or if hand is intersecting stretch handle. if (side !== dominantHand) { showUI = !intersection.handIntersected || (intersection.entityID !== null - && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition())); + && !isCameraOutsideEntity(intersection.entityID, intersection.intersection)); if (showUI !== isUIVisible) { isUIVisible = !isUIVisible; ui.setVisible(isUIVisible); @@ -952,7 +952,8 @@ && otherEditor.isHandle(intersection.overlayID)) && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) + && (isTriggerPressed + || isCameraOutsideEntity(intersection.entityID, intersection.intersection))) && !(intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked)) { // No transition. @@ -969,7 +970,7 @@ setState(EDITOR_HANDLE_SCALING); } else if (intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) && (wasTriggerClicked || !isTriggerClicked) && !isAutoGrab - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, intersection.intersection))) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); setState(EDITOR_HIGHLIGHTING); @@ -1020,7 +1021,7 @@ case EDITOR_HIGHLIGHTING: if (hand.valid() && intersection.entityID && (intersection.editableEntity || toolSelected === TOOL_PICK_COLOR) - && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, hand.palmPosition())) + && (isTriggerPressed || isCameraOutsideEntity(intersection.entityID, intersection.intersection)) && !(!wasTriggerClicked && isTriggerClicked && (!otherEditor.isEditing(rootEntityID) || toolSelected !== TOOL_SCALE)) && !(!wasTriggerClicked && isTriggerClicked && intersection.overlayID @@ -1100,8 +1101,9 @@ } else { setState(EDITOR_GRABBING); } + } else if (!intersection.entityID || !intersection.editableEntity - || (!isTriggerPressed && !isCameraOutsideEntity(intersection.entityID, hand.palmPosition()))) { + || (!isTriggerPressed && !isCameraOutsideEntity(intersection.entityID, intersection.intersection))) { setState(EDITOR_SEARCHING); } else { log(side, "ERROR: Editor: Unexpected condition B in EDITOR_HIGHLIGHTING!"); From dbf8d19095d2e9b56de89bf426289cb3017b86fd Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Mon, 2 Oct 2017 12:18:13 -0400 Subject: [PATCH 405/504] [Case 6491] eslint pass: fixes issues with equality checks. * Also adds some paren grouping to help with readability of some statements. * eslint pass on script using .eslintrc.js Changes to be committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index efe68fe973..61664ce245 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -140,12 +140,12 @@ SelectionManager = (function() { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { - if (entityID == that.selections[i]) { + if (entityID === that.selections[i]) { idx = i; break; } } - if (idx == -1) { + if (idx === -1) { that.selections.push(entityID); } else if (toggleSelection) { that.selections.splice(idx, 1); @@ -175,7 +175,7 @@ SelectionManager = (function() { that.localPosition = null; that.worldDimensions = null; that.worldPosition = null; - } else if (that.selections.length == 1) { + } else if (that.selections.length === 1) { properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; @@ -1186,7 +1186,7 @@ SelectionDisplay = (function() { var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; var dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; } else { rotation = SelectionManager.worldRotation; @@ -1478,9 +1478,9 @@ SelectionDisplay = (function() { //var translateHandlesVisible = true; //var selectionBoxVisible = true; var isPointLight = false; - if (selectionManager.selections.length == 1) { + if (selectionManager.selections.length === 1) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); - isPointLight = properties.type == "Light" && !properties.isSpotlight; + isPointLight = (properties.type === "Light") && !properties.isSpotlight; } if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || @@ -1567,7 +1567,7 @@ SelectionDisplay = (function() { print("======> SetSpaceMode called. ========"); } - if (spaceMode != newSpaceMode) { + if (spaceMode !== newSpaceMode) { if (wantDebug) { print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); } @@ -1587,7 +1587,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("========> ToggleSpaceMode called. ========="); } - if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { if (wantDebug) { print("Local space editing is not available with multiple selections"); } @@ -1596,7 +1596,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("PreToggle: " + spaceMode); } - spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; + spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL; that.updateHandles(); if (wantDebug) { print("PostToggle: " + spaceMode); @@ -1627,7 +1627,7 @@ SelectionDisplay = (function() { var rotation, dimensions, position, registrationPoint; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; dimensions = SelectionManager.localDimensions; position = SelectionManager.localPosition; @@ -1852,17 +1852,17 @@ SelectionDisplay = (function() { var inModeRotate = (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)); var inModeTranslate = (isActiveTool(selectionBox) || isActiveTool(grabberCloner) || isActiveTool(grabberMoveUp)); - var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode == SPACE_LOCAL); + var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode === SPACE_LOCAL); var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); var cloneHandleVisible = !(inModeRotate || inModeTranslate); if (wantDebug) { print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); } - var isSingleSelection = (selectionManager.selections.length == 1); + var isSingleSelection = (selectionManager.selections.length === 1); if (isSingleSelection) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); - var isLightSelection = (properties.type == "Light"); + var isLightSelection = (properties.type === "Light"); if (isLightSelection) { if (wantDebug) { print(" Light Selection revoking Non-Light Grabbers Visibility!"); @@ -2691,9 +2691,9 @@ SelectionDisplay = (function() { var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; - rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); + rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; initialPosition = SelectionManager.localPosition; initialDimensions = SelectionManager.localDimensions; @@ -2739,7 +2739,7 @@ SelectionDisplay = (function() { } var start = null; var end = null; - if (numDimensions == 1 && mask.x) { + if ((numDimensions === 1) && mask.x) { start = Vec3.multiplyQbyV(rotation, { x: -10000, y: 0, @@ -2758,7 +2758,7 @@ SelectionDisplay = (function() { visible: true, }); } - if (numDimensions == 1 && mask.y) { + if ((numDimensions === 1) && mask.y) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: -10000, @@ -2777,7 +2777,7 @@ SelectionDisplay = (function() { visible: true, }); } - if (numDimensions == 1 && mask.z) { + if ((numDimensions === 1) && mask.z) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: 0, @@ -2796,14 +2796,14 @@ SelectionDisplay = (function() { visible: true, }); } - if (numDimensions == 1) { - if (mask.x == 1) { + if (numDimensions === 1) { + if (mask.x === 1) { planeNormal = { x: 0, y: 1, z: 0 }; - } else if (mask.y == 1) { + } else if (mask.y === 1) { planeNormal = { x: 1, y: 0, @@ -2816,7 +2816,7 @@ SelectionDisplay = (function() { z: 0 }; } - } else if (numDimensions == 2) { + } else if (numDimensions === 2) { if (mask.x === 0) { planeNormal = { x: 1, @@ -2873,10 +2873,10 @@ SelectionDisplay = (function() { }; var onMove = function(event) { - var proportional = spaceMode == SPACE_WORLD || event.isShifted || isActiveTool(grabberSpotLightRadius); + var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || isActiveTool(grabberSpotLightRadius); var position, dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { position = SelectionManager.localPosition; dimensions = SelectionManager.localDimensions; rotation = SelectionManager.localRotation; @@ -3942,7 +3942,7 @@ SelectionDisplay = (function() { var results = testRayIntersect(pickRay, interactiveOverlays); if (results.intersects) { var hitOverlayID = results.overlayID; - if ((hitOverlayID == HMD.tabletID) || (hitOverlayID == HMD.tabletScreenID) || (hitOverlayID == HMD.homeButtonID)) { + if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { //--EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) return false; } From a610946af1c7c8bba2b4a3fc7e924a43f23f1e45 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Mon, 2 Oct 2017 12:31:10 -0400 Subject: [PATCH 406/504] [Case 6491] eslint pass: fixes undefined center var in that.updateHandles. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 61664ce245..cbd49e9ed8 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1646,7 +1646,7 @@ SelectionDisplay = (function() { }; // Center of entity, relative to registration point - center = getRelativeCenterPosition(dimensions, registrationPoint); + var center = getRelativeCenterPosition(dimensions, registrationPoint); // Distances in world coordinates relative to the registration point var left = -registrationPointDimensions.x; From c131686f1bd0cffa785abbf2d6610637d37fc29b Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Mon, 2 Oct 2017 12:55:51 -0400 Subject: [PATCH 407/504] [Case 6491] eslint pass: Addressing some comment issues. * This should reduces the noise for the eslint passes on this script. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index cbd49e9ed8..63d4c3cb2a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1042,7 +1042,7 @@ SelectionDisplay = (function() { // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(that.triggerMapping.disable); - that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. that.TRIGGER_ON_VALUE = 0.4; that.TRIGGER_OFF_VALUE = 0.15; that.triggered = false; @@ -1103,7 +1103,7 @@ SelectionDisplay = (function() { if (!grabberTools.hasOwnProperty(toolHandle)) { print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool."); - //--EARLY EXIT-- + // EARLY EXIT return false; } @@ -1472,11 +1472,11 @@ SelectionDisplay = (function() { var rotateHandlesVisible = true; var rotationOverlaysVisible = false; - //note: Commented out as these are currently unused here; however, - // leaving them around as they document intent of state as it - // relates to modes that may be useful later. - //var translateHandlesVisible = true; - //var selectionBoxVisible = true; + // note: Commented out as these are currently unused here; however, + // leaving them around as they document intent of state as it + // relates to modes that may be useful later. + // var translateHandlesVisible = true; + // var selectionBoxVisible = true; var isPointLight = false; if (selectionManager.selections.length === 1) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); @@ -1487,14 +1487,14 @@ SelectionDisplay = (function() { isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { rotationOverlaysVisible = true; rotateHandlesVisible = false; - //translateHandlesVisible = false; - //selectionBoxVisible = false; + // translateHandlesVisible = false; + // selectionBoxVisible = false; } else if (isActiveTool(grabberMoveUp) || isPointLight) { rotateHandlesVisible = false; } else if (activeTool) { // every other mode is a stretch mode... rotateHandlesVisible = false; - //translateHandlesVisible = false; + // translateHandlesVisible = false; } Overlays.editOverlay(rotateZeroOverlay, { @@ -1622,7 +1622,7 @@ SelectionDisplay = (function() { return; } - //print(" Triggering updateRotationHandles"); + // print(" Triggering updateRotationHandles"); that.updateRotationHandles(); var rotation, dimensions, position, registrationPoint; @@ -1938,7 +1938,7 @@ SelectionDisplay = (function() { visible: true, }); - } else { //..it's a PointLight + } else { // ..it's a PointLight that.setSpotLightHandlesVisible(false); var showEdgePointGrabbers = !inModeTranslate; @@ -2003,11 +2003,11 @@ SelectionDisplay = (function() { visible: true, }); } - } else { //..it's not a light at all + } else { // ..it's not a light at all that.setSpotLightHandlesVisible(false); that.setPointLightHandlesVisible(false); } - }//--end of isSingleSelection + }// end of isSingleSelection @@ -2290,7 +2290,7 @@ SelectionDisplay = (function() { var visibilityUpdate = { visible: isVisible }; for (var toolKey in grabberTools) { if (!grabberTools.hasOwnProperty(toolKey)) { - //--EARLY ITERATION EXIT--(On to the next one) + // EARLY ITERATION EXIT--(On to the next one) continue; } @@ -2411,14 +2411,14 @@ SelectionDisplay = (function() { print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); } - //--EARLY EXIT--(Invalid ray detected.) + // EARLY EXIT--(Invalid ray detected.) return; } var vector = Vec3.subtract(pick, initialXZPick); // If the mouse is too close to the horizon of the pick plane, stop moving - var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it var elevation = translateXZTool.elevation(pickRay.origin, pick); if (wantDebug) { print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); @@ -2429,12 +2429,12 @@ SelectionDisplay = (function() { print(" "+ translateXZTool.mode + " - too close to horizon!"); } - //--EARLY EXIT--(Don't proceed past the reached limit.) + // EARLY EXIT--(Don't proceed past the reached limit.) return; } // If the angular size of the object is too small, stop moving - var MIN_ANGULAR_SIZE = 0.01; // Radians + var MIN_ANGULAR_SIZE = 0.01; // Radians if (translateXZTool.greatestDimension > 0) { var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); if (wantDebug) { @@ -2592,7 +2592,7 @@ SelectionDisplay = (function() { print(" event.y:" + event.y); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" newPosition:", newPosition); + // Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; @@ -2970,10 +2970,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); + // Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); + // Vec3.print(" oldPOS:", oldPOS); + // Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -2983,7 +2983,7 @@ SelectionDisplay = (function() { } SelectionManager._update(); - };//--End of onMove def + };// End of onMove def return { mode: stretchMode, @@ -3581,7 +3581,7 @@ SelectionDisplay = (function() { if (!rotationChange) { print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); - //--EARLY EXIT-- + // EARLY EXIT return; } @@ -3677,14 +3677,14 @@ SelectionDisplay = (function() { if (wantDebug) { print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); } - }//--End_Function(helperRotationHandleOnBegin) + }// End_Function(helperRotationHandleOnBegin) function helperRotationHandleOnMove(event, rotAroundAxis, rotCenter, handleRotation) { if (!rotZero) { print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); - //--EARLY EXIT-- + // EARLY EXIT return; } @@ -3777,12 +3777,12 @@ SelectionDisplay = (function() { minorTickMarksLength: 0.1, }); } - }//--End_If(results.intersects) + }// End_If(results.intersects) if (wantDebug) { print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); } - }//--End_Function(helperRotationHandleOnMove) + }// End_Function(helperRotationHandleOnMove) function helperRotationHandleOnEnd() { var wantDebug = false; @@ -3807,7 +3807,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("================== " + getMode() + "(onEnd) <- ======================="); } - }//--End_Function(helperRotationHandleOnEnd) + }// End_Function(helperRotationHandleOnEnd) // YAW GRABBER TOOL DEFINITION @@ -3860,7 +3860,7 @@ SelectionDisplay = (function() { if (SelectionManager.hasSelection()) { // FIXME - this cause problems with editing in the entity properties window - //SelectionManager._update(); + // SelectionManager._update(); if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { @@ -3923,12 +3923,12 @@ SelectionDisplay = (function() { print("=============== eST::MousePressEvent BEG ======================="); } if (!event.isLeftButton && !that.triggered) { - //--EARLY EXIT-(if another mouse button than left is pressed ignore it) + // EARLY EXIT-(if another mouse button than left is pressed ignore it) return false; } var pickRay = generalComputePickRay(event.x, event.y); - //TODO_Case6491: Move this out to setup just to make it once + // TODO_Case6491: Move this out to setup just to make it once var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; for (var key in grabberTools) { if (grabberTools.hasOwnProperty(key)) { @@ -3943,7 +3943,7 @@ SelectionDisplay = (function() { if (results.intersects) { var hitOverlayID = results.overlayID; if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { - //--EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) return false; } @@ -3959,8 +3959,8 @@ SelectionDisplay = (function() { } } else { print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); - }//--End_if (hitTool) - }//--End_If(results.intersects) + }// End_if (hitTool) + }// End_If(results.intersects) if (wantDebug) { print(" DisplayMode: " + getMode()); @@ -3992,7 +3992,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } - //--EARLY EXIT--(Move handled via active tool) + // EARLY EXIT--(Move handled via active tool) return true; } @@ -4156,7 +4156,7 @@ SelectionDisplay = (function() { } - showHandles = activeTool;// Date: Mon, 2 Oct 2017 13:21:10 -0400 Subject: [PATCH 408/504] [Case 6491] eslint pass: Cleaning up comma-dangle noise. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 63d4c3cb2a..db16acac94 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -217,7 +217,7 @@ SelectionManager = (function() { that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), - z: brn.z + (that.worldDimensions.z / 2), + z: brn.z + (that.worldDimensions.z / 2) }; // For 1+ selections we can only modify selections in world space @@ -373,7 +373,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesEdge = { @@ -390,7 +390,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesFace = { @@ -407,7 +407,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesCloner = { @@ -424,12 +424,12 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var spotLightLineProperties = { color: lightOverlayColor, - lineWidth: 1.5, + lineWidth: 1.5 }; var highlightBox = Overlays.addOverlay("cube", { @@ -469,7 +469,7 @@ SelectionDisplay = (function() { solid: false, visible: false, dashed: false, - lineWidth: 1.0, + lineWidth: 1.0 }); var selectionBoxes = []; @@ -505,7 +505,7 @@ SelectionDisplay = (function() { topMargin: 0, rightMargin: 0, bottomMargin: 0, - leftMargin: 0, + leftMargin: 0 }); var grabberMoveUp = Overlays.addOverlay("image3d", { @@ -521,7 +521,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: true, - drawInFront: true, + drawInFront: true }); // var normalLine = Overlays.addOverlay("line3d", { @@ -613,7 +613,7 @@ SelectionDisplay = (function() { var pointLightGrabberHandles = [ grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, grabberPointLightT, grabberPointLightB, grabberPointLightL, - grabberPointLightR, grabberPointLightF, grabberPointLightN, + grabberPointLightR, grabberPointLightF, grabberPointLightN ]; var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); @@ -692,7 +692,7 @@ SelectionDisplay = (function() { width: 300, height: 200, rotation: baseOverlayRotation, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var yawOverlayAngles = { @@ -793,7 +793,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateCurrentOverlay = Overlays.addOverlay("line3d", { @@ -814,7 +814,7 @@ SelectionDisplay = (function() { green: 0, blue: 255 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); @@ -849,7 +849,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -884,7 +884,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -914,7 +914,7 @@ SelectionDisplay = (function() { red: 0, green: 0, blue: 0 - }, + } }); var yawHandle = Overlays.addOverlay("image3d", { @@ -930,7 +930,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -947,7 +947,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -964,7 +964,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); var allOverlays = [ @@ -987,7 +987,7 @@ SelectionDisplay = (function() { grabberSpotLightCircle, grabberPointLightCircleX, grabberPointLightCircleY, - grabberPointLightCircleZ, + grabberPointLightCircleZ ].concat(stretchHandles); @@ -1532,20 +1532,20 @@ SelectionDisplay = (function() { for (var i = 0; i < stretchHandles.length; i++) { Overlays.editOverlay(stretchHandles[i], { - size: grabberSize, + size: grabberSize }); } var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; handleSize = Math.min(handleSize, avgDimension / 3); Overlays.editOverlay(yawHandle, { - scale: handleSize, + scale: handleSize }); Overlays.editOverlay(pitchHandle, { - scale: handleSize, + scale: handleSize }); Overlays.editOverlay(rollHandle, { - scale: handleSize, + scale: handleSize }); var pos = Vec3.sum(grabberMoveUpPosition, { x: 0, @@ -1554,7 +1554,7 @@ SelectionDisplay = (function() { }); Overlays.editOverlay(grabberMoveUp, { position: pos, - scale: handleSize / 1.25, + scale: handleSize / 1.25 }); } }; @@ -1642,7 +1642,7 @@ SelectionDisplay = (function() { var registrationPointDimensions = { x: dimensions.x * registrationPoint.x, y: dimensions.y * registrationPoint.y, - z: dimensions.z * registrationPoint.z, + z: dimensions.z * registrationPoint.z }; // Center of entity, relative to registration point @@ -1877,33 +1877,33 @@ SelectionDisplay = (function() { var showEdgeSpotGrabbers = !(inModeTranslate || inModeRotate); Overlays.editOverlay(grabberSpotLightCenter, { position: position, - visible: false, + visible: false }); Overlays.editOverlay(grabberSpotLightRadius, { position: NEAR, rotation: rotation, - visible: showEdgeSpotGrabbers, + visible: showEdgeSpotGrabbers }); Overlays.editOverlay(grabberSpotLightL, { position: EdgeNL, rotation: rotation, - visible: showEdgeSpotGrabbers, + visible: showEdgeSpotGrabbers }); Overlays.editOverlay(grabberSpotLightR, { position: EdgeNR, rotation: rotation, - visible: showEdgeSpotGrabbers, + visible: showEdgeSpotGrabbers }); Overlays.editOverlay(grabberSpotLightT, { position: EdgeTN, rotation: rotation, - visible: showEdgeSpotGrabbers, + visible: showEdgeSpotGrabbers }); Overlays.editOverlay(grabberSpotLightB, { position: EdgeBN, rotation: rotation, - visible: showEdgeSpotGrabbers, + visible: showEdgeSpotGrabbers }); Overlays.editOverlay(grabberSpotLightCircle, { position: NEAR, @@ -1914,28 +1914,28 @@ SelectionDisplay = (function() { }, lineWidth: 1.5, rotation: rotation, - visible: true, + visible: true }); Overlays.editOverlay(grabberSpotLightLineT, { start: position, end: EdgeTN, - visible: true, + visible: true }); Overlays.editOverlay(grabberSpotLightLineB, { start: position, end: EdgeBN, - visible: true, + visible: true }); Overlays.editOverlay(grabberSpotLightLineR, { start: position, end: EdgeNR, - visible: true, + visible: true }); Overlays.editOverlay(grabberSpotLightLineL, { start: position, end: EdgeNL, - visible: true, + visible: true }); } else { // ..it's a PointLight @@ -1945,32 +1945,32 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberPointLightT, { position: TOP, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightB, { position: BOTTOM, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightL, { position: LEFT, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightR, { position: RIGHT, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightF, { position: FAR, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightN, { position: NEAR, rotation: rotation, - visible: showEdgePointGrabbers, + visible: showEdgePointGrabbers }); Overlays.editOverlay(grabberPointLightCircleX, { position: position, @@ -1980,7 +1980,7 @@ SelectionDisplay = (function() { y: properties.dimensions.z / 2.0, z: 1 }, - visible: true, + visible: true }); Overlays.editOverlay(grabberPointLightCircleY, { position: position, @@ -1990,7 +1990,7 @@ SelectionDisplay = (function() { y: properties.dimensions.z / 2.0, z: 1 }, - visible: true, + visible: true }); Overlays.editOverlay(grabberPointLightCircleZ, { position: position, @@ -2000,7 +2000,7 @@ SelectionDisplay = (function() { y: properties.dimensions.z / 2.0, z: 1 }, - visible: true, + visible: true }); } } else { // ..it's not a light at all @@ -2096,7 +2096,7 @@ SelectionDisplay = (function() { position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, - visible: !inModeRotate, + visible: !inModeRotate }); // Create more selection box overlays if we don't have enough @@ -2120,7 +2120,7 @@ SelectionDisplay = (function() { visible: false, dashed: false, lineWidth: 1.0, - ignoreRayIntersection: true, + ignoreRayIntersection: true })); } @@ -2150,7 +2150,7 @@ SelectionDisplay = (function() { color: color, rotation: props.rotation, dimensions: props.dimensions, - visible: true, + visible: true }); } } @@ -2244,7 +2244,7 @@ SelectionDisplay = (function() { x: selectionManager.worldDimensions.x, y: selectionManager.worldDimensions.z }, - rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0) }); if (wantDebug) { @@ -2368,7 +2368,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2519,7 +2519,7 @@ SelectionDisplay = (function() { z: vector.z }); Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, + position: newPosition }); if (wantDebug) { @@ -2562,7 +2562,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2602,12 +2602,12 @@ SelectionDisplay = (function() { var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { - position: newPosition, + position: newPosition }); } SelectionManager._update(); - }, + } }); // GRABBER TOOL: GRABBER CLONER @@ -2661,13 +2661,13 @@ SelectionDisplay = (function() { var signs = { x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0), y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0), - z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0), + z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0) }; var mask = { x: Math.abs(direction.x) > 0 ? 1 : 0, y: Math.abs(direction.y) > 0 ? 1 : 0, - z: Math.abs(direction.z) > 0 ? 1 : 0, + z: Math.abs(direction.z) > 0 ? 1 : 0 }; @@ -2755,7 +2755,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(xRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } if ((numDimensions === 1) && mask.y) { @@ -2774,7 +2774,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(yRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } if ((numDimensions === 1) && mask.z) { @@ -2793,7 +2793,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(zRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } if (numDimensions === 1) { @@ -2962,7 +2962,7 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { Entities.editEntity(SelectionManager.selections[i], { position: newPosition, - dimensions: newDimensions, + dimensions: newDimensions }); } @@ -3090,7 +3090,7 @@ SelectionDisplay = (function() { var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI; Entities.editEntity(selectionManager.selections[0], { - cutoff: cutoff, + cutoff: cutoff }); SelectionManager._update(); @@ -3117,7 +3117,7 @@ SelectionDisplay = (function() { }; Entities.editEntity(selectionManager.selections[0], { - dimensions: newDimensions, + dimensions: newDimensions }); SelectionManager._update(); @@ -3544,7 +3544,7 @@ SelectionDisplay = (function() { var position = { x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - z: 0, + z: 0 }; if (wantDebug) { print(" Angle: " + angle); @@ -3560,7 +3560,7 @@ SelectionDisplay = (function() { y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, - text: normalizeDegrees(angleFromZero) + "°", + text: normalizeDegrees(angleFromZero) + "°" }; if (wantDebug) { print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); @@ -3594,7 +3594,7 @@ SelectionDisplay = (function() { var initialProperties = SelectionManager.savedProperties[entityID]; var newProperties = { - rotation: Quat.multiply(rotationChange, initialProperties.rotation), + rotation: Quat.multiply(rotationChange, initialProperties.rotation) }; if (reposition) { @@ -3637,7 +3637,7 @@ SelectionDisplay = (function() { innerRadius: 0.9, startAt: 0, endAt: 360, - alpha: innerAlpha, + alpha: innerAlpha }); Overlays.editOverlay(rotateOverlayOuter, { @@ -3648,7 +3648,7 @@ SelectionDisplay = (function() { innerRadius: 0.9, startAt: 0, endAt: 360, - alpha: outerAlpha, + alpha: outerAlpha }); Overlays.editOverlay(rotateOverlayCurrent, { @@ -3658,11 +3658,11 @@ SelectionDisplay = (function() { size: outerRadius, startAt: 0, endAt: 0, - innerRadius: 0.9, + innerRadius: 0.9 }); Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, + visible: true }); updateRotationDegreesOverlay(0, handleRotation, rotCenter); @@ -3756,7 +3756,7 @@ SelectionDisplay = (function() { majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, majorTickMarksLength: -0.25, - minorTickMarksLength: 0, + minorTickMarksLength: 0 }); } else { Overlays.editOverlay(rotateOverlayInner, { @@ -3774,7 +3774,7 @@ SelectionDisplay = (function() { majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, + minorTickMarksLength: 0.1 }); } }// End_If(results.intersects) From cb264b5552594ebb552972b14a45fb042dc60c68 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Mon, 2 Oct 2017 13:28:43 -0400 Subject: [PATCH 409/504] [Case 6491] eslint pass: Addresses curly bracket, keyword spacing issues. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index db16acac94..191d7a0748 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -238,8 +238,14 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees <= -180) degrees += 360; + while (degrees > 180) { + degrees -= 360; + } + + while (degrees <= -180) { + degrees += 360; + } + return degrees; } @@ -2143,7 +2149,9 @@ SelectionDisplay = (function() { var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; - if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; + if (i >= selectionManager.selections.length - 1) { + color = {red: 255, green: 255, blue: 64}; + } Overlays.editOverlay(selectionBoxes[i], { position: curBoxPosition, @@ -4134,7 +4142,7 @@ SelectionDisplay = (function() { print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); } activeTool.onEnd(event); - }else if (wantDebug) { + } else if (wantDebug) { print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); } } From 97b09d680ca7bbff215a7765d082f909deed5d73 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 2 Oct 2017 13:17:53 -0700 Subject: [PATCH 410/504] fix and improve laser pointer locking --- interface/src/raypick/LaserPointer.h | 3 + interface/src/raypick/LaserPointerManager.cpp | 105 ++++++++++-------- interface/src/raypick/LaserPointerManager.h | 2 - interface/src/raypick/RayPick.h | 5 + interface/src/raypick/RayPickManager.cpp | 76 +++++++------ interface/src/raypick/RayPickManager.h | 2 - 6 files changed, 108 insertions(+), 85 deletions(-) diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 5467a8233e..fa7d396ae8 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -75,6 +75,8 @@ public: void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = std::pair(objectID, isOverlay); } + QReadWriteLock* getLock() { return &_lock; } + void update(); private: @@ -89,6 +91,7 @@ private: std::pair _objectLockEnd { std::pair(QUuid(), false)}; QUuid _rayPickUID; + QReadWriteLock _lock; void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState); diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp index b19ecc14f0..387f88724e 100644 --- a/interface/src/raypick/LaserPointerManager.cpp +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -17,7 +17,6 @@ QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const La QWriteLocker containsLock(&_containsLock); QUuid id = QUuid::createUuid(); _laserPointers[id] = laserPointer; - _laserPointerLocks[id] = std::make_shared(); return id; } return QUuid(); @@ -26,46 +25,50 @@ QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const La void LaserPointerManager::removeLaserPointer(const QUuid uid) { QWriteLocker lock(&_containsLock); _laserPointers.remove(uid); - _laserPointerLocks.remove(uid); } void LaserPointerManager::enableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->enable(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->enable(); } } void LaserPointerManager::disableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->disable(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->disable(); } } void LaserPointerManager::setRenderState(QUuid uid, const std::string& renderState) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setRenderState(renderState); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setRenderState(renderState); } } void LaserPointerManager::editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->editRenderState(state, startProps, pathProps, endProps); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->editRenderState(state, startProps, pathProps, endProps); } } const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QReadLocker laserLock(_laserPointerLocks[uid].get()); - return _laserPointers[uid]->getPrevRayPickResult(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QReadLocker laserLock(laserPointer.value()->getLock()); + return laserPointer.value()->getPrevRayPickResult(); } return RayPickResult(); } @@ -74,79 +77,89 @@ void LaserPointerManager::update() { QReadLocker lock(&_containsLock); for (QUuid& uid : _laserPointers.keys()) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts - QReadLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->update(); + auto laserPointer = _laserPointers.find(uid); + QReadLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->update(); } } void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setPrecisionPicking(precisionPicking); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setPrecisionPicking(precisionPicking); } } void LaserPointerManager::setLaserLength(QUuid uid, const float laserLength) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setLaserLength(laserLength); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setLaserLength(laserLength); } } void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreEntities(ignoreEntities); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIgnoreEntities(ignoreEntities); } } void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeEntities(includeEntities); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIncludeEntities(includeEntities); } } void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreOverlays(ignoreOverlays); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIgnoreOverlays(ignoreOverlays); } } void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeOverlays(includeOverlays); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIncludeOverlays(includeOverlays); } } void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreAvatars(ignoreAvatars); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIgnoreAvatars(ignoreAvatars); } } void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeAvatars(includeAvatars); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setIncludeAvatars(includeAvatars); } } void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setLockEndUUID(objectID, isOverlay); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + QWriteLocker laserLock(laserPointer.value()->getLock()); + laserPointer.value()->setLockEndUUID(objectID, isOverlay); } } diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h index 6494bb7056..b841877578 100644 --- a/interface/src/raypick/LaserPointerManager.h +++ b/interface/src/raypick/LaserPointerManager.h @@ -13,7 +13,6 @@ #include #include -#include #include "LaserPointer.h" @@ -46,7 +45,6 @@ public: private: QHash> _laserPointers; - QHash> _laserPointerLocks; QReadWriteLock _containsLock; }; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 0686a05718..428e44d670 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -16,6 +16,7 @@ #include "EntityItemID.h" #include "ui/overlays/Overlay.h" +#include class RayPickFilter { public: @@ -127,6 +128,8 @@ public: void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); } void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); } + QReadWriteLock* getLock() { return &_lock; } + private: RayPickFilter _filter; float _maxDistance; @@ -139,6 +142,8 @@ private: QVector _includeOverlays; QVector _ignoreAvatars; QVector _includeAvatars; + + QReadWriteLock _lock; }; #endif // hifi_RayPick_h diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp index bfc6e3fcb2..65f82dcd5f 100644 --- a/interface/src/raypick/RayPickManager.cpp +++ b/interface/src/raypick/RayPickManager.cpp @@ -47,6 +47,7 @@ void RayPickManager::update() { RayPickCache results; for (auto& uid : _rayPicks.keys()) { std::shared_ptr rayPick = _rayPicks[uid]; + QWriteLocker lock(rayPick->getLock()); if (!rayPick->isEnabled() || rayPick->getFilter().doesPickNothing() || rayPick->getMaxDistance() < 0.0f) { continue; } @@ -114,7 +115,6 @@ void RayPickManager::update() { } } - QWriteLocker lock(_rayPickLocks[uid].get()); if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) { rayPick->setRayPickResult(res); } else { @@ -127,7 +127,6 @@ QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(jointName, posOffset, dirOffset, filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } @@ -135,7 +134,6 @@ QUuid RayPickManager::createRayPick(const RayPickFilter& filter, const float max QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } @@ -143,93 +141,101 @@ QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(position, direction, filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } void RayPickManager::removeRayPick(const QUuid uid) { QWriteLocker lock(&_containsLock); _rayPicks.remove(uid); - _rayPickLocks.remove(uid); } void RayPickManager::enableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker rayPickLock(_rayPickLocks[uid].get()); - _rayPicks[uid]->enable(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker rayPickLock(rayPick.value()->getLock()); + rayPick.value()->enable(); } } void RayPickManager::disableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker rayPickLock(_rayPickLocks[uid].get()); - _rayPicks[uid]->disable(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker rayPickLock(rayPick.value()->getLock()); + rayPick.value()->disable(); } } const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QReadLocker lock(_rayPickLocks[uid].get()); - return _rayPicks[uid]->getPrevRayPickResult(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QReadLocker lock(rayPick.value()->getLock()); + return rayPick.value()->getPrevRayPickResult(); } return RayPickResult(); } void RayPickManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setPrecisionPicking(precisionPicking); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setPrecisionPicking(precisionPicking); } } void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreEntities(ignoreEntities); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIgnoreEntities(ignoreEntities); } } void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeEntities(includeEntities); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIncludeEntities(includeEntities); } } void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreOverlays(ignoreOverlays); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIgnoreOverlays(ignoreOverlays); } } void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeOverlays(includeOverlays); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIncludeOverlays(includeOverlays); } } void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreAvatars(ignoreAvatars); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIgnoreAvatars(ignoreAvatars); } } void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeAvatars(includeAvatars); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + QWriteLocker lock(rayPick.value()->getLock()); + rayPick.value()->setIncludeAvatars(includeAvatars); } } \ No newline at end of file diff --git a/interface/src/raypick/RayPickManager.h b/interface/src/raypick/RayPickManager.h index 9717767f19..974022eb4d 100644 --- a/interface/src/raypick/RayPickManager.h +++ b/interface/src/raypick/RayPickManager.h @@ -15,7 +15,6 @@ #include #include -#include #include "RegisteredMetaTypes.h" @@ -47,7 +46,6 @@ public: private: QHash> _rayPicks; - QHash> _rayPickLocks; QReadWriteLock _containsLock; typedef QHash, std::unordered_map> RayPickCache; From ca11d19b3ea611e11345953d5a528a2e0978bbd8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 2 Oct 2017 16:06:44 -0700 Subject: [PATCH 411/504] allow importing of avatar entities from json --- libraries/entities/src/EntityTree.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bf37a08386..445f2ddb38 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1935,6 +1935,12 @@ bool EntityTree::readFromMap(QVariantMap& map) { entityItemID = EntityItemID(QUuid::createUuid()); } + if (properties.getClientOnly()) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + properties.setOwningAvatarID(myNodeID); + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); From e7fa8131eaf3457596cfebeff5a5d50404d82ce3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 2 Oct 2017 16:44:03 -0700 Subject: [PATCH 412/504] make json importer understand AVATAR_SELF_ID --- libraries/entities/src/EntityTree.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 445f2ddb38..e0187cd2b6 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1935,11 +1935,14 @@ bool EntityTree::readFromMap(QVariantMap& map) { entityItemID = EntityItemID(QUuid::createUuid()); } + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); if (properties.getClientOnly()) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); properties.setOwningAvatarID(myNodeID); } + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(myNodeID); + } EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { From 46ce68a893944e4cf3aef18c26765771764a2ff6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 3 Oct 2017 13:33:11 +1300 Subject: [PATCH 413/504] Fix lasers --- scripts/system/libraries/utils.js | 4 ++-- scripts/tutorials/entity_scripts/ambientSound.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 162edcaea0..fa9e60a4ef 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -6,8 +6,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// note: this constant is currently duplicated in edit.js -EDIT_SETTING = "io.highfidelity.isEditting"; +// note: this constant is currently duplicated in edit.js and ambientSounds.js +EDIT_SETTING = "io.highfidelity.isEditing"; isInEditMode = function isInEditMode() { return Settings.getValue(EDIT_SETTING); }; diff --git a/scripts/tutorials/entity_scripts/ambientSound.js b/scripts/tutorials/entity_scripts/ambientSound.js index e0a7d0a3cf..dd964fb4e0 100644 --- a/scripts/tutorials/entity_scripts/ambientSound.js +++ b/scripts/tutorials/entity_scripts/ambientSound.js @@ -139,7 +139,7 @@ this._toggle = function(hint) { // Toggle between ON/OFF state, but only if not in edit mode - if (Settings.getValue("io.highfidelity.isEditting")) { + if (Settings.getValue("io.highfidelity.isEditing")) { return; } var props = Entities.getEntityProperties(entity, [ "userData" ]); From a59d590111f94527ae67dee26e9d2e21ab90aafa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 3 Oct 2017 13:33:20 +1300 Subject: [PATCH 414/504] ESLint --- scripts/system/controllers/controllerModules/inVREditMode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index c48955442b..f807dfd7ef 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.hand = hand; this.disableModules = false; this.parameters = makeDispatcherModuleParameters( - 200, // Not too high otherwise the tablet laser doesn't work. + 200, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], From 31e72c55034b3c445e11995bc923a3e87548d0f2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 3 Oct 2017 16:26:00 +1300 Subject: [PATCH 415/504] Work around bug undoing physics where entities can end at wrong position --- scripts/shapes/modules/history.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 4c1b7857b8..7065a3c52b 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -145,9 +145,17 @@ History = (function () { return undoPosition < history.length - 1; } + function undoSetProperties(entityID, properties) { + Entities.editEntity(entityID, properties); + if (properties.gravity) { + kickPhysics(entityID); + } + } + function undo() { var undoData, entityID, + REPEAT_UNDO_DELAY = 500, // ms i, length; @@ -163,9 +171,16 @@ History = (function () { if (undoData.setProperties) { for (i = 0, length = undoData.setProperties.length; i < length; i++) { - Entities.editEntity(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); - if (undoData.setProperties[i].properties.gravity) { - kickPhysics(undoData.setProperties[i].entityID); + undoSetProperties(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); + if (undoData.setProperties[i].properties.velocity + && Vec3.equal(undoData.setProperties[i].properties.velocity, Vec3.ZERO) + && undoData.setProperties[i].properties.angularVelocity + && Vec3.equal(undoData.setProperties[i].properties.angularVelocity, Vec3.ZERO)) { + // Work around physics bug wherein the entity doesn't always end up at the correct position. + Script.setTimeout( + undoSetProperties(undoData.setProperties[i].entityID, undoData.setProperties[i].properties), + REPEAT_UNDO_DELAY + ); } } } From 5145107f8c002e8b9ac39fdcd5fc5641481a6d7a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 3 Oct 2017 21:36:40 +1300 Subject: [PATCH 416/504] 2D overlays can no longer be lasered --- scripts/system/controllers/controllerModules/inVREditMode.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index f807dfd7ef..952bd14fd7 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -41,9 +41,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return makeRunningValues(false, [], []); } - // 2D overlay lasers. - // These are automatically enabled. - // Tablet stylus. // Includes the tablet laser. var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND From 06f0e23bf9bbfc16c4dea90d564aeaa272a2c0b1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 3 Oct 2017 21:44:56 +1300 Subject: [PATCH 417/504] There no longer is a handControllerGrab.js --- scripts/shapes/modules/laser.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/shapes/modules/laser.js b/scripts/shapes/modules/laser.js index 29434a17f9..1efc38b65a 100644 --- a/scripts/shapes/modules/laser.js +++ b/scripts/shapes/modules/laser.js @@ -25,18 +25,18 @@ Laser = function (side) { searchDistance = 0.0, - SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js. + SEARCH_SPHERE_SIZE = 0.013, // Per farActionGrabEntity.js multiplied by 1.2 per farActionGrabEntity.js. MINUMUM_SEARCH_SPHERE_SIZE = 0.006, - SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js. - COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js. - COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per handControllgerGrab.js. + SEARCH_SPHERE_FOLLOW_RATE = 0.5, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per controllerDispatcherUtils.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per controllerDispatcherUtils.js. COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, - BRIGHT_POW = 0.06, // Per handControllerGrab.js. + BRIGHT_POW = 0.06, // Per old handControllerGrab.js. GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. - PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js. + PICK_MAX_DISTANCE = 500, // Per controllerDispatcherUtils.js. PRECISION_PICKING = true, NO_INCLUDE_IDS = [], NO_EXCLUDE_IDS = [], @@ -56,7 +56,7 @@ Laser = function (side) { return new Laser(side); } - function colorPow(color, power) { // Per handControllerGrab.js. + function colorPow(color, power) { // Per old handControllerGrab.js. return { red: Math.pow(color.red / 255, power) * 255, green: Math.pow(color.green / 255, power) * 255, From 477dfdff1ea3e48ae2a580007f4ea27ead945815 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 11:45:21 -0700 Subject: [PATCH 418/504] wip hud layering and fix crashes --- interface/src/ui/overlays/ModelOverlay.cpp | 9 +++++ interface/src/ui/overlays/ModelOverlay.h | 2 ++ interface/src/ui/overlays/OverlaysPayload.cpp | 13 +++---- .../display-plugins/OpenGLDisplayPlugin.cpp | 13 +++++-- .../src/display-plugins/OpenGLDisplayPlugin.h | 1 + .../display-plugins/hmd/HmdDisplayPlugin.cpp | 28 ++++++++------- .../render-utils/src/MeshPartPayload.cpp | 14 ++++---- libraries/render-utils/src/Model.cpp | 15 ++++++++ libraries/render-utils/src/Model.h | 1 + .../render-utils/src/RenderDeferredTask.cpp | 34 ++++++++++++++----- libraries/render/src/render/FilterTask.cpp | 13 ++++--- libraries/render/src/render/FilterTask.h | 8 ++--- libraries/render/src/render/Item.h | 7 +++- 13 files changed, 111 insertions(+), 47 deletions(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 8ce6e7f1f3..a3088e1ddb 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -88,6 +88,10 @@ void ModelOverlay::update(float deltatime) { _drawInFrontDirty = false; _model->setLayeredInFront(getDrawInFront(), scene); } + if (_drawInHUDDirty) { + _drawInHUDDirty = false; + _model->setLayeredInHUD(getDrawHUDLayer(), scene); + } scene->enqueueTransaction(transaction); } @@ -112,6 +116,11 @@ void ModelOverlay::setDrawInFront(bool drawInFront) { _drawInFrontDirty = true; } +void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) { + Base3DOverlay::setDrawHUDLayer(drawHUDLayer); + _drawInHUDDirty = true; +} + void ModelOverlay::setProperties(const QVariantMap& properties) { auto origPosition = getPosition(); auto origRotation = getRotation(); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index ba1ffa86c1..ea0eff170c 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -51,6 +51,7 @@ public: void setVisible(bool visible) override; void setDrawInFront(bool drawInFront) override; + void setDrawHUDLayer(bool drawHUDLayer) override; protected: Transform evalRenderTransform() override; @@ -98,6 +99,7 @@ private: bool _visibleDirty { false }; bool _drawInFrontDirty { false }; + bool _drawInHUDDirty { false }; }; diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 7beb96c061..194c530df0 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -50,20 +50,17 @@ namespace render { return overlay->getBounds(); } template <> int payloadGetLayer(const Overlay::Pointer& overlay) { - // Magic number while we are defining the layering mechanism: - const int LAYER_2D = 2; - const int LAYER_3D_FRONT = 1; - const int LAYER_3D = 0; - if (overlay->is3D()) { auto overlay3D = std::dynamic_pointer_cast(overlay); if (overlay3D->getDrawInFront()) { - return LAYER_3D_FRONT; + return Item::LAYER_3D_FRONT; + } else if (overlay3D->getDrawHUDLayer()) { + return Item::LAYER_3D_HUD; } else { - return LAYER_3D; + return Item::LAYER_3D; } } else { - return LAYER_2D; + return Item::LAYER_2D; } } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0872edcd95..93e648f441 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -569,11 +569,11 @@ std::function OpenGLDisplayPlugin batch.setResourceTexture(0, hudTexture); if (isStereo()) { for_each_eye([&](Eye eye) { - batch.setViewportTransform(eyeViewport(eye)); + batch.setViewportTransform(eyeViewport(eye, getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } }; @@ -838,6 +838,15 @@ ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const { return ivec4(vpPos, vpSize); } +ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye, uvec2 vpSize) const { + vpSize.x /= 2; + uvec2 vpPos; + if (eye == Eye::Right) { + vpPos.x = vpSize.x; + } + return ivec4(vpPos, vpSize); +} + gpu::gl::GLBackend* OpenGLDisplayPlugin::getGLBackend() { if (!_gpuContext || !_gpuContext->getBackend()) { return nullptr; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index ec775aed4c..4bb82f3651 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -125,6 +125,7 @@ protected: void present(); virtual void swapBuffers(); ivec4 eyeViewport(Eye eye) const; + ivec4 eyeViewport(Eye eye, uvec2 vpSize) const; void render(std::function f); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6d0134ec23..e5e1295786 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -430,23 +430,25 @@ void HmdDisplayPlugin::HUDRenderer::updatePipeline() { std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { updatePipeline(); return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { - batch.setPipeline(pipeline); + if (pipeline) { - batch.setInputFormat(format); - gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); - gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); - batch.setInputBuffer(gpu::Stream::POSITION, posView); - batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); - batch.setIndexBuffer(gpu::UINT16, indices, 0); + batch.setPipeline(pipeline); + batch.setInputFormat(format); + gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); + gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); + batch.setInputBuffer(gpu::Stream::POSITION, posView); + batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); + batch.setIndexBuffer(gpu::UINT16, indices, 0); - uniformsBuffer->setSubData(0, uniforms); - batch.setUniformBuffer(uniformsLocation, uniformsBuffer); + uniformsBuffer->setSubData(0, uniforms); + batch.setUniformBuffer(uniformsLocation, uniformsBuffer); - auto compositorHelper = DependencyManager::get(); - batch.setModelTransform(compositorHelper->getUiTransform()); - batch.setResourceTexture(0, hudTexture); + auto compositorHelper = DependencyManager::get(); + batch.setModelTransform(compositorHelper->getUiTransform()); + batch.setResourceTexture(0, hudTexture); - batch.drawIndexed(gpu::TRIANGLES, indexCount); + batch.drawIndexed(gpu::TRIANGLES, indexCount); + } }; } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 942da69dd7..8b65c9f7ce 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -404,15 +404,15 @@ ItemKey ModelMeshPartPayload::getKey() const { } int ModelMeshPartPayload::getLayer() const { - // MAgic number while we are defining the layering mechanism: - const int LAYER_3D_FRONT = 1; - const int LAYER_3D = 0; ModelPointer model = _model.lock(); - if (model && model->isLayeredInFront()) { - return LAYER_3D_FRONT; - } else { - return LAYER_3D; + if (model) { + if (model->isLayeredInFront()) { + return Item::LAYER_3D_FRONT; + } else if (model->isLayeredInHUD()) { + return Item::LAYER_3D_HUD; + } } + return Item::LAYER_3D; } ShapeKey ModelMeshPartPayload::getShapeKey() const { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index f25cad8a6e..252afada5d 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -598,6 +598,21 @@ void Model::setLayeredInFront(bool layered, const render::ScenePointer& scene) { } } +void Model::setLayeredInHUD(bool layered, const render::ScenePointer& scene) { + if (_isLayeredInHUD != layered) { + _isLayeredInHUD = layered; + + render::Transaction transaction; + foreach(auto item, _modelMeshRenderItemsMap.keys()) { + transaction.resetItem(item, _modelMeshRenderItemsMap[item]); + } + foreach(auto item, _collisionRenderItemsMap.keys()) { + transaction.resetItem(item, _collisionRenderItemsMap[item]); + } + scene->enqueueTransaction(transaction); + } +} + bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 395a45b952..ee68258e2d 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -84,6 +84,7 @@ public: // new Scene/Engine rendering support void setVisibleInScene(bool newValue, const render::ScenePointer& scene); void setLayeredInFront(bool layered, const render::ScenePointer& scene); + void setLayeredInHUD(bool layered, const render::ScenePointer& scene); bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 7681642753..a0bef828fa 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -187,14 +187,19 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren } // Overlays - const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).asVarying(); - const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).asVarying(); - task.addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); - task.addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - { // Debug the bounds of the rendered Overlay items, still look at the zbuffer - task.addJob("DrawOverlayOpaqueBounds", overlayOpaques); - task.addJob("DrawOverlayTransparentBounds", overlayTransparents); + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer + task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); + task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); } // Debugging stages @@ -233,9 +238,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // AA job to be revisited task.addJob("Antialiasing", primaryFramebuffer); - // Composite the HUD + // Composite the HUD and HUD overlays task.addJob("HUD"); + const auto overlaysHUDOpaque = filteredOverlaysOpaque.getN(1); + const auto overlaysHUDTransparent = filteredOverlaysTransparent.getN(1); + + const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel).asVarying(); + const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); + task.addJob("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer + task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); + task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDTransparent); + } + task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); // Blit! diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index e0298c2a44..49a9ada91e 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -21,19 +21,24 @@ using namespace render; -void FilterLayeredItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) { +void FilterLayeredItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, Outputs& outputs) { auto& scene = renderContext->_scene; - // Clear previous values - outItems.clear(); + ItemBounds matchedItems; + ItemBounds nonMatchItems; // For each item, filter it into one bucket for (auto& itemBound : inItems) { auto& item = scene->getItem(itemBound.id); if (item.getLayer() == _keepLayer) { - outItems.emplace_back(itemBound); + matchedItems.emplace_back(itemBound); + } else { + nonMatchItems.emplace_back(itemBound); } } + + outputs.edit0() = matchedItems; + outputs.edit1() = nonMatchItems; } void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) { diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index 1e023a8bb9..a7180b6cde 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -65,15 +65,15 @@ namespace render { // Filter the items belonging to the job's keep layer class FilterLayeredItems { public: - using JobModel = Job::ModelIO; + using Outputs = render::VaryingSet2; + using JobModel = Job::ModelIO; - FilterLayeredItems() {} FilterLayeredItems(int keepLayer) : _keepLayer(keepLayer) {} - int _keepLayer { 0 }; + int _keepLayer; - void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems); + void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, Outputs& outputs); }; // SliceItems job config defining the slice range diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 96faf9719e..8cdb41de45 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -356,9 +356,14 @@ public: // Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace()) const Bound getBound() const { return _payload->getBound(); } - // Get the layer where the item belongs. 0 by default meaning NOT LAYERED + // Get the layer where the item belongs. int getLayer() const { return _payload->getLayer(); } + static const int LAYER_2D = 0; + static const int LAYER_3D = 1; + static const int LAYER_3D_FRONT = 2; + static const int LAYER_3D_HUD = 3; + // Render call for the item void render(RenderArgs* args) const { _payload->render(args); } From a5f5f9fc5d7b3d4a6c2fddf54961b3c5070e441c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 12:05:13 -0700 Subject: [PATCH 419/504] try to improve performance --- .../src/EntityTreeRenderer.cpp | 10 ------- .../src/RenderableEntityItem.cpp | 17 ------------ .../src/RenderableEntityItem.h | 26 ------------------- .../src/RenderableModelEntityItem.cpp | 9 ++++--- .../src/RenderableModelEntityItem.h | 6 ++--- 5 files changed, 8 insertions(+), 60 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 0cb25a2e2f..f8c40a5229 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -222,16 +222,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene _renderablesToUpdate.insert({ entityId, renderable }); } - // NOTE: Looping over all the entity renderers is likely to be a bottleneck in the future - // Currently, this is necessary because the model entity loading logic requires constant polling - // This was working fine because the entity server used to send repeated updates as your view changed, - // but with the improved entity server logic (PR 11141), updateInScene (below) would not be triggered enough - for (const auto& entry : _entitiesInScene) { - const auto& renderable = entry.second; - if (renderable) { - renderable->update(scene, transaction); - } - } if (!_renderablesToUpdate.empty()) { for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index ea514d3181..3f1e89b86c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -291,18 +291,6 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans }); } -void EntityRenderer::update(const ScenePointer& scene, Transaction& transaction) { - if (!isValidRenderItem()) { - return; - } - - if (!needsUpdate()) { - return; - } - - doUpdate(scene, transaction, _entity); -} - // // Internal methods // @@ -316,11 +304,6 @@ bool EntityRenderer::needsRenderUpdate() const { return needsRenderUpdateFromEntity(_entity); } -// Returns true if the item needs to have update called -bool EntityRenderer::needsUpdate() const { - return needsUpdateFromEntity(_entity); -} - // Returns true if the item in question needs to have updateInScene called because of changes in the entity bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity) const { bool success = false; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 56cb39252f..6b47ff8b1d 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -49,8 +49,6 @@ public: virtual bool addToScene(const ScenePointer& scene, Transaction& transaction) final; virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction); - virtual void update(const ScenePointer& scene, Transaction& transaction); - protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -73,12 +71,6 @@ protected: // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const; - // Returns true if the item in question needs to have update called - virtual bool needsUpdate() const; - - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const { return false; } - // Will be called on the main thread from updateInScene. This can be used to fetch things like // network textures or model geometry from resource caches virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } @@ -88,8 +80,6 @@ protected: // data in this method if using multi-threaded rendering virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity); - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } - // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; @@ -158,15 +148,6 @@ protected: onRemoveFromSceneTyped(_typedEntity); } - using Parent::needsUpdateFromEntity; - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const override final { - if (Parent::needsUpdateFromEntity(entity)) { - return true; - } - return needsUpdateFromTypedEntity(_typedEntity); - } - using Parent::needsRenderUpdateFromEntity; // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const override final { @@ -181,11 +162,6 @@ protected: doRenderUpdateSynchronousTyped(scene, transaction, _typedEntity); } - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) override final { - Parent::doUpdate(scene, transaction, entity); - doUpdateTyped(scene, transaction, _typedEntity); - } - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) override final { Parent::doRenderUpdateAsynchronous(entity); doRenderUpdateAsynchronousTyped(_typedEntity); @@ -194,8 +170,6 @@ protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { } - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void onAddToSceneTyped(const TypedEntityPointer& entity) { } virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) { } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d1e47fd906..19da8a77f4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1026,7 +1026,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { entity->copyAnimationJointDataToModel(); } -bool ModelEntityRenderer::needsUpdate() const { +bool ModelEntityRenderer::needsRenderUpdate() const { ModelPointer model; withReadLock([&] { model = _model; @@ -1061,10 +1061,10 @@ bool ModelEntityRenderer::needsUpdate() const { return true; } } - return Parent::needsUpdate(); + return Parent::needsRenderUpdate(); } -bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { +bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (resultWithReadLock([&] { if (entity->hasModel() != _hasModel) { return true; @@ -1126,7 +1126,7 @@ bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& e return false; } -void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { +void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { if (_hasModel != entity->hasModel()) { _hasModel = entity->hasModel(); } @@ -1250,6 +1250,7 @@ void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& void ModelEntityRenderer::handleModelLoaded(bool success) { if (success) { _modelJustLoaded = true; + emit requestRenderUpdate(); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ad0afeee0a..77121c30d9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -138,10 +138,10 @@ protected: virtual ItemKey getKey() override; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override; - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; - virtual bool needsUpdate() const override; + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; private: void animate(const TypedEntityPointer& entity); From 215193ad90425a980ac6cad229edbe9331b9c04c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 12:05:13 -0700 Subject: [PATCH 420/504] try to improve performance (cherry picked from commit a5f5f9fc5d7b3d4a6c2fddf54961b3c5070e441c) --- .../src/EntityTreeRenderer.cpp | 10 ------- .../src/RenderableEntityItem.cpp | 17 ------------ .../src/RenderableEntityItem.h | 26 ------------------- .../src/RenderableModelEntityItem.cpp | 9 ++++--- .../src/RenderableModelEntityItem.h | 6 ++--- 5 files changed, 8 insertions(+), 60 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 0cb25a2e2f..f8c40a5229 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -222,16 +222,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene _renderablesToUpdate.insert({ entityId, renderable }); } - // NOTE: Looping over all the entity renderers is likely to be a bottleneck in the future - // Currently, this is necessary because the model entity loading logic requires constant polling - // This was working fine because the entity server used to send repeated updates as your view changed, - // but with the improved entity server logic (PR 11141), updateInScene (below) would not be triggered enough - for (const auto& entry : _entitiesInScene) { - const auto& renderable = entry.second; - if (renderable) { - renderable->update(scene, transaction); - } - } if (!_renderablesToUpdate.empty()) { for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index ea514d3181..3f1e89b86c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -291,18 +291,6 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans }); } -void EntityRenderer::update(const ScenePointer& scene, Transaction& transaction) { - if (!isValidRenderItem()) { - return; - } - - if (!needsUpdate()) { - return; - } - - doUpdate(scene, transaction, _entity); -} - // // Internal methods // @@ -316,11 +304,6 @@ bool EntityRenderer::needsRenderUpdate() const { return needsRenderUpdateFromEntity(_entity); } -// Returns true if the item needs to have update called -bool EntityRenderer::needsUpdate() const { - return needsUpdateFromEntity(_entity); -} - // Returns true if the item in question needs to have updateInScene called because of changes in the entity bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity) const { bool success = false; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 56cb39252f..6b47ff8b1d 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -49,8 +49,6 @@ public: virtual bool addToScene(const ScenePointer& scene, Transaction& transaction) final; virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction); - virtual void update(const ScenePointer& scene, Transaction& transaction); - protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -73,12 +71,6 @@ protected: // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const; - // Returns true if the item in question needs to have update called - virtual bool needsUpdate() const; - - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const { return false; } - // Will be called on the main thread from updateInScene. This can be used to fetch things like // network textures or model geometry from resource caches virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } @@ -88,8 +80,6 @@ protected: // data in this method if using multi-threaded rendering virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity); - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } - // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; @@ -158,15 +148,6 @@ protected: onRemoveFromSceneTyped(_typedEntity); } - using Parent::needsUpdateFromEntity; - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const override final { - if (Parent::needsUpdateFromEntity(entity)) { - return true; - } - return needsUpdateFromTypedEntity(_typedEntity); - } - using Parent::needsRenderUpdateFromEntity; // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const override final { @@ -181,11 +162,6 @@ protected: doRenderUpdateSynchronousTyped(scene, transaction, _typedEntity); } - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) override final { - Parent::doUpdate(scene, transaction, entity); - doUpdateTyped(scene, transaction, _typedEntity); - } - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) override final { Parent::doRenderUpdateAsynchronous(entity); doRenderUpdateAsynchronousTyped(_typedEntity); @@ -194,8 +170,6 @@ protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { } - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void onAddToSceneTyped(const TypedEntityPointer& entity) { } virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) { } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d1e47fd906..19da8a77f4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1026,7 +1026,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { entity->copyAnimationJointDataToModel(); } -bool ModelEntityRenderer::needsUpdate() const { +bool ModelEntityRenderer::needsRenderUpdate() const { ModelPointer model; withReadLock([&] { model = _model; @@ -1061,10 +1061,10 @@ bool ModelEntityRenderer::needsUpdate() const { return true; } } - return Parent::needsUpdate(); + return Parent::needsRenderUpdate(); } -bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { +bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (resultWithReadLock([&] { if (entity->hasModel() != _hasModel) { return true; @@ -1126,7 +1126,7 @@ bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& e return false; } -void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { +void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { if (_hasModel != entity->hasModel()) { _hasModel = entity->hasModel(); } @@ -1250,6 +1250,7 @@ void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& void ModelEntityRenderer::handleModelLoaded(bool success) { if (success) { _modelJustLoaded = true; + emit requestRenderUpdate(); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ad0afeee0a..77121c30d9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -138,10 +138,10 @@ protected: virtual ItemKey getKey() override; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override; - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; - virtual bool needsUpdate() const override; + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; private: void animate(const TypedEntityPointer& entity); From a5b913a8b992f73216cc2c4af7c308deb85b9b09 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 4 Oct 2017 17:36:28 -0400 Subject: [PATCH 421/504] [Case 6491] Fixes minor spacing issue, on while loop missed previous eslint pass. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 191d7a0748..30c050d7e5 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -239,13 +239,13 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { while (degrees > 180) { - degrees -= 360; + degrees -= 360; } while (degrees <= -180) { - degrees += 360; + degrees += 360; } - + return degrees; } From 9de1ca4e1af3f22431a30af5e68bde0774692a9c Mon Sep 17 00:00:00 2001 From: druiz17 Date: Wed, 4 Oct 2017 13:44:36 -0700 Subject: [PATCH 422/504] allow farGrab module to run when stylus is visible --- .../controllerModules/tabletStylusInput.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 36ed7920dd..29fa878cb1 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -248,17 +248,20 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.nearGrabWantsToRun = function(controllerData) { - var moduleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; - var module = getEnabledModuleByName(moduleName); - var ready = module ? module.isReady(controllerData) : makeRunningValues(false, [], []); - return ready.active; + this.otherModuleNeedsToRun = function(controllerData) { + var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; + var grabOverlayModule = getEnabledModuleByName(grabOverlayModuleName); + var grabOverlayModuleReady = grabOverlayModule ? grabOverlayModule.isReady(controllerData) : makeRunningValues(false, [], []); + var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"; + var farGrabModule = getEnabledModuleByName(farGrabModuleName); + var farGrabModuleReady = farGrabModule ? farGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + return grabOverlayModuleReady.active || farGrabModuleReady.active; }; this.processStylus = function(controllerData) { this.updateStylusTip(); - if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.nearGrabWantsToRun(controllerData)) { + if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) { this.pointFinger(false); this.hideStylus(); return false; From ac81b22cc92480f6ff7769f41068d95c54205c9b Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 4 Oct 2017 20:24:20 -0400 Subject: [PATCH 423/504] [Case 6491] eslint pass: Fixes references to SelectionManager (details below). SelectionManager was being referred to as selectionManager. * eslint pass used .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 30c050d7e5..62e265b5fd 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1176,8 +1176,8 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -1484,8 +1484,8 @@ SelectionDisplay = (function() { // var translateHandlesVisible = true; // var selectionBoxVisible = true; var isPointLight = false; - if (selectionManager.selections.length === 1) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); + if (SelectionManager.selections.length === 1) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); isPointLight = (properties.type === "Light") && !properties.isSpotlight; } @@ -1529,8 +1529,8 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLE SIZES that.updateHandleSizes = function() { - if (selectionManager.hasSelection()) { - var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); + if (SelectionManager.hasSelection()) { + var diff = Vec3.subtract(SelectionManager.worldPosition, Camera.getPosition()); var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; var dimensions = SelectionManager.worldDimensions; var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; @@ -1864,10 +1864,10 @@ SelectionDisplay = (function() { if (wantDebug) { print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); } - var isSingleSelection = (selectionManager.selections.length === 1); + var isSingleSelection = (SelectionManager.selections.length === 1); if (isSingleSelection) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); var isLightSelection = (properties.type === "Light"); if (isLightSelection) { if (wantDebug) { @@ -2106,7 +2106,7 @@ SelectionDisplay = (function() { }); // Create more selection box overlays if we don't have enough - var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length; + var overlaysNeeded = SelectionManager.selections.length - selectionBoxes.length; for (var i = 0; i < overlaysNeeded; i++) { selectionBoxes.push( Overlays.addOverlay("cube", { @@ -2132,9 +2132,9 @@ SelectionDisplay = (function() { i = 0; // Only show individual selections boxes if there is more than 1 selection - if (selectionManager.selections.length > 1) { - for (; i < selectionManager.selections.length; i++) { - var props = Entities.getEntityProperties(selectionManager.selections[i]); + if (SelectionManager.selections.length > 1) { + for (; i < SelectionManager.selections.length; i++) { + var props = Entities.getEntityProperties(SelectionManager.selections[i]); // Adjust overlay position to take registrationPoint into account // centeredRP = registrationPoint with range [-0.5, 0.5] @@ -2149,7 +2149,7 @@ SelectionDisplay = (function() { var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; - if (i >= selectionManager.selections.length - 1) { + if (i >= SelectionManager.selections.length - 1) { color = {red: 255, green: 255, blue: 64}; } @@ -2244,13 +2244,13 @@ SelectionDisplay = (function() { visible: !inModeRotate, solid: true, position: { - x: selectionManager.worldPosition.x, + x: SelectionManager.worldPosition.x, y: grid.getOrigin().y, - z: selectionManager.worldPosition.z + z: SelectionManager.worldPosition.z }, dimensions: { - x: selectionManager.worldDimensions.x, - y: selectionManager.worldDimensions.z + x: SelectionManager.worldDimensions.x, + y: SelectionManager.worldDimensions.z }, rotation: Quat.fromPitchYawRollDegrees(90, 0, 0) }); @@ -2509,7 +2509,7 @@ SelectionDisplay = (function() { } constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions)); + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); vector = Vec3.subtract( grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); @@ -2604,7 +2604,7 @@ SelectionDisplay = (function() { } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; - var properties = selectionManager.savedProperties[id]; + var properties = SelectionManager.savedProperties[id]; var original = properties.position; var newPosition = Vec3.sum(properties.position, vector); @@ -3088,7 +3088,7 @@ SelectionDisplay = (function() { Vec3.print("Radius stretch: ", vector); } var length = vector.x + vector.y + vector.z; - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; var radius = props.dimensions.z / 2; var originalCutoff = props.cutoff; @@ -3097,7 +3097,7 @@ SelectionDisplay = (function() { var newSize = originalSize + length; var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI; - Entities.editEntity(selectionManager.selections[0], { + Entities.editEntity(SelectionManager.selections[0], { cutoff: cutoff }); @@ -3106,7 +3106,7 @@ SelectionDisplay = (function() { // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; // Find the axis being adjusted var size; @@ -3124,7 +3124,7 @@ SelectionDisplay = (function() { z: size }; - Entities.editEntity(selectionManager.selections[0], { + Entities.editEntity(SelectionManager.selections[0], { dimensions: newDimensions }); @@ -3631,8 +3631,8 @@ SelectionDisplay = (function() { rotationNormal[rotAroundAxis] = 1; // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); innerRadius = diagonal; outerRadius = diagonal * 1.15; var innerAlpha = 0.2; @@ -3955,7 +3955,7 @@ SelectionDisplay = (function() { return false; } - entityIconOverlayManager.setIconsSelectable(selectionManager.selections, true); + entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true); var hitTool = grabberTools[ hitOverlayID ]; if (hitTool) { From 3a6e84e681a070e2bf1f809c9ca151ebd1c15f8a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 17:22:21 -0700 Subject: [PATCH 424/504] trying to fix model issue --- .../src/RenderableModelEntityItem.cpp | 17 ++++------------- .../src/RenderableModelEntityItem.h | 3 --- libraries/render-utils/src/Model.cpp | 2 ++ libraries/render-utils/src/Model.h | 3 ++- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 19da8a77f4..11c97f6716 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1032,10 +1032,6 @@ bool ModelEntityRenderer::needsRenderUpdate() const { model = _model; }); - if (_modelJustLoaded) { - return true; - } - if (model) { if (_needsJointSimulation || _moving || _animating) { return true; @@ -1149,14 +1145,15 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce model->removeFromScene(scene, transaction); withWriteLock([&] { _model.reset(); }); } + emit requestRenderUpdate(); return; } - _modelJustLoaded = false; // Check for addition if (_hasModel && !(bool)_model) { model = std::make_shared(nullptr, entity.get()); - connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::handleModelLoaded); + connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::requestRenderUpdate); + connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); model->init(); entity->setModel(model); @@ -1172,6 +1169,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // Nothing else to do unless the model is loaded if (!model->isLoaded()) { + emit needsRenderUpdate(); return; } @@ -1247,13 +1245,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } -void ModelEntityRenderer::handleModelLoaded(bool success) { - if (success) { - _modelJustLoaded = true; - emit requestRenderUpdate(); - } -} - // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items void ModelEntityRenderer::doRender(RenderArgs* args) { PROFILE_RANGE(render_detail, "MetaModelRender"); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 77121c30d9..d1424316e9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -151,7 +151,6 @@ private: // Transparency is handled in ModelMeshPartPayload virtual bool isTransparent() const override { return false; } - bool _modelJustLoaded { false }; bool _hasModel { false }; ::ModelPointer _model; GeometryResource::Pointer _compoundShapeResource; @@ -180,8 +179,6 @@ private: uint64_t _lastAnimated { 0 }; float _currentFrame { 0 }; -private slots: - void handleModelLoaded(bool success); }; } } // namespace diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index f25cad8a6e..837f485417 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -814,11 +814,13 @@ void Model::setTextures(const QVariantMap& textures) { _needsUpdateTextures = true; _needsFixupInScene = true; _renderGeometry->setTextures(textures); + emit requestRenderUpdate(); } else { // FIXME(Huffman): Disconnect previously connected lambdas so we don't set textures multiple // after the geometry has finished loading. connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, [this, textures]() { _renderGeometry->setTextures(textures); + emit requestRenderUpdate(); }); } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6d338b4598..95dc171ff5 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -103,7 +103,7 @@ public: bool isLayeredInFront() const { return _isLayeredInFront; } virtual void updateRenderItems(); - void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; } + void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; emit requestRenderUpdate(); } bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } AABox getRenderableMeshBound() const; const render::ItemIDs& fetchRenderItemIDs() const; @@ -265,6 +265,7 @@ public slots: signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); + void requestRenderUpdate(); protected: From e9ae2099f3910f5d7b77a6a0af62b7f3ae0e2297 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 4 Oct 2017 20:37:57 -0400 Subject: [PATCH 425/504] [Case 6491] eslint pass: Fixes indent issues. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- .../system/libraries/entitySelectionTool.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 62e265b5fd..647080b742 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2429,7 +2429,7 @@ SelectionDisplay = (function() { var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it var elevation = translateXZTool.elevation(pickRay.origin, pick); if (wantDebug) { - print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { @@ -2642,11 +2642,11 @@ SelectionDisplay = (function() { // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { - return { - x: v1.x * v2.x, - y: v1.y * v2.y, - z: v1.z * v2.z - }; + return { + x: v1.x * v2.x, + y: v1.y * v2.y, + z: v1.z * v2.z + }; }; // FUNCTION: MAKE STRETCH TOOL @@ -2740,8 +2740,7 @@ SelectionDisplay = (function() { deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), - centeredRP)); + Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); } @@ -2921,8 +2920,8 @@ SelectionDisplay = (function() { } else { newPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + pickRayPosition, + planeNormal); vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); From 18d884f6c45c00cc4086fa2472139bd6d82d7afc Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 4 Oct 2017 20:44:47 -0400 Subject: [PATCH 426/504] [Case 6491] eslint pass: Fixes no-multiple-empty-lines issues. * eslint pass using .eslintrc.js Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 647080b742..68cdf91dc1 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2016,7 +2016,6 @@ SelectionDisplay = (function() { }// end of isSingleSelection - Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, rotation: rotation, @@ -2515,7 +2514,6 @@ SelectionDisplay = (function() { cornerPosition); - for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; if (!properties) { @@ -2639,7 +2637,6 @@ SelectionDisplay = (function() { }); - // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { return { @@ -2677,8 +2674,7 @@ SelectionDisplay = (function() { y: Math.abs(direction.y) > 0 ? 1 : 0, z: Math.abs(direction.z) > 0 ? 1 : 0 }; - - + var numDimensions = mask.x + mask.y + mask.z; @@ -4187,7 +4183,6 @@ SelectionDisplay = (function() { Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); - return that; }()); From 4855e0f528a55dbd58ee66609dbd16c22feb8a69 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 5 Oct 2017 14:36:25 +1300 Subject: [PATCH 427/504] Fix children's rotations drifting when scale group --- scripts/shapes/modules/selection.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index 5c59c20aec..e88b0f8809 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -55,6 +55,7 @@ SelectionManager = function (side) { position: properties.position, parentID: properties.parentID, localPosition: properties.localPosition, + localRotation: properties.localRotation, registrationPoint: properties.registrationPoint, rotation: properties.rotation, dimensions: properties.dimensions, @@ -363,7 +364,8 @@ SelectionManager = function (side) { for (i = 1, length = selection.length; i < length; i++) { Entities.editEntity(selection[i].id, { dimensions: Vec3.multiply(factor, selection[i].dimensions), - localPosition: Vec3.multiply(factor, selection[i].localPosition) + localPosition: Vec3.multiply(factor, selection[i].localPosition), + localRotation: selection[i].localRotation // Always specify localRotation otherwise rotations can drift. }); } @@ -405,7 +407,8 @@ SelectionManager = function (side) { entityID: selection[i].id, properties: { dimensions: selection[i].dimensions, - localPosition: selection[i].localPosition + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation } }); selection[i].dimensions = Vec3.multiply(scaleFactor, selection[i].dimensions); @@ -414,7 +417,8 @@ SelectionManager = function (side) { entityID: selection[i].id, properties: { dimensions: selection[i].dimensions, - localPosition: selection[i].localPosition + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation } }); } @@ -476,7 +480,8 @@ SelectionManager = function (side) { for (i = 1, length = selection.length; i < length; i++) { Entities.editEntity(selection[i].id, { dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), - localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition) + localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition), + localRotation: selection[i].localRotation // Always specify localRotation otherwise rotations can drift. }); } @@ -518,7 +523,8 @@ SelectionManager = function (side) { entityID: selection[i].id, properties: { dimensions: selection[i].dimensions, - localPosition: selection[i].localPosition + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation } }); selection[i].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[i].dimensions); @@ -527,7 +533,8 @@ SelectionManager = function (side) { entityID: selection[i].id, properties: { dimensions: selection[i].dimensions, - localPosition: selection[i].localPosition + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation } }); } From b526ec0d9332e5505a4ea82d2f8163c7a4b1b153 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 5 Oct 2017 18:21:04 +1300 Subject: [PATCH 428/504] Code review --- scripts/shapes/modules/createPalette.js | 3 +- scripts/shapes/modules/selection.js | 18 +++++---- scripts/shapes/modules/toolsMenu.js | 40 +++++++++++-------- scripts/shapes/shapes.js | 6 ++- .../controllerModules/inVREditMode.js | 2 +- scripts/system/libraries/utils.js | 2 +- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/scripts/shapes/modules/createPalette.js b/scripts/shapes/modules/createPalette.js index af22c4e4ff..694705cb2f 100644 --- a/scripts/shapes/modules/createPalette.js +++ b/scripts/shapes/modules/createPalette.js @@ -334,8 +334,9 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { function setHand(hand) { // Assumes UI is not displaying. + var NUMBER_OF_HANDS = 2; side = hand; - otherSide = (side + 1) % 2; + otherSide = (side + 1) % NUMBER_OF_HANDS; controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; paletteLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index e88b0f8809..afa0da5fd2 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -168,14 +168,16 @@ SelectionManager = function (side) { for (i = 1, length = selection.length; i < length; i++) { registration = selection[i].registrationPoint; - corners[0] = { x: -registration.x, y: -registration.y, z: -registration.z }; - corners[1] = { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }; - corners[2] = { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }; - corners[3] = { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; - corners[4] = { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }; - corners[5] = { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }; - corners[6] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }; - corners[7] = { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }; + corners = [ + { x: -registration.x, y: -registration.y, z: -registration.z }, + { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }, + { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }, + { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }, + { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }, + { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }, + { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }, + { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z } + ]; position = selection[i].position; rotation = selection[i].rotation; diff --git a/scripts/shapes/modules/toolsMenu.js b/scripts/shapes/modules/toolsMenu.js index 08476f9251..19c114c8e9 100644 --- a/scripts/shapes/modules/toolsMenu.js +++ b/scripts/shapes/modules/toolsMenu.js @@ -129,6 +129,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, MENU_HEADER_BACK_PROPERTIES = { + // Magic numbers per UI design spec. url: Script.resolvePath("../assets/tools/back-icon.svg"), dimensions: { x: 0.0069, y: 0.0107 }, localPosition: { @@ -200,7 +201,8 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { SWATCH_HIGHLIGHT_DELTA = 0.0020, UI_ELEMENTS = { - "menuButton": { + // Magic numbers per UI design spec. + menuButton: { overlay: "cube", // Invisible cube for hit area. properties: { dimensions: UIT.dimensions.itemCollisionZone, @@ -268,7 +270,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } }, - "button": { + button: { overlay: "cube", properties: { dimensions: UIT.dimensions.buttonDimensions, @@ -289,7 +291,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white } }, - "toggleButton": { + toggleButton: { overlay: "cube", properties: { dimensions: UIT.dimensions.buttonDimensions, @@ -327,7 +329,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { offSublabel: { } // Optional properties to update sublabel with. */ }, - "swatch": { + swatch: { overlay: "shape", properties: { shape: "Cylinder", @@ -344,7 +346,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { // Setting property may optionally include a defaultValue. // A setting value of "" means that the swatch is unpopulated. }, - "swatchHighlight": { + swatchHighlight: { overlay: "shape", properties: { shape: "Cylinder", @@ -364,7 +366,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } }, - "square": { + square: { overlay: "cube", // Emulate a 2D square with a cube. properties: { localRotation: Quat.ZERO, @@ -375,7 +377,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "image": { + image: { overlay: "image3d", properties: { localPosition: { x: 0, y: 0, z: 0 }, @@ -387,7 +389,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "horizontalRule": { + horizontalRule: { overlay: "image3d", properties: { url: Script.resolvePath("../assets/horizontal-rule.svg"), @@ -401,7 +403,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "sphere": { + sphere: { overlay: "sphere", properties: { dimensions: { x: 0.01, y: 0.01, z: 0.01 }, @@ -413,7 +415,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "barSlider": { + barSlider: { // Invisible cube to catch laser intersections; value and remainder entities move inside. // Values range between 0.0 and 1.0. overlay: "cube", @@ -470,7 +472,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { } } }, - "imageSlider": { // Values range between 0.0 and 1.0. + imageSlider: { // Values range between 0.0 and 1.0. overlay: "cube", properties: { dimensions: { x: 0.0160, y: 0.1229, z: UIT.dimensions.buttonDimensions.z }, @@ -485,7 +487,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { imageURL: null, imageOverlayURL: null }, - "sliderPointer": { + sliderPointer: { overlay: "shape", properties: { shape: "Cone", @@ -498,7 +500,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "colorCircle": { + colorCircle: { overlay: "shape", properties: { shape: "Cylinder", @@ -513,7 +515,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { imageURL: null, imageOverlayURL: null }, - "circlePointer": { + circlePointer: { overlay: "shape", properties: { shape: "Cone", @@ -526,7 +528,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { visible: true } }, - "picklist": { + picklist: { overlay: "cube", properties: { dimensions: { x: 0.06, y: 0.02, z: 0.01 }, @@ -542,7 +544,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { color: UIT.colors.white } }, - "picklistItem": { // Note: When using, declare before picklist item that it's being used in. + picklistItem: { // Note: When using, declare before picklist item that it's being used in. overlay: "cube", properties: { dimensions: { x: 0.1416, y: 0.0280, z: UIT.dimensions.buttonDimensions.z }, @@ -589,6 +591,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { }, OPTONS_PANELS = { + // Magic numbers per UI design spec. colorOptions: [ { id: "colorSwatchesLabel", @@ -1683,6 +1686,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { MENU_ITEM_YS = [0.086, 0.030, -0.026, -0.082], MENU_ITEMS = [ + // Magic numbers per UI design spec. { id: "colorButton", type: "menuButton", @@ -1921,6 +1925,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { DELETE_TOOL = 5, FOOTER_ITEMS = [ + // Magic numbers per UI design spec. { id: "footerRule", type: "horizontalRule", @@ -2041,6 +2046,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { function setHand(hand) { // Assumes UI is not displaying. + var NUMBER_OF_HANDS = 2; side = hand; if (side === LEFT_HAND) { controlHand = rightInputs.hand(); @@ -2055,7 +2061,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { menuOriginLocalPosition = PANEL_ORIGIN_POSITION_RIGHT_HAND; menuOriginLocalRotation = PANEL_ORIGIN_ROTATION_RIGHT_HAND; } - otherSide = (side + 1) % 2; + otherSide = (side + 1) % NUMBER_OF_HANDS; } setHand(side); diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index b4e822c233..465ce6258f 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -113,7 +113,8 @@ } function otherHand(hand) { - return (hand + 1) % 2; + var NUMBER_OF_HANDS = 2; + return (hand + 1) % NUMBER_OF_HANDS; } App = { @@ -1989,7 +1990,8 @@ Entities.canRezChanged.disconnect(onCanRezChanged); Entities.canRezTmpChanged.disconnect(onCanRezChanged); Messages.messageReceived.disconnect(onMessageReceived); - Messages.unsubscribe(DOMAIN_CHANGED_MESSAGE); + // Messages.unsubscribe(DOMAIN_CHANGED_MESSAGE); Do NOT unsubscribe because edit.js also subscribes and + // Messages.subscribe works client-wide. MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.skeletonChanged.disconnect(onSkeletonChanged); diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 952bd14fd7..e3035b26f2 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -105,4 +105,4 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); disableDispatcherModule("RightHandInVREditMode"); }; Script.scriptEnding.connect(this.cleanup); -}()); \ No newline at end of file +}()); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index fa9e60a4ef..e6730b8826 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// note: this constant is currently duplicated in edit.js and ambientSounds.js +// note: this constant is currently duplicated in edit.js and ambientSound.js EDIT_SETTING = "io.highfidelity.isEditing"; isInEditMode = function isInEditMode() { return Settings.getValue(EDIT_SETTING); From 193dc83b99878a9aeea73b5688c78b0db13080bf Mon Sep 17 00:00:00 2001 From: vladest Date: Thu, 5 Oct 2017 13:59:42 +0200 Subject: [PATCH 429/504] Fix alignment of audio devices input header --- interface/resources/qml/hifi/audio/Audio.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index b1f80ac5e8..12c2ec1835 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -167,6 +167,9 @@ Rectangle { } RalewayRegular { anchors.verticalCenter: parent.verticalCenter; + width: margins.sizeText + margins.sizeLevel + anchors.left: parent.left + anchors.leftMargin: margins.sizeCheckBox size: 16; color: hifi.colors.lightGrayText; text: qsTr("CHOOSE INPUT DEVICE"); From 218de29356eb515774efb7e31cc7e9bf5467308d Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 5 Oct 2017 07:39:21 -0700 Subject: [PATCH 430/504] disable compiler optimization as temporary fix --- libraries/audio/src/AudioGate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 5b2561da07..18884d008a 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -13,6 +13,10 @@ #include #include "AudioDynamics.h" +#ifdef __clang__ +#pragma clang optimize off +#endif + // log2 domain headroom bits above 0dB (int32_t) static const int LOG2_HEADROOM_Q30 = 1; From 347645329eaa14071792d032672ec04e82168e17 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Thu, 5 Oct 2017 09:11:02 -0700 Subject: [PATCH 431/504] fixing laser staying on in edit mode --- .../system/controllers/controllerModules/farActionGrabEntity.js | 1 - scripts/system/libraries/entitySelectionTool.js | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index c5b82f75f0..81ffaa4189 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -408,7 +408,6 @@ Script.include("/~/system/libraries/controllers.js"); this.distanceRotating = false; if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.updateLaserPointer(controllerData); this.prepareDistanceRotatingData(controllerData); return makeRunningValues(true, [], []); } else { diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4df25c41b7..010c86f4d4 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -4076,7 +4076,6 @@ SelectionDisplay = (function() { if (controllerPose.valid && lastControllerPoses[hand].valid) { if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { - print("setting controller pose"); that.mouseMoveEvent({}); } } From d8341a0929e6992bbdb670f9b684773a854e9a4c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 5 Oct 2017 09:27:07 -0700 Subject: [PATCH 432/504] Work around compiler optimization bug --- libraries/audio/src/AudioDynamics.h | 19 +++++++++++-------- libraries/audio/src/AudioGate.cpp | 4 ---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index 08daa21348..a759e1c63c 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -31,6 +31,9 @@ #define MULQ31(a,b) ((int32_t)(MUL64(a, b) >> 31)) #define MULDIV64(a,b,c) (int32_t)(MUL64(a, b) / (c)) +#define ADDMOD32(a,b) (int32_t)((uint32_t)(a) + (uint32_t)(b)) +#define SUBMOD32(a,b) (int32_t)((uint32_t)(a) - (uint32_t)(b)) + // // on x86 architecture, assume that SSE2 is present // @@ -399,14 +402,14 @@ public: x = MULHI(x, CICGAIN); _buffer[i] = _acc1; - _acc1 += x; // integrator + _acc1 = ADDMOD32(_acc1, x); // integrator i = (i + CIC1 - 1) & MASK; - x = _acc1 - _buffer[i]; // comb + x = SUBMOD32(_acc1, _buffer[i]); // comb _buffer[i] = _acc2; - _acc2 += x; // integrator + _acc2 = ADDMOD32(_acc2, x); // integrator i = (i + CIC2 - 1) & MASK; - x = _acc2 - _buffer[i]; // comb + x = SUBMOD32(_acc2, _buffer[i]); // comb _index = (i + 1) & MASK; // skip unused tap return x; @@ -464,14 +467,14 @@ public: x = MULHI(x, CICGAIN); _buffer[i] = _acc1; - _acc1 += x; // integrator + _acc1 = ADDMOD32(_acc1, x); // integrator i = (i + CIC1 - 1) & MASK; - x = _acc1 - _buffer[i]; // comb + x = SUBMOD32(_acc1, _buffer[i]); // comb _buffer[i] = _acc2; - _acc2 += x; // integrator + _acc2 = ADDMOD32(_acc2, x); // integrator i = (i + CIC2 - 1) & MASK; - x = _acc2 - _buffer[i]; // comb + x = SUBMOD32(_acc2, _buffer[i]); // comb _index = (i + 1) & MASK; // skip unused tap return x; diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 18884d008a..5b2561da07 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -13,10 +13,6 @@ #include #include "AudioDynamics.h" -#ifdef __clang__ -#pragma clang optimize off -#endif - // log2 domain headroom bits above 0dB (int32_t) static const int LOG2_HEADROOM_Q30 = 1; From 82d13090f9a40e6a30372581658663942e6eea8c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 5 Oct 2017 09:28:30 -0700 Subject: [PATCH 433/504] Better comments --- libraries/audio/src/AudioDynamics.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index a759e1c63c..b7c168bfab 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -397,6 +397,8 @@ public: // Fast FIR attack/lowpass filter using a 2-stage CIC filter. // The step response reaches final value after N-1 samples. + // NOTE: CIC integrators intentionally overflow, using modulo arithmetic. + // See E. B. Hogenauer, "An economical class of digital filters for decimation and interpolation" const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 x = MULHI(x, CICGAIN); @@ -462,6 +464,8 @@ public: // Fast FIR attack/lowpass filter using a 2-stage CIC filter. // The step response reaches final value after N-1 samples. + // NOTE: CIC integrators intentionally overflow, using modulo arithmetic. + // See E. B. Hogenauer, "An economical class of digital filters for decimation and interpolation" const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 x = MULHI(x, CICGAIN); From 8822b1bfa425dc1434d363df542d094543589dc8 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 5 Oct 2017 09:49:38 -0700 Subject: [PATCH 434/504] Minor cleanup --- libraries/audio/src/AudioGate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 5b2561da07..13c794b923 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -6,12 +6,12 @@ // Copyright 2017 High Fidelity, Inc. // -#include "AudioGate.h" - #include +#include #include -#include + #include "AudioDynamics.h" +#include "AudioGate.h" // log2 domain headroom bits above 0dB (int32_t) static const int LOG2_HEADROOM_Q30 = 1; @@ -418,7 +418,7 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { _dc.process(x); // peak detect - int32_t peak = std::abs(x); + int32_t peak = abs(x); // convert to log2 domain peak = fixlog2(peak); From d605bd9ef4f1da448ccc1561df1cb12d5ce5b480 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 5 Oct 2017 11:01:15 -0700 Subject: [PATCH 435/504] Preprocess inventory endpoint result --- .../qml/hifi/commerce/purchases/Purchases.qml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 0bb1515b69..1f7f2e6e53 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -81,8 +81,10 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { + var inventoryResult = processInventoryResult(result.data.assets); + purchasesModel.clear(); - purchasesModel.append(result.data.assets); + purchasesModel.append(inventoryResult); if (previousPurchasesModel.count !== 0) { checkIfAnyItemStatusChanged(); @@ -93,7 +95,7 @@ Rectangle { purchasesModel.setProperty(i, "statusChanged", false); } } - previousPurchasesModel.append(result.data.assets); + previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); @@ -590,6 +592,17 @@ Rectangle { // FUNCTION DEFINITIONS START // + function processInventoryResult(inventory) { + for (var i = 0; i < inventory.length; i++) { + if (inventory[i].status.length > 1) { + console.log("WARNING: Inventory result index " + i + " has a status of length >1!") + } + inventory[i].status = inventory[i].status[0]; + inventory[i].categories = inventory[i].categories.join(';'); + } + return inventory; + } + function populateDisplayedItemCounts() { var itemCountDictionary = {}; var currentItemId; From b3c3b2d34fbab47b720c8a6b1876568433ed8097 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Thu, 5 Oct 2017 19:42:31 +0100 Subject: [PATCH 436/504] Switched Time & Session ID Over --- libraries/shared/src/shared/FileLogger.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index b019b69fb8..8ceb378574 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -41,7 +41,7 @@ private: -static const QString FILENAME_FORMAT = "hifi-log%1_%2.txt"; +static const QString FILENAME_FORMAT = "hifi-log_%1%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; @@ -69,7 +69,7 @@ QString getLogRollerFilename() { fileSessionID = "_" + SESSION_ID.toString().replace("{", "").replace("}", ""); } - result.append(QString(FILENAME_FORMAT).arg(fileSessionID, now.toString(DATETIME_FORMAT))); + result.append(QString(FILENAME_FORMAT).arg(now.toString(DATETIME_FORMAT), fileSessionID)); return result; } From 53a49272dc0e5f99ac8ea5dd8a67bb4b17ba4c63 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 5 Oct 2017 11:43:19 -0700 Subject: [PATCH 437/504] fix importing of avatar-entities --- interface/src/avatar/MyAvatar.cpp | 6 ++++++ interface/src/avatar/MyAvatar.h | 2 ++ .../entities/src/EntityEditPacketSender.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 15 +++++++++------ libraries/shared/src/SpatiallyNestable.cpp | 18 ++++++++++++++---- .../libraries/controllerDispatcherUtils.js | 2 +- 6 files changed, 33 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 10e2202553..5d82405aee 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3238,3 +3238,9 @@ void MyAvatar::setModelScale(float scale) { emit sensorToWorldScaleChanged(sensorToWorldScale); } } + +SpatialParentTree* MyAvatar::getParentTree() const { + auto entityTreeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; + return entityTree.get(); +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9620d61a49..952315a85f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -544,6 +544,8 @@ public: float getUserHeight() const; float getUserEyeHeight() const; + virtual SpatialParentTree* getParentTree() const override; + public slots: void increaseSize(); void decreaseSize(); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index ee0fcf8218..f93b6a49ec 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -45,7 +45,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, } EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); if (!entity) { - qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; + qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID; return; } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e0187cd2b6..ef1d27640c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1842,7 +1842,13 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void QHash::iterator iter = args->map->find(oldID); if (iter == args->map->end()) { - EntityItemID newID = QUuid::createUuid(); + EntityItemID newID; + if (oldID == AVATAR_SELF_ID) { + auto nodeList = DependencyManager::get(); + newID = EntityItemID(nodeList->getSessionUUID()); + } else { + newID = QUuid::createUuid(); + } args->map->insert(oldID, newID); return newID; } @@ -1859,8 +1865,8 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void properties.setPosition(properties.getPosition() + args->root); } else { EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID); - if (parentEntity) { // map the parent - properties.setParentID(getMapped(parentEntity->getID())); + if (parentEntity || oldParentID == AVATAR_SELF_ID) { // map the parent + properties.setParentID(getMapped(oldParentID)); // But do not add root offset in this case. } else { // Should not happen, but let's try to be helpful... item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); @@ -1940,9 +1946,6 @@ bool EntityTree::readFromMap(QVariantMap& map) { if (properties.getClientOnly()) { properties.setOwningAvatarID(myNodeID); } - if (properties.getParentID() == AVATAR_SELF_ID) { - properties.setParentID(myNodeID); - } EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 8ea38f5f13..8c43632456 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -102,8 +102,11 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons if (parent && parent->getID() == parentID) { // parent pointer is up-to-date if (!_parentKnowsMe) { - parent->beParentOfChild(getThisPointer()); - _parentKnowsMe = true; + SpatialParentTree* parentTree = parent->getParentTree(); + if (!parentTree || parentTree == getParentTree()) { + parent->beParentOfChild(getThisPointer()); + _parentKnowsMe = true; + } } success = true; return parent; @@ -129,8 +132,15 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons parent = _parent.lock(); if (parent) { - parent->beParentOfChild(getThisPointer()); - _parentKnowsMe = true; + + // it's possible for an entity with a parent of AVATAR_SELF_ID can be imported into a side-tree + // such as the clipboard's. if this is the case, we don't want the parent to consider this a + // child. + SpatialParentTree* parentTree = parent->getParentTree(); + if (!parentTree || parentTree == getParentTree()) { + parent->beParentOfChild(getThisPointer()); + _parentKnowsMe = true; + } } success = (parent || parentID.isNull()); diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index e9e25b058b..d6d80541ee 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -222,7 +222,7 @@ getControllerJointIndex = function (hand) { return controllerJointIndex; } - return MyAvatar.getJointIndex("Head"); + return -1; }; propsArePhysical = function (props) { From a6b7578c3c923578aa60133b4a1695320edce081 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Oct 2017 11:53:16 -0700 Subject: [PATCH 438/504] start fixing asynch issue, fixes model loading! --- .../src/RenderableEntityItem.cpp | 36 +++---- .../src/RenderableEntityItem.h | 4 +- .../src/RenderableModelEntityItem.cpp | 2 - .../src/RenderableShapeEntityItem.cpp | 53 +++++----- .../src/RenderableWebEntityItem.cpp | 98 +++++++++---------- .../src/RenderableWebEntityItem.h | 1 - libraries/render-utils/src/Model.cpp | 7 +- libraries/render-utils/src/Model.h | 2 +- 8 files changed, 102 insertions(+), 101 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 3f1e89b86c..c38d106bfa 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -329,25 +329,27 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return false; } -void EntityRenderer::doRenderUpdateAsynchronous(const EntityItemPointer& entity) { - auto transparent = isTransparent(); - if (_prevIsTransparent && !transparent) { - _isFading = false; - } - _prevIsTransparent = transparent; +void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { + withWriteLock([&] { + auto transparent = isTransparent(); + if (_prevIsTransparent && !transparent) { + _isFading = false; + } + _prevIsTransparent = transparent; - bool success = false; - auto bound = entity->getAABox(success); - if (success) { - _bound = bound; - } - auto newModelTransform = entity->getTransformToCenter(success); - if (success) { - _modelTransform = newModelTransform; - } + bool success = false; + auto bound = entity->getAABox(success); + if (success) { + _bound = bound; + } + auto newModelTransform = entity->getTransformToCenter(success); + if (success) { + _modelTransform = newModelTransform; + } - _moving = entity->isMovingRelativeToParent(); - _visible = entity->getVisible(); + _moving = entity->isMovingRelativeToParent(); + _visible = entity->getVisible(); + }); } void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 6b47ff8b1d..6581e923bd 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -73,12 +73,12 @@ protected: // Will be called on the main thread from updateInScene. This can be used to fetch things like // network textures or model geometry from resource caches - virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } + virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity); // Will be called by the lambda posted to the scene in updateInScene. // This function will execute on the rendering thread, so you cannot use network caches to fetch // data in this method if using multi-threaded rendering - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity); + virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) { } // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 11c97f6716..064eacdb35 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1145,7 +1145,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce model->removeFromScene(scene, transaction); withWriteLock([&] { _model.reset(); }); } - emit requestRenderUpdate(); return; } @@ -1169,7 +1168,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // Nothing else to do unless the model is loaded if (!model->isLoaded()) { - emit needsRenderUpdate(); return; } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 8fcf2c090d..4028f105c8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -88,31 +88,33 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } _color = vec4(toGlm(entity->getXColor()), entity->getLocalRenderAlpha()); + + _shape = entity->getShape(); + _position = entity->getPosition(); + _dimensions = entity->getDimensions(); + _orientation = entity->getOrientation(); + + if (_shape == entity::Sphere) { + _modelTransform.postScale(SPHERE_ENTITY_SCALE); + } + + _modelTransform.postScale(_dimensions); }); } void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - if (_procedural.isEnabled() && _procedural.isFading()) { - float isFading = Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; - _procedural.setIsFading(isFading); - } - - _shape = entity->getShape(); - _position = entity->getPosition(); - _dimensions = entity->getDimensions(); - _orientation = entity->getOrientation(); - - if (_shape == entity::Sphere) { - _modelTransform.postScale(SPHERE_ENTITY_SCALE); - } - - _modelTransform.postScale(_dimensions); + withReadLock([&] { + if (_procedural.isEnabled() && _procedural.isFading()) { + float isFading = Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; + _procedural.setIsFading(isFading); + } + }); } bool ShapeEntityRenderer::isTransparent() const { if (_procedural.isEnabled() && _procedural.isFading()) { return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; - } + } // return _entity->getLocalRenderAlpha() < 1.0f || Parent::isTransparent(); return Parent::isTransparent(); @@ -126,15 +128,16 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; - auto geometryShape = MAPPING[_shape]; - batch.setModelTransform(_modelTransform); // use a transform with scale, rotation, registration point and translation - + GeometryCache::Shape geometryShape; bool proceduralRender = false; - glm::vec4 outColor = _color; + glm::vec4 outColor; withReadLock([&] { + geometryShape = MAPPING[_shape]; + batch.setModelTransform(_modelTransform); // use a transform with scale, rotation, registration point and translation + outColor = _color; if (_procedural.isReady()) { _procedural.prepare(batch, _position, _dimensions, _orientation); - auto outColor = _procedural.getColor(_color); + outColor = _procedural.getColor(_color); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; proceduralRender = true; } @@ -149,13 +152,13 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } } else { // FIXME, support instanced multi-shape rendering using multidraw indirect - _color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; auto geometryCache = DependencyManager::get(); - auto pipeline = _color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); + auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { - geometryCache->renderWireShapeInstance(args, batch, geometryShape, _color, pipeline); + geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline); } else { - geometryCache->renderSolidShapeInstance(args, batch, geometryShape, _color, pipeline); + geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 0561fa5130..4688ef5d2b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -118,31 +118,30 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - // This work must be done on the main thread - if (!hasWebSurface()) { - buildWebSurface(entity); - } + withWriteLock([&] { + // This work must be done on the main thread + if (!hasWebSurface()) { + buildWebSurface(entity); + } - if (_contextPosition != entity->getPosition()) { - // update globalPosition - _contextPosition = entity->getPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } + if (_contextPosition != entity->getPosition()) { + // update globalPosition + _contextPosition = entity->getPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); + } - if (_lastSourceUrl != entity->getSourceUrl()) { - _lastSourceUrl = entity->getSourceUrl(); - loadSourceURL(); - } + if (_lastSourceUrl != entity->getSourceUrl()) { + _lastSourceUrl = entity->getSourceUrl(); + loadSourceURL(); + } - _lastDPI = entity->getDPI(); + _lastDPI = entity->getDPI(); - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); -} + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); -void WebEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - Parent::doRenderUpdateAsynchronousTyped(entity); - _modelTransform.postScale(entity->getDimensions()); + _modelTransform.postScale(entity->getDimensions()); + }); } void WebEntityRenderer::doRender(RenderArgs* args) { @@ -180,7 +179,9 @@ void WebEntityRenderer::doRender(RenderArgs* args) { static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(_modelTransform); + withReadLock([&] { + batch.setModelTransform(_modelTransform); + }); batch.setResourceTexture(0, _texture); float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio); @@ -190,7 +191,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { } bool WebEntityRenderer::hasWebSurface() { - return resultWithReadLock([&] { return (bool)_webSurface; }); + return (bool)_webSurface; } bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { @@ -213,11 +214,8 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }; { - QSharedPointer webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); - webSurface->create(); - withWriteLock([&] { - _webSurface = webSurface; - }); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface->create(); } // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces @@ -322,33 +320,31 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con } void WebEntityRenderer::loadSourceURL() { - withWriteLock([&] { - const QUrl sourceUrl(_lastSourceUrl); - if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || - _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { - _contentType = htmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); + const QUrl sourceUrl(_lastSourceUrl); + if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || + _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { + _contentType = htmlContent; + _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - - _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); - }); + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); } else { - _contentType = qmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); - _webSurface->load(_lastSourceUrl); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } + _webSurface->setMaxFps(DEFAULT_MAX_FPS); } - }); + + _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty("url", _lastSourceUrl); + }); + } else { + _contentType = qmlContent; + _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); + _webSurface->load(_lastSourceUrl); + if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); + } + } } void WebEntityRenderer::handlePointerEvent(const TypedEntityPointer& entity, const PointerEvent& event) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index a67eb39670..4b7e7e25a1 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -29,7 +29,6 @@ protected: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 837f485417..b6b85783eb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -267,6 +267,11 @@ void Model::updateRenderItems() { }); } +void Model::setRenderItemsNeedUpdate() { + _renderItemsNeedUpdate = true; + emit requestRenderUpdate(); +} + void Model::initJointTransforms() { if (isLoaded()) { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); @@ -814,13 +819,11 @@ void Model::setTextures(const QVariantMap& textures) { _needsUpdateTextures = true; _needsFixupInScene = true; _renderGeometry->setTextures(textures); - emit requestRenderUpdate(); } else { // FIXME(Huffman): Disconnect previously connected lambdas so we don't set textures multiple // after the geometry has finished loading. connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, [this, textures]() { _renderGeometry->setTextures(textures); - emit requestRenderUpdate(); }); } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 95dc171ff5..a742b46d6a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -103,7 +103,7 @@ public: bool isLayeredInFront() const { return _isLayeredInFront; } virtual void updateRenderItems(); - void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; emit requestRenderUpdate(); } + void setRenderItemsNeedUpdate(); bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } AABox getRenderableMeshBound() const; const render::ItemIDs& fetchRenderItemIDs() const; From 8e8209513502e97c83e8716c0c156300c00ad23b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 5 Oct 2017 12:11:31 -0700 Subject: [PATCH 439/504] Number sold and limited run --- .../resources/qml/hifi/commerce/purchases/PurchasedItem.qml | 4 ++++ interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index a026a818c0..1e26806b30 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -36,6 +36,8 @@ Item { property string itemHref; property int displayedItemCount; property int itemEdition; + property int numberSold; + property int limitedRun; property string originalStatusText; property string originalStatusColor; @@ -222,6 +224,8 @@ Item { "PENDING..." } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" + } else if (root.numberSold !== -1) { + ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "INFTY" : root.limitedRun)) } else { "" } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 1f7f2e6e53..990fd348c6 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -430,6 +430,8 @@ Rectangle { purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; + numberSold: model.number_sold; + limitedRun: model.limited_run; displayedItemCount: model.displayedItemCount; anchors.topMargin: 12; anchors.bottomMargin: 12; From 6b02cbb9112923e90fd92baa91304f1bbd3ebe93 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Oct 2017 13:17:57 -0700 Subject: [PATCH 440/504] move locking inside laserpointer and raypick --- interface/src/raypick/LaserPointer.cpp | 56 +++++++++++++++++++ interface/src/raypick/LaserPointer.h | 20 +++---- interface/src/raypick/LaserPointerManager.cpp | 16 ------ interface/src/raypick/RayPick.cpp | 44 +++++++++++++++ interface/src/raypick/RayPick.h | 18 +++--- interface/src/raypick/RayPickManager.cpp | 10 ---- 6 files changed, 119 insertions(+), 45 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 55ddd01123..0e0f13cd6c 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -49,11 +49,13 @@ LaserPointer::~LaserPointer() { } void LaserPointer::enable() { + QWriteLocker lock(getLock()); DependencyManager::get()->enableRayPick(_rayPickUID); _renderingEnabled = true; } void LaserPointer::disable() { + QWriteLocker lock(getLock()); DependencyManager::get()->disableRayPick(_rayPickUID); _renderingEnabled = false; if (!_currentRenderState.empty()) { @@ -67,6 +69,7 @@ void LaserPointer::disable() { } void LaserPointer::setRenderState(const std::string& state) { + QWriteLocker lock(getLock()); if (!_currentRenderState.empty() && state != _currentRenderState) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); @@ -79,6 +82,7 @@ void LaserPointer::setRenderState(const std::string& state) { } void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + QWriteLocker lock(getLock()); updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); @@ -92,6 +96,11 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& } } +const RayPickResult LaserPointer::getPrevRayPickResult() { + QReadLocker lock(getLock()); + return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); +} + void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState) { if (!renderState.getStartID().isNull()) { QVariantMap startProps; @@ -183,6 +192,8 @@ void LaserPointer::disableRenderState(const RenderState& renderState) { } void LaserPointer::update() { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + QReadLocker lock(getLock()); RayPickResult prevRayPickResult = DependencyManager::get()->getPrevRayPickResult(_rayPickUID); if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { @@ -198,6 +209,51 @@ void LaserPointer::update() { } } +void LaserPointer::setPrecisionPicking(const bool precisionPicking) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setPrecisionPicking(_rayPickUID, precisionPicking); +} + +void LaserPointer::setLaserLength(const float laserLength) { + QWriteLocker lock(getLock()); + _laserLength = laserLength; +} + +void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) { + QWriteLocker lock(getLock()); + _objectLockEnd = std::pair(objectID, isOverlay); +} + +void LaserPointer::setIgnoreEntities(const QScriptValue& ignoreEntities) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); +} + +void LaserPointer::setIncludeEntities(const QScriptValue& includeEntities) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); +} + +void LaserPointer::setIgnoreOverlays(const QScriptValue& ignoreOverlays) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); +} + +void LaserPointer::setIncludeOverlays(const QScriptValue& includeOverlays) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); +} + +void LaserPointer::setIgnoreAvatars(const QScriptValue& ignoreAvatars) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); +} + +void LaserPointer::setIncludeAvatars(const QScriptValue& includeAvatars) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); +} + RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : _startID(startID), _pathID(pathID), _endID(endID) { diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index fa7d396ae8..01dfe01cfd 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -58,22 +58,22 @@ public: QUuid getRayUID() { return _rayPickUID; } void enable(); void disable(); - const RayPickResult getPrevRayPickResult() { return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); } + const RayPickResult getPrevRayPickResult(); void setRenderState(const std::string& state); // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); - void setPrecisionPicking(const bool precisionPicking) { DependencyManager::get()->setPrecisionPicking(_rayPickUID, precisionPicking); } - void setLaserLength(const float laserLength) { _laserLength = laserLength; } - void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); } - void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); } - void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); } - void setIncludeOverlays(const QScriptValue& includeOverlays) { DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); } - void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); } - void setIncludeAvatars(const QScriptValue& includeAvatars) { DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); } + void setPrecisionPicking(const bool precisionPicking); + void setLaserLength(const float laserLength); + void setLockEndUUID(QUuid objectID, const bool isOverlay); - void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = std::pair(objectID, isOverlay); } + void setIgnoreEntities(const QScriptValue& ignoreEntities); + void setIncludeEntities(const QScriptValue& includeEntities); + void setIgnoreOverlays(const QScriptValue& ignoreOverlays); + void setIncludeOverlays(const QScriptValue& includeOverlays); + void setIgnoreAvatars(const QScriptValue& ignoreAvatars); + void setIncludeAvatars(const QScriptValue& includeAvatars); QReadWriteLock* getLock() { return &_lock; } diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp index 387f88724e..8615a96c3f 100644 --- a/interface/src/raypick/LaserPointerManager.cpp +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -31,7 +31,6 @@ void LaserPointerManager::enableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->enable(); } } @@ -40,7 +39,6 @@ void LaserPointerManager::disableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->disable(); } } @@ -49,7 +47,6 @@ void LaserPointerManager::setRenderState(QUuid uid, const std::string& renderSta QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setRenderState(renderState); } } @@ -58,7 +55,6 @@ void LaserPointerManager::editRenderState(QUuid uid, const std::string& state, c QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->editRenderState(state, startProps, pathProps, endProps); } } @@ -67,7 +63,6 @@ const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QReadLocker laserLock(laserPointer.value()->getLock()); return laserPointer.value()->getPrevRayPickResult(); } return RayPickResult(); @@ -76,9 +71,7 @@ const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { void LaserPointerManager::update() { QReadLocker lock(&_containsLock); for (QUuid& uid : _laserPointers.keys()) { - // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts auto laserPointer = _laserPointers.find(uid); - QReadLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->update(); } } @@ -87,7 +80,6 @@ void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPic QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setPrecisionPicking(precisionPicking); } } @@ -96,7 +88,6 @@ void LaserPointerManager::setLaserLength(QUuid uid, const float laserLength) { QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setLaserLength(laserLength); } } @@ -105,7 +96,6 @@ void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignor QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIgnoreEntities(ignoreEntities); } } @@ -114,7 +104,6 @@ void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& incl QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIncludeEntities(includeEntities); } } @@ -123,7 +112,6 @@ void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignor QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIgnoreOverlays(ignoreOverlays); } } @@ -132,7 +120,6 @@ void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& incl QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIncludeOverlays(includeOverlays); } } @@ -141,7 +128,6 @@ void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignore QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIgnoreAvatars(ignoreAvatars); } } @@ -150,7 +136,6 @@ void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& inclu QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setIncludeAvatars(includeAvatars); } } @@ -159,7 +144,6 @@ void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool i QReadLocker lock(&_containsLock); auto laserPointer = _laserPointers.find(uid); if (laserPointer != _laserPointers.end()) { - QWriteLocker laserLock(laserPointer.value()->getLock()); laserPointer.value()->setLockEndUUID(objectID, isOverlay); } } diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 70170a8f85..a5b1299210 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -16,3 +16,47 @@ RayPick::RayPick(const RayPickFilter& filter, const float maxDistance, const boo _enabled(enabled) { } +void RayPick::enable() { + QWriteLocker lock(getLock()); + _enabled = true; +} + +void RayPick::disable() { + QWriteLocker lock(getLock()); + _enabled = false; +} + +const RayPickResult& RayPick::getPrevRayPickResult() { + QReadLocker lock(getLock()); + return _prevResult; +} + +void RayPick::setIgnoreEntities(const QScriptValue& ignoreEntities) { + QWriteLocker lock(getLock()); + _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); +} + +void RayPick::setIncludeEntities(const QScriptValue& includeEntities) { + QWriteLocker lock(getLock()); + _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); +} + +void RayPick::setIgnoreOverlays(const QScriptValue& ignoreOverlays) { + QWriteLocker lock(getLock()); + _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); +} + +void RayPick::setIncludeOverlays(const QScriptValue& includeOverlays) { + QWriteLocker lock(getLock()); + _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); +} + +void RayPick::setIgnoreAvatars(const QScriptValue& ignoreAvatars) { + QWriteLocker lock(getLock()); + _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); +} + +void RayPick::setIncludeAvatars(const QScriptValue& includeAvatars) { + QWriteLocker lock(getLock()); + _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); +} \ No newline at end of file diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 428e44d670..6dacc084b4 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -103,13 +103,13 @@ public: virtual const PickRay getPickRay(bool& valid) const = 0; - void enable() { _enabled = true; } - void disable() { _enabled = false; } + void enable(); + void disable(); const RayPickFilter& getFilter() { return _filter; } float getMaxDistance() { return _maxDistance; } bool isEnabled() { return _enabled; } - const RayPickResult& getPrevRayPickResult() { return _prevResult; } + const RayPickResult& getPrevRayPickResult(); void setPrecisionPicking(bool precisionPicking) { _filter.setFlag(RayPickFilter::PICK_COURSE, !precisionPicking); } @@ -121,12 +121,12 @@ public: const QVector& getIncludeOverlays() { return _includeOverlays; } const QVector& getIgnoreAvatars() { return _ignoreAvatars; } const QVector& getIncludeAvatars() { return _includeAvatars; } - void setIgnoreEntities(const QScriptValue& ignoreEntities) { _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); } - void setIncludeEntities(const QScriptValue& includeEntities) { _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); } - void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); } - void setIncludeOverlays(const QScriptValue& includeOverlays) { _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); } - void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); } - void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); } + void setIgnoreEntities(const QScriptValue& ignoreEntities); + void setIncludeEntities(const QScriptValue& includeEntities); + void setIgnoreOverlays(const QScriptValue& ignoreOverlays); + void setIncludeOverlays(const QScriptValue& includeOverlays); + void setIgnoreAvatars(const QScriptValue& ignoreAvatars); + void setIncludeAvatars(const QScriptValue& includeAvatars); QReadWriteLock* getLock() { return &_lock; } diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp index 65f82dcd5f..1728ecd01a 100644 --- a/interface/src/raypick/RayPickManager.cpp +++ b/interface/src/raypick/RayPickManager.cpp @@ -153,7 +153,6 @@ void RayPickManager::enableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker rayPickLock(rayPick.value()->getLock()); rayPick.value()->enable(); } } @@ -162,7 +161,6 @@ void RayPickManager::disableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker rayPickLock(rayPick.value()->getLock()); rayPick.value()->disable(); } } @@ -171,7 +169,6 @@ const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QReadLocker lock(rayPick.value()->getLock()); return rayPick.value()->getPrevRayPickResult(); } return RayPickResult(); @@ -181,7 +178,6 @@ void RayPickManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setPrecisionPicking(precisionPicking); } } @@ -190,7 +186,6 @@ void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEnti QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIgnoreEntities(ignoreEntities); } } @@ -199,7 +194,6 @@ void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEn QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIncludeEntities(includeEntities); } } @@ -208,7 +202,6 @@ void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOver QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIgnoreOverlays(ignoreOverlays); } } @@ -217,7 +210,6 @@ void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOv QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIncludeOverlays(includeOverlays); } } @@ -226,7 +218,6 @@ void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvata QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIgnoreAvatars(ignoreAvatars); } } @@ -235,7 +226,6 @@ void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAva QReadLocker containsLock(&_containsLock); auto rayPick = _rayPicks.find(uid); if (rayPick != _rayPicks.end()) { - QWriteLocker lock(rayPick.value()->getLock()); rayPick.value()->setIncludeAvatars(includeAvatars); } } \ No newline at end of file From 9290a516851f7e70a437468cc313752baa65e504 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 5 Oct 2017 14:03:56 -0700 Subject: [PATCH 441/504] Fix confirmed text --- .../resources/qml/hifi/commerce/purchases/PurchasedItem.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 1e26806b30..5eb5516519 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -52,7 +52,6 @@ Item { statusText.text = "CONFIRMED!"; statusText.color = hifi.colors.blueAccent; confirmedTimer.start(); - root.purchaseStatusChanged = false; } } @@ -62,6 +61,7 @@ Item { onTriggered: { statusText.text = root.originalStatusText; statusText.color = root.originalStatusColor; + root.purchaseStatusChanged = false; } } @@ -205,7 +205,7 @@ Item { Item { id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated"; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; anchors.left: itemName.left; anchors.top: certificateContainer.bottom; anchors.topMargin: 8; From 21d286b4220111c0ff07f4932acfa1bf9d1c1b4b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 10:04:14 +1300 Subject: [PATCH 442/504] Fix undo not coping with cloning with both hands simultaneously --- scripts/shapes/modules/createPalette.js | 1 + scripts/shapes/modules/history.js | 24 ++++++++++----------- scripts/shapes/modules/selection.js | 28 +++++++++---------------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/scripts/shapes/modules/createPalette.js b/scripts/shapes/modules/createPalette.js index 694705cb2f..0eea8379d6 100644 --- a/scripts/shapes/modules/createPalette.js +++ b/scripts/shapes/modules/createPalette.js @@ -405,6 +405,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { entityID = Entities.addEntity(properties); if (entityID !== Uuid.NULL) { History.prePush( + otherSide, { deleteEntities: [{ entityID: entityID }] }, { createEntities: [{ entityID: entityID, properties: properties }] } ); diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 7065a3c52b..4b84a5699d 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -47,8 +47,8 @@ History = (function () { ], MAX_HISTORY_ITEMS = 1000, undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. - undoData = {}, - redoData = {}; + undoData = [{}, {}], + redoData = [{}, {}]; function doKick(entityID) { var properties, @@ -73,16 +73,16 @@ History = (function () { }, KICK_DELAY); } - function prePush(undo, redo) { - // Stores undo and redo data to include in the next history entry. - undoData = undo; - redoData = redo; + function prePush(side, undo, redo) { + // Stores undo and redo data to include in the next history entry generated for the side. + undoData[side] = undo; + redoData[side] = redo; } - function push(undo, redo) { + function push(side, undo, redo) { // Add a history entry. - undoData = Object.merge(undoData, undo); - redoData = Object.merge(redoData, redo); + undoData[side] = Object.merge(undoData[side], undo); + redoData[side] = Object.merge(redoData[side], redo); // Wipe any redo history after current undo position. if (undoPosition < history.length - 1) { @@ -95,11 +95,11 @@ History = (function () { undoPosition = history.length - 1; } - history.push({ undoData: undoData, redoData: redoData }); + history.push({ undoData: undoData[side], redoData: redoData[side] }); undoPosition++; - undoData = {}; - redoData = {}; + undoData[side] = {}; + redoData[side] = {}; } function updateEntityIDs(oldEntityID, newEntityID) { diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index afa0da5fd2..5720e0bee0 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -282,6 +282,7 @@ SelectionManager = function (side) { && (!Vec3.equal(startPosition, rootPosition) || !Quat.equal(startOrientation, rootOrientation))) { // Positions and orientations can be identical if change grabbing hands when finish scaling. History.push( + side, { setProperties: [ { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } @@ -331,6 +332,7 @@ SelectionManager = function (side) { if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { History.push( + side, { setProperties: [ { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } @@ -426,10 +428,7 @@ SelectionManager = function (side) { } // Add history entry. - History.push( - { setProperties: undoData }, - { setProperties: redoData } - ); + History.push(side, { setProperties: undoData }, { setProperties: redoData }); // Update grab start data for its undo. startPosition = rootPosition; @@ -445,6 +444,7 @@ SelectionManager = function (side) { if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { History.push( + side, { setProperties: [ { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } @@ -542,10 +542,7 @@ SelectionManager = function (side) { } // Add history entry. - History.push( - { setProperties: undoData }, - { setProperties: redoData } - ); + History.push(side, { setProperties: undoData }, { setProperties: redoData }); // Update grab start data for its undo. startPosition = rootPosition; @@ -593,10 +590,7 @@ SelectionManager = function (side) { rootEntityID = selection[0].id; // Add history entry. - History.prePush( - { deleteEntities: undoData }, - { createEntities: redoData } - ); + History.prePush(side, { deleteEntities: undoData }, { createEntities: redoData }); } function applyColor(color, isApplyToAll) { @@ -621,7 +615,7 @@ SelectionManager = function (side) { } } if (undoData.length > 0) { - History.push({ setProperties: undoData }, { setProperties: redoData }); + History.push(side, { setProperties: undoData }, { setProperties: redoData }); } } else { properties = Entities.getEntityProperties(intersectedEntityID, ["type", "color"]); @@ -630,6 +624,7 @@ SelectionManager = function (side) { color: color }); History.push( + side, { setProperties: [{ entityID: intersectedEntityID, properties: { color: properties.color } }] }, { setProperties: [{ entityID: intersectedEntityID, properties: { color: color } }] } ); @@ -740,10 +735,7 @@ SelectionManager = function (side) { }); // Add history entry. - History.push( - { setProperties: undoData }, - { setProperties: redoData } - ); + History.push(side, { setProperties: undoData }, { setProperties: redoData }); // Kick off physics if necessary. if (physicsProperties.dynamic) { @@ -759,7 +751,7 @@ SelectionManager = function (side) { function deleteEntities() { if (rootEntityID) { - History.push({ createEntities: selectionProperties }, { deleteEntities: [{ entityID: rootEntityID }] }); + History.push(side, { createEntities: selectionProperties }, { deleteEntities: [{ entityID: rootEntityID }] }); Entities.deleteEntity(rootEntityID); // Children are automatically deleted. clear(); } From 0b600a74c3b6990e4caf90bb9c9adbfccc582163 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 5 Oct 2017 14:34:53 -0700 Subject: [PATCH 443/504] Fix passphrase input focus problem; fix getting inventory and balance after logging in on Checkout --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 4 ++++ .../qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml | 2 +- .../resources/qml/hifi/commerce/wallet/PassphraseModal.qml | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 32f324aea9..09c2f6fa76 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -892,6 +892,10 @@ Rectangle { } else { root.activeView = "checkoutSuccess"; } + root.balanceReceived = false; + root.purchasesReceived = false; + commerce.inventory(); + commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 420b51ba15..cc316a70e9 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -39,7 +39,7 @@ Item { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 3) { commerce.getSecurityImage(); - } else { + } else if (walletStatus > 3) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 5bd88ba790..8d5d9f97de 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -197,6 +197,8 @@ Item { height: 50; echoMode: TextInput.Password; placeholderText: "passphrase"; + activeFocusOnPress: true; + activeFocusOnTab: true; onFocusChanged: { root.keyboardRaised = focus; @@ -206,8 +208,8 @@ Item { anchors.fill: parent; onClicked: { - parent.focus = true; root.keyboardRaised = true; + mouse.accepted = false; } } From 1673c0835349d64b6657517140144493a45c445a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 10:42:23 +1300 Subject: [PATCH 444/504] Update undo for grouping --- scripts/shapes/modules/groups.js | 10 ++-------- scripts/shapes/modules/history.js | 8 ++++++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/shapes/modules/groups.js b/scripts/shapes/modules/groups.js index 223992d646..3153a622ee 100644 --- a/scripts/shapes/modules/groups.js +++ b/scripts/shapes/modules/groups.js @@ -143,10 +143,7 @@ Groups = function () { selections.splice(1, selections.length - 1); // Add history entry. - History.push( - { setProperties: undoData }, - { setProperties: redoData } - ); + History.push(null, { setProperties: undoData }, { setProperties: redoData }); } function ungroup() { @@ -248,10 +245,7 @@ Groups = function () { } // Add history entry. - History.push( - { setProperties: undoData }, - { setProperties: redoData } - ); + History.push(null, { setProperties: undoData }, { setProperties: redoData }); } function clear() { diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 4b84a5699d..3820feaf81 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -47,8 +47,9 @@ History = (function () { ], MAX_HISTORY_ITEMS = 1000, undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. - undoData = [{}, {}], - redoData = [{}, {}]; + undoData = [{}, {}, {}], // Left side, right side, no side. + redoData = [{}, {}, {}], + NO_SIDE = 2; function doKick(entityID) { var properties, @@ -81,6 +82,9 @@ History = (function () { function push(side, undo, redo) { // Add a history entry. + if (side === null) { + side = NO_SIDE; + } undoData[side] = Object.merge(undoData[side], undo); redoData[side] = Object.merge(redoData[side], redo); From ba50fcc5097835c443f9e753e9888d1ab3aa6069 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Thu, 5 Oct 2017 15:07:49 -0700 Subject: [PATCH 445/504] fix mouse dissapearing in desktop mode --- scripts/system/controllers/controllerModules/mouseHMD.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index 9ccf4912a1..ed0bc03223 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -115,7 +115,12 @@ this.run = function(controllerData, deltaTime) { var now = Date.now(); if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now)) { - Reticle.visible = false; + if (!HMD.active) { + Reticle.visible = true; + } else { + Reticle.visible = false; + } + return ControllerDispatcherUtils.makeRunningValues(false, [], []); } this.adjustReticleDepth(controllerData); From 3139f5ef2a2b29c4b6e4328b59bf7679256bec54 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Thu, 5 Oct 2017 15:12:58 -0700 Subject: [PATCH 446/504] improve mouseHMD exit logic --- scripts/system/controllers/controllerModules/mouseHMD.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index ed0bc03223..1d8aeee1f9 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -114,8 +114,9 @@ this.run = function(controllerData, deltaTime) { var now = Date.now(); - if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now)) { - if (!HMD.active) { + var hmdActive = HMD.active; + if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now) || !hmdActive) { + if (!hmdActive) { Reticle.visible = true; } else { Reticle.visible = false; From 5587a2fdf842f0ab29fe42b78ad02f6fd27df7c5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 11:13:53 +1300 Subject: [PATCH 447/504] Fix creating an entity while a tool is active --- scripts/shapes/shapes.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index 465ce6258f..2fdf56900b 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -979,7 +979,9 @@ && (!wasTriggerClicked || isAutoGrab) && isTriggerClicked) { intersectedEntityID = intersection.entityID; rootEntityID = Entities.rootOf(intersectedEntityID); - if (otherEditor.isEditing(rootEntityID)) { + if (isAutoGrab) { + setState(EDITOR_GRABBING); + } else if (otherEditor.isEditing(rootEntityID)) { if (toolSelected !== TOOL_SCALE) { setState(EDITOR_DIRECT_SCALING); } @@ -1013,10 +1015,10 @@ selection.deleteEntities(); setState(EDITOR_SEARCHING); } else { - setState(EDITOR_GRABBING); + log(side, "ERROR: Editor: Unexpected condition A in EDITOR_SEARCHING!"); } } else { - log(side, "ERROR: Editor: Unexpected condition in EDITOR_SEARCHING!"); + log(side, "ERROR: Editor: Unexpected condition B in EDITOR_SEARCHING!"); } break; case EDITOR_HIGHLIGHTING: From 5c72a411638b85f8c72aa50992d004e9f640a7bc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 11:14:07 +1300 Subject: [PATCH 448/504] Delete some redundant code --- scripts/shapes/shapes.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index 2fdf56900b..4686a48470 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -1043,10 +1043,6 @@ wasScaleTool = toolSelected === TOOL_SCALE; doUpdateState = true; } - if (toolSelected === TOOL_COLOR && intersection.entityID !== intersectedEntityID) { - intersectedEntityID = intersection.entityID; - doUpdateState = true; - } if ((toolSelected === TOOL_COLOR || toolSelected === TOOL_PHYSICS) && intersection.entityID !== intersectedEntityID) { intersectedEntityID = intersection.entityID; From 601b30f0628aaa2466e59984ecd91ec3c875d654 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Oct 2017 15:31:45 -0700 Subject: [PATCH 449/504] fix flash when switching display modes --- .../display-plugins/OpenGLDisplayPlugin.cpp | 22 ++++++++++--------- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 93e648f441..13abcbadc6 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -564,17 +564,19 @@ void OpenGLDisplayPlugin::updateFrameData() { std::function OpenGLDisplayPlugin::getHUDOperator() { return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { - batch.enableStereo(false); - batch.setPipeline(_hudPipeline); - batch.setResourceTexture(0, hudTexture); - if (isStereo()) { - for_each_eye([&](Eye eye) { - batch.setViewportTransform(eyeViewport(eye, getRecommendedRenderSize())); + if (_hudPipeline) { + batch.enableStereo(false); + batch.setPipeline(_hudPipeline); + batch.setResourceTexture(0, hudTexture); + if (isStereo()) { + for_each_eye([&](Eye eye) { + batch.setViewportTransform(eyeViewport(eye, getRecommendedRenderSize())); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } else { + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - } else { - batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); - batch.draw(gpu::TRIANGLE_STRIP, 4); + } } }; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index e5e1295786..053f3171be 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -431,8 +431,8 @@ std::function HmdDisplayPlugin::H updatePipeline(); return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { if (pipeline) { - batch.setPipeline(pipeline); + batch.setInputFormat(format); gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); From 525e0d8f61418a567e6f15d0f602f67e8be78279 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 5 Oct 2017 16:51:13 -0700 Subject: [PATCH 450/504] Make SettingsScriptingInterface accessible to tablet --- interface/src/ui/overlays/Web3DOverlay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 811e169faf..526890b9c1 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -40,6 +40,7 @@ #include "scripting/HMDScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "scripting/SettingsScriptingInterface.h" #include #include #include "FileDialogHelper.h" @@ -243,6 +244,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); From 105457b388b2c065733aa21a52b9fe62e440c69a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 13:27:08 +1300 Subject: [PATCH 451/504] Don't unbusubscribe from possibly shared channel --- scripts/shapes/shapes.js | 4 ++-- scripts/system/edit.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/shapes/shapes.js b/scripts/shapes/shapes.js index 4686a48470..290424f55e 100644 --- a/scripts/shapes/shapes.js +++ b/scripts/shapes/shapes.js @@ -1988,8 +1988,8 @@ Entities.canRezChanged.disconnect(onCanRezChanged); Entities.canRezTmpChanged.disconnect(onCanRezChanged); Messages.messageReceived.disconnect(onMessageReceived); - // Messages.unsubscribe(DOMAIN_CHANGED_MESSAGE); Do NOT unsubscribe because edit.js also subscribes and - // Messages.subscribe works client-wide. + // Messages.unsubscribe(DOMAIN_CHANGED_MESSAGE); Do not unsubscribe because edit.js also subscribes and + // Messages.subscribe works script engine-wide which would mess things up if they're both run in the same engine. MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.skeletonChanged.disconnect(onSkeletonChanged); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 9f15abaa1f..d6d4de2a4b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1234,7 +1234,8 @@ Script.scriptEnding.connect(function () { Messages.messageReceived.disconnect(handleMessagesReceived); Messages.unsubscribe("entityToolUpdates"); - Messages.unsubscribe("Toolbar-DomainChanged"); + // Messages.unsubscribe("Toolbar-DomainChanged"); // Do not unsubscribe because the shapes.js app also subscribes and + // Messages.subscribe works script engine-wide which would mess things up if they're both run in the same engine. createButton = null; }); From 0c33f46ab1f48eb518c05d6c794356736056607d Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Thu, 5 Oct 2017 21:42:16 -0400 Subject: [PATCH 452/504] Update SetupQt.cmake This change allows -DQT_CMAKE_PREFIX_PATH to function again. Previously, set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") always ran, which overrode QT_CMAKE_PREFIX_PATH every time on cmake. This proposed change now only uses the environment variable if DQT_CMAKE_PREFIX_PATH is not specified. --- cmake/macros/SetupQt.cmake | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index ece8607b9b..bbdce06f37 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -44,7 +44,11 @@ endfunction() # Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables # Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC macro(setup_qt) - set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + # if QT_CMAKE_PREFIX_PATH was not specified before hand, + # try to use the environment variable + if (NOT QT_CMAKE_PREFIX_PATH) + set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + endif() if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}")) calculate_default_qt_dir(QT_DIR) set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake") @@ -81,4 +85,4 @@ macro(setup_qt) add_paths_to_fixup_libs("${QT_DIR}/bin") endif () -endmacro() \ No newline at end of file +endmacro() From 707569b23020391c43697d6d51999b8b02671f7c Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 6 Oct 2017 09:50:24 -0400 Subject: [PATCH 453/504] [Case 6491] More efficient version of normalizeDegrees function (details below). Refactoring the original normalizeDegrees function to be more efficient as suggested via: https://github.com/highfidelity/hifi/pull/11338#pullrequestreview-67520585 Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 68cdf91dc1..b6b4b56d5a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -238,11 +238,8 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { - while (degrees > 180) { - degrees -= 360; - } - - while (degrees <= -180) { + degrees = ((degrees + 180) % 360) - 180; + if (degrees < -180) { degrees += 360; } From 2f082d9e86684a791e928750a21863084af47fdf Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 6 Oct 2017 10:11:26 -0400 Subject: [PATCH 454/504] [Case 6491] Fixes issue in normalizeDegrees from last commit (details below). The range of the function is targeting (-180, 180] such that the statement: if (degrees < -180) should be: if (degrees <= -180) to account for degrees being -180. Changes Committed: modified: scripts/system/libraries/entitySelectionTool.js --- scripts/system/libraries/entitySelectionTool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b6b4b56d5a..d36b3f28ee 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -239,7 +239,7 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { degrees = ((degrees + 180) % 360) - 180; - if (degrees < -180) { + if (degrees <= -180) { degrees += 360; } From 24974fdae3f3797b625c3d19a425ab1280e73057 Mon Sep 17 00:00:00 2001 From: Nex-Pro <7314019+Nex-Pro@users.noreply.github.com> Date: Fri, 6 Oct 2017 18:42:02 +0100 Subject: [PATCH 455/504] Fixed typo in qWarning message. --- interface/src/assets/ATPAssetMigrator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 8de40865b7..45ac80b054 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -78,7 +78,7 @@ void ATPAssetMigrator::loadEntityServerFile() { request->send(); } else { ++_errorCount; - qWarning() << "Count not create request for asset at" << migrationURL.toString(); + qWarning() << "Could not create request for asset at" << migrationURL.toString(); } }; From f4dedf05bb45df2fc744cd763ab2e4dc7f064259 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 6 Oct 2017 11:02:26 -0700 Subject: [PATCH 456/504] move variables into block that needs them --- libraries/entities/src/EntityTree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ef1d27640c..e19d7a3a7f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1941,9 +1941,9 @@ bool EntityTree::readFromMap(QVariantMap& map) { entityItemID = EntityItemID(QUuid::createUuid()); } - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); if (properties.getClientOnly()) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); properties.setOwningAvatarID(myNodeID); } From 9064114ce5336676963f3fdeda690ce8a683f3c7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 6 Oct 2017 11:03:48 -0700 Subject: [PATCH 457/504] fix mistaken logic-flip in recent PR --- libraries/entities/src/ModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 14813a68fe..9c3ce47886 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -497,7 +497,7 @@ bool ModelEntityItem::hasModel() const { }); } bool ModelEntityItem::hasCompoundShapeURL() const { - return _compoundShapeURL.get().isEmpty(); + return !_compoundShapeURL.get().isEmpty(); } QString ModelEntityItem::getModelURL() const { From e95ecc3f272a176737b4c42695e66d65546d0685 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 6 Oct 2017 15:30:34 -0700 Subject: [PATCH 458/504] fix missing sensorToWorld matrix --- interface/src/Application.cpp | 1 + interface/src/ui/OverlayConductor.cpp | 1 - interface/src/ui/overlays/Base3DOverlay.cpp | 8 +++----- .../src/display-plugins/OpenGLDisplayPlugin.cpp | 13 ++----------- .../src/display-plugins/OpenGLDisplayPlugin.h | 1 - .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 1 - 6 files changed, 6 insertions(+), 19 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6e93e67d5a..2079705bcf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5053,6 +5053,7 @@ void Application::update(float deltaTime) { float sensorToWorldScale = getMyAvatar()->getSensorToWorldScale(); appRenderArgs._sensorToWorldScale = sensorToWorldScale; + appRenderArgs._sensorToWorld = getMyAvatar()->getSensorToWorldMatrix(); { PROFILE_RANGE(render, "/buildFrustrumAndArgs"); { diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 71aff38032..dbf58c5cbc 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -96,7 +96,6 @@ bool OverlayConductor::updateAvatarHasDriveInput() { void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); - qDebug() << "OverlayConductor::centerUI" << camMat; qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 6625ae54f7..a34240483b 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -277,10 +277,10 @@ void Base3DOverlay::update(float duration) { // then the correct transform used for rendering is computed in the update transaction and assigned. if (_renderTransformDirty) { auto itemID = getRenderItemID(); - // Capture the render transform value in game loop before - auto latestTransform = evalRenderTransform(); - _renderTransformDirty = false; if (render::Item::isValidID(itemID)) { + // Capture the render transform value in game loop before + auto latestTransform = evalRenderTransform(); + _renderTransformDirty = false; render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; transaction.updateItem(itemID, [latestTransform](Overlay& data) { @@ -290,8 +290,6 @@ void Base3DOverlay::update(float duration) { } }); scene->enqueueTransaction(transaction); - } else { - setRenderTransform(latestTransform); } } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 13abcbadc6..88aa56cccf 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -570,11 +570,11 @@ std::function OpenGLDisplayPlugin batch.setResourceTexture(0, hudTexture); if (isStereo()) { for_each_eye([&](Eye eye) { - batch.setViewportTransform(eyeViewport(eye, getRecommendedRenderSize())); + batch.setViewportTransform(eyeViewport(eye)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } } @@ -840,15 +840,6 @@ ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const { return ivec4(vpPos, vpSize); } -ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye, uvec2 vpSize) const { - vpSize.x /= 2; - uvec2 vpPos; - if (eye == Eye::Right) { - vpPos.x = vpSize.x; - } - return ivec4(vpPos, vpSize); -} - gpu::gl::GLBackend* OpenGLDisplayPlugin::getGLBackend() { if (!_gpuContext || !_gpuContext->getBackend()) { return nullptr; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 4bb82f3651..ec775aed4c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -125,7 +125,6 @@ protected: void present(); virtual void swapBuffers(); ivec4 eyeViewport(Eye eye) const; - ivec4 eyeViewport(Eye eye, uvec2 vpSize) const; void render(std::function f); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index fde9b26779..053f3171be 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -444,7 +444,6 @@ std::function HmdDisplayPlugin::H batch.setUniformBuffer(uniformsLocation, uniformsBuffer); auto compositorHelper = DependencyManager::get(); - qDebug() << "getUITransform" << compositorHelper->getUiTransform(); batch.setModelTransform(compositorHelper->getUiTransform()); batch.setResourceTexture(0, hudTexture); From b4a3ab920450c36949cde73a54d377e307251d39 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 6 Oct 2017 15:46:10 -0700 Subject: [PATCH 459/504] fix debugDeferredLighting --- .../render-utils/src/RenderDeferredTask.cpp | 4 ++-- .../utilities/render/deferredLighting.qml | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index a0bef828fa..45e6fd4ba4 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -186,7 +186,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZones", zones); } - // Overlays + // Layered Overlays const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); @@ -251,7 +251,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); - task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDTransparent); + task.addJob("DrawOverlayHUDTransparentBounds", overlaysHUDTransparent); } task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 2254b6d95f..60929d75c0 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -178,15 +178,26 @@ Column { onCheckedChanged: { mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked } } CheckBox { - text: "Overlay Opaques" - checked: mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"] = checked } + text: "Opaques in Front" + checked: mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] + onCheckedChanged: { mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] = checked } } CheckBox { - text: "Overlay Transparents" - checked: mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked } + text: "Transparents in Front" + checked: mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] + onCheckedChanged: { mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] = checked } } + CheckBox { + text: "Opaques in HUD" + checked: mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] + onCheckedChanged: { mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked } + } + CheckBox { + text: "Transparents in HUD" + checked: mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] + onCheckedChanged: { mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } + } + } Column { CheckBox { From 3fbaf250a4bd8030f1441e36ddcab5408700350e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 7 Oct 2017 11:50:31 +1300 Subject: [PATCH 460/504] Increase delay in kicking off physics --- scripts/shapes/modules/history.js | 2 +- scripts/shapes/modules/selection.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shapes/modules/history.js b/scripts/shapes/modules/history.js index 3820feaf81..9f2c45046a 100644 --- a/scripts/shapes/modules/history.js +++ b/scripts/shapes/modules/history.js @@ -66,7 +66,7 @@ History = (function () { function kickPhysics(entityID) { // Gives entities a small kick to start off physics, if necessary. - var KICK_DELAY = 500; // ms + var KICK_DELAY = 750; // ms // Give physics a chance to catch up. Avoids some erratic behavior. Script.setTimeout(function () { diff --git a/scripts/shapes/modules/selection.js b/scripts/shapes/modules/selection.js index 5720e0bee0..955cde6bda 100644 --- a/scripts/shapes/modules/selection.js +++ b/scripts/shapes/modules/selection.js @@ -234,7 +234,7 @@ SelectionManager = function (side) { function kickPhysics(entityID) { // Gives entities a small kick to start off physics, if necessary. - var KICK_DELAY = 500; // ms + var KICK_DELAY = 750; // ms // Give physics a chance to catch up. Avoids some erratic behavior. Script.setTimeout(function () { From 988d8a1a62811a48652ce1eaa137dba69e2f15c8 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 6 Oct 2017 18:17:48 -0700 Subject: [PATCH 461/504] TRying to debug the overlay notifications to show ?) --- interface/src/ui/overlays/Billboard3DOverlay.cpp | 8 ++++++++ interface/src/ui/overlays/Billboard3DOverlay.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/interface/src/ui/overlays/Billboard3DOverlay.cpp b/interface/src/ui/overlays/Billboard3DOverlay.cpp index 960f0de095..f19426b054 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.cpp +++ b/interface/src/ui/overlays/Billboard3DOverlay.cpp @@ -37,6 +37,14 @@ QVariant Billboard3DOverlay::getProperty(const QString &property) { return Planar3DOverlay::getProperty(property); } +void Billboard3DOverlay::update(float duration) { + // Billboard's transform needs to constantly be adjusted if it faces the avatar + // if (isFacingAvatar()) { + notifyRenderTransformChange(); + // } + Base3DOverlay::update(duration); +} + bool Billboard3DOverlay::applyTransformTo(Transform& transform, bool force) { bool transformChanged = false; if (force || usecTimestampNow() > _transformExpiry) { diff --git a/interface/src/ui/overlays/Billboard3DOverlay.h b/interface/src/ui/overlays/Billboard3DOverlay.h index 6b3aa40451..6f590dd6ec 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.h +++ b/interface/src/ui/overlays/Billboard3DOverlay.h @@ -26,6 +26,8 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + void update(float deltatime) override; + protected: virtual bool applyTransformTo(Transform& transform, bool force = false) override; From 596ce8e9c12c8a75db2a1d7092cf3f9c38ab3969 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 7 Oct 2017 21:46:56 +1300 Subject: [PATCH 462/504] Add script that tests setting your listening position and orientation --- scripts/developer/utilities/tools/testEars.js | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 scripts/developer/utilities/tools/testEars.js diff --git a/scripts/developer/utilities/tools/testEars.js b/scripts/developer/utilities/tools/testEars.js new file mode 100644 index 0000000000..600358eeb1 --- /dev/null +++ b/scripts/developer/utilities/tools/testEars.js @@ -0,0 +1,101 @@ +// +// testEars.js +// +// Positions and orients your listening position at a virtual head created in front of you. +// +// Created by David Rowe on 7 Oct 2017. +// Copyright 2017 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. +// + +(function () { + + "use strict"; + + var overlays = [ + { + // Head + type: "sphere", + properties: { + dimensions: { x: 0.2, y: 0.3, z: 0.25 }, + position: { x: 0, y: 0, z: -2 }, + rotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + color: { red: 128, green: 128, blue: 128 }, + alpha: 1.0, + solid: true + } + }, + { + // Left ear + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.10, z: 0.08 }, + localPosition: { x: -0.1, y: 0, z: 0.05 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + color: { red: 255, green: 0, blue: 0 }, // Red for "port". + alpha: 1.0, + solid: true + } + }, + { + // Right ear + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.10, z: 0.08 }, + localPosition: { x: 0.1, y: 0, z: 0.05 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + color: { red: 0, green: 255, blue: 0 }, // Green for "starboard". + alpha: 1.0, + solid: true + } + }, + { + // Nose + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.04, z: 0.04 }, + localPosition: { x: 0, y: 0, z: -0.125 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: -0.08, z: 0 }), + color: { red: 160, green: 160, blue: 160 }, + alpha: 1.0, + solid: true + } + } + ], + originalListenerMode; + + function setUp() { + var i, length; + + originalListenerMode = MyAvatar.audioListenerMode; + + for (i = 0, length = overlays.length; i < length; i++) { + if (i === 0) { + overlays[i].properties.position = Vec3.sum(MyAvatar.getHeadPosition(), + Vec3.multiplyQbyV(MyAvatar.orientation, overlays[i].properties.position)); + overlays[i].properties.rotation = Quat.multiply(MyAvatar.orientation, overlays[i].properties.rotation); + } else { + overlays[i].properties.parentID = overlays[0].id; + } + overlays[i].id = Overlays.addOverlay(overlays[i].type, overlays[i].properties); + } + + MyAvatar.audioListenerMode = MyAvatar.audioListenerModeCustom; + MyAvatar.customListenPosition = overlays[0].properties.position; + MyAvatar.customListenOrientation = overlays[0].properties.orientation; + } + + function tearDown() { + var i, length; + for (i = 0, length = overlays.length; i < length; i++) { + Overlays.deleteOverlay(overlays[i].id); + } + + MyAvatar.audioListenerMode = originalListenerMode; + } + + Script.setTimeout(setUp, 2000); // Delay so that overlays display if script runs at Interface start. + Script.scriptEnding.connect(tearDown); +}()); From 28a8b180605c205d913442414f8423d3a4b76d64 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Oct 2017 16:54:05 -0700 Subject: [PATCH 463/504] Revert "fix importing of avatar entities" --- interface/src/avatar/MyAvatar.cpp | 6 ------ interface/src/avatar/MyAvatar.h | 2 -- .../entities/src/EntityEditPacketSender.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 18 +++--------------- libraries/shared/src/SpatiallyNestable.cpp | 18 ++++-------------- .../libraries/controllerDispatcherUtils.js | 2 +- 6 files changed, 9 insertions(+), 39 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d82405aee..10e2202553 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3238,9 +3238,3 @@ void MyAvatar::setModelScale(float scale) { emit sensorToWorldScaleChanged(sensorToWorldScale); } } - -SpatialParentTree* MyAvatar::getParentTree() const { - auto entityTreeRenderer = qApp->getEntities(); - EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; - return entityTree.get(); -} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 952315a85f..9620d61a49 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -544,8 +544,6 @@ public: float getUserHeight() const; float getUserEyeHeight() const; - virtual SpatialParentTree* getParentTree() const override; - public slots: void increaseSize(); void decreaseSize(); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index f93b6a49ec..ee0fcf8218 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -45,7 +45,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, } EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); if (!entity) { - qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID; + qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; return; } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e19d7a3a7f..bf37a08386 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1842,13 +1842,7 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void QHash::iterator iter = args->map->find(oldID); if (iter == args->map->end()) { - EntityItemID newID; - if (oldID == AVATAR_SELF_ID) { - auto nodeList = DependencyManager::get(); - newID = EntityItemID(nodeList->getSessionUUID()); - } else { - newID = QUuid::createUuid(); - } + EntityItemID newID = QUuid::createUuid(); args->map->insert(oldID, newID); return newID; } @@ -1865,8 +1859,8 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void properties.setPosition(properties.getPosition() + args->root); } else { EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID); - if (parentEntity || oldParentID == AVATAR_SELF_ID) { // map the parent - properties.setParentID(getMapped(oldParentID)); + if (parentEntity) { // map the parent + properties.setParentID(getMapped(parentEntity->getID())); // But do not add root offset in this case. } else { // Should not happen, but let's try to be helpful... item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); @@ -1941,12 +1935,6 @@ bool EntityTree::readFromMap(QVariantMap& map) { entityItemID = EntityItemID(QUuid::createUuid()); } - if (properties.getClientOnly()) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - properties.setOwningAvatarID(myNodeID); - } - EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 8c43632456..8ea38f5f13 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -102,11 +102,8 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons if (parent && parent->getID() == parentID) { // parent pointer is up-to-date if (!_parentKnowsMe) { - SpatialParentTree* parentTree = parent->getParentTree(); - if (!parentTree || parentTree == getParentTree()) { - parent->beParentOfChild(getThisPointer()); - _parentKnowsMe = true; - } + parent->beParentOfChild(getThisPointer()); + _parentKnowsMe = true; } success = true; return parent; @@ -132,15 +129,8 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons parent = _parent.lock(); if (parent) { - - // it's possible for an entity with a parent of AVATAR_SELF_ID can be imported into a side-tree - // such as the clipboard's. if this is the case, we don't want the parent to consider this a - // child. - SpatialParentTree* parentTree = parent->getParentTree(); - if (!parentTree || parentTree == getParentTree()) { - parent->beParentOfChild(getThisPointer()); - _parentKnowsMe = true; - } + parent->beParentOfChild(getThisPointer()); + _parentKnowsMe = true; } success = (parent || parentID.isNull()); diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index d6d80541ee..e9e25b058b 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -222,7 +222,7 @@ getControllerJointIndex = function (hand) { return controllerJointIndex; } - return -1; + return MyAvatar.getJointIndex("Head"); }; propsArePhysical = function (props) { From 7aeeaeb4e7efecca8bd2c96cd1bb51b304213ec6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 7 Oct 2017 19:41:43 -0700 Subject: [PATCH 464/504] Fix rendering transform for text3d overlays --- interface/src/ui/overlays/Text3DOverlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 57e3c32060..2e55a9a471 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -88,6 +88,7 @@ void Text3DOverlay::update(float deltatime) { applyTransformTo(transform); setTransform(transform); } + Parent::update(deltatime); } void Text3DOverlay::render(RenderArgs* args) { From c45fe1af6c966cb541ae7b3bdd4073e02039b004 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 7 Oct 2017 19:41:43 -0700 Subject: [PATCH 465/504] Fix rendering transform for text3d overlays --- interface/src/ui/overlays/Text3DOverlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 57e3c32060..2e55a9a471 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -88,6 +88,7 @@ void Text3DOverlay::update(float deltatime) { applyTransformTo(transform); setTransform(transform); } + Parent::update(deltatime); } void Text3DOverlay::render(RenderArgs* args) { From 765f3fc38175a6ab53d0c1c50b0821da08f72dda Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 9 Oct 2017 10:38:40 +1300 Subject: [PATCH 466/504] Move Shapes app files from scripts directory to marketplace directory --- .../marketplace}/shapes/assets/audio/clone.wav | Bin .../marketplace}/shapes/assets/audio/create.wav | Bin .../marketplace}/shapes/assets/audio/delete.wav | Bin .../marketplace}/shapes/assets/audio/drop.wav | Bin .../marketplace}/shapes/assets/audio/equip.wav | Bin .../marketplace}/shapes/assets/audio/error.wav | Bin .../marketplace}/shapes/assets/audio/select.wav | Bin .../marketplace}/shapes/assets/blue-header-bar.fbx | Bin .../marketplace}/shapes/assets/create/circle.fbx | Bin .../marketplace}/shapes/assets/create/cone.fbx | Bin .../shapes/assets/create/create-heading.svg | 0 .../marketplace}/shapes/assets/create/cube.fbx | Bin .../marketplace}/shapes/assets/create/cylinder.fbx | Bin .../shapes/assets/create/dodecahedron.fbx | Bin .../marketplace}/shapes/assets/create/hexagon.fbx | Bin .../shapes/assets/create/icosahedron.fbx | Bin .../marketplace}/shapes/assets/create/octagon.fbx | Bin .../shapes/assets/create/octahedron.fbx | Bin .../marketplace}/shapes/assets/create/prism.fbx | Bin .../marketplace}/shapes/assets/create/sphere.fbx | Bin .../shapes/assets/create/tetrahedron.fbx | Bin .../marketplace}/shapes/assets/gray-header.fbx | Bin .../marketplace}/shapes/assets/green-header-bar.fbx | Bin .../marketplace}/shapes/assets/green-header.fbx | Bin .../marketplace}/shapes/assets/horizontal-rule.svg | 0 .../marketplace}/shapes/assets/shapes-a.svg | 0 .../marketplace}/shapes/assets/shapes-d.svg | 0 .../marketplace}/shapes/assets/shapes-i.svg | 0 .../shapes/assets/tools/back-heading.svg | 0 .../marketplace}/shapes/assets/tools/back-icon.svg | 0 .../marketplace}/shapes/assets/tools/clone-icon.svg | 0 .../shapes/assets/tools/clone-label.svg | 0 .../shapes/assets/tools/clone-tool-heading.svg | 0 .../marketplace}/shapes/assets/tools/color-icon.svg | 0 .../shapes/assets/tools/color-label.svg | 0 .../shapes/assets/tools/color-tool-heading.svg | 0 .../assets/tools/color/color-circle-black.png | Bin .../shapes/assets/tools/color/color-circle.png | Bin .../shapes/assets/tools/color/pick-color-label.svg | 0 .../shapes/assets/tools/color/slider-alpha.png | Bin .../shapes/assets/tools/color/slider-white.png | Bin .../shapes/assets/tools/color/swatches-label.svg | 0 .../shapes/assets/tools/common/actions-label.svg | 0 .../shapes/assets/tools/common/down-arrow.svg | 0 .../shapes/assets/tools/common/finish-label.svg | 0 .../shapes/assets/tools/common/info-icon.svg | 0 .../shapes/assets/tools/common/up-arrow.svg | 0 .../shapes/assets/tools/delete-icon.svg | 0 .../shapes/assets/tools/delete-label.svg | 0 .../shapes/assets/tools/delete-tool-heading.svg | 0 .../shapes/assets/tools/delete/info-text.svg | 0 .../marketplace}/shapes/assets/tools/group-icon.svg | 0 .../shapes/assets/tools/group-label.svg | 0 .../shapes/assets/tools/group-tool-heading.svg | 0 .../shapes/assets/tools/group/clear-label.svg | 0 .../shapes/assets/tools/group/group-label.svg | 0 .../assets/tools/group/selection-box-label.svg | 0 .../shapes/assets/tools/group/ungroup-label.svg | 0 .../shapes/assets/tools/physics-icon.svg | 0 .../shapes/assets/tools/physics-label.svg | 0 .../shapes/assets/tools/physics-tool-heading.svg | 0 .../tools/physics/buttons/collisions-label.svg | 0 .../tools/physics/buttons/grabbable-label.svg | 0 .../assets/tools/physics/buttons/gravity-label.svg | 0 .../assets/tools/physics/buttons/off-label.svg | 0 .../assets/tools/physics/buttons/on-label.svg | 0 .../shapes/assets/tools/physics/presets-label.svg | 0 .../assets/tools/physics/presets/balloon-label.svg | 0 .../assets/tools/physics/presets/cotton-label.svg | 0 .../assets/tools/physics/presets/custom-label.svg | 0 .../assets/tools/physics/presets/default-label.svg | 0 .../assets/tools/physics/presets/ice-label.svg | 0 .../assets/tools/physics/presets/lead-label.svg | 0 .../assets/tools/physics/presets/rubber-label.svg | 0 .../tools/physics/presets/tumbleweed-label.svg | 0 .../assets/tools/physics/presets/wood-label.svg | 0 .../assets/tools/physics/presets/zero-g-label.svg | 0 .../assets/tools/physics/properties-label.svg | 0 .../assets/tools/physics/sliders/bounce-label.svg | 0 .../assets/tools/physics/sliders/density-label.svg | 0 .../assets/tools/physics/sliders/friction-label.svg | 0 .../assets/tools/physics/sliders/gravity-label.svg | 0 .../marketplace}/shapes/assets/tools/redo-icon.svg | 0 .../marketplace}/shapes/assets/tools/redo-label.svg | 0 .../shapes/assets/tools/stretch-icon.svg | 0 .../shapes/assets/tools/stretch-label.svg | 0 .../shapes/assets/tools/stretch-tool-heading.svg | 0 .../shapes/assets/tools/stretch/info-text.svg | 0 .../marketplace}/shapes/assets/tools/tool-icon.fbx | Bin .../marketplace}/shapes/assets/tools/tool-label.svg | 0 .../shapes/assets/tools/tools-heading.svg | 0 .../marketplace}/shapes/assets/tools/undo-icon.svg | 0 .../marketplace}/shapes/assets/tools/undo-label.svg | 0 .../marketplace}/shapes/modules/createPalette.js | 0 .../marketplace}/shapes/modules/feedback.js | 0 .../marketplace}/shapes/modules/groups.js | 0 .../marketplace}/shapes/modules/hand.js | 0 .../marketplace}/shapes/modules/handles.js | 0 .../marketplace}/shapes/modules/highlights.js | 0 .../marketplace}/shapes/modules/history.js | 0 .../marketplace}/shapes/modules/laser.js | 0 .../marketplace}/shapes/modules/selection.js | 0 .../marketplace}/shapes/modules/toolIcon.js | 0 .../marketplace}/shapes/modules/toolsMenu.js | 0 .../marketplace}/shapes/modules/uit.js | 0 .../marketplace}/shapes/shapes.js | 0 .../marketplace}/shapes/utilities/utilities.js | 0 107 files changed, 0 insertions(+), 0 deletions(-) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/clone.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/create.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/delete.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/drop.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/equip.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/error.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/audio/select.wav (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/blue-header-bar.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/circle.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/cone.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/create-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/cube.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/cylinder.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/dodecahedron.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/hexagon.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/icosahedron.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/octagon.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/octahedron.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/prism.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/sphere.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/create/tetrahedron.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/gray-header.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/green-header-bar.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/green-header.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/horizontal-rule.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/shapes-a.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/shapes-d.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/shapes-i.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/back-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/back-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/clone-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/clone-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/clone-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/color-circle-black.png (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/color-circle.png (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/pick-color-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/slider-alpha.png (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/slider-white.png (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/color/swatches-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/common/actions-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/common/down-arrow.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/common/finish-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/common/info-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/common/up-arrow.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/delete-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/delete-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/delete-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/delete/info-text.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group/clear-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group/group-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group/selection-box-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/group/ungroup-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/buttons/collisions-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/buttons/grabbable-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/buttons/gravity-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/buttons/off-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/buttons/on-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/balloon-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/cotton-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/custom-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/default-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/ice-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/lead-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/rubber-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/tumbleweed-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/wood-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/presets/zero-g-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/properties-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/sliders/bounce-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/sliders/density-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/sliders/friction-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/physics/sliders/gravity-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/redo-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/redo-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/stretch-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/stretch-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/stretch-tool-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/stretch/info-text.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/tool-icon.fbx (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/tool-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/tools-heading.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/undo-icon.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/assets/tools/undo-label.svg (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/createPalette.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/feedback.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/groups.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/hand.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/handles.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/highlights.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/history.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/laser.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/selection.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/toolIcon.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/toolsMenu.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/modules/uit.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/shapes.js (100%) rename {scripts => unpublishedScripts/marketplace}/shapes/utilities/utilities.js (100%) diff --git a/scripts/shapes/assets/audio/clone.wav b/unpublishedScripts/marketplace/shapes/assets/audio/clone.wav similarity index 100% rename from scripts/shapes/assets/audio/clone.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/clone.wav diff --git a/scripts/shapes/assets/audio/create.wav b/unpublishedScripts/marketplace/shapes/assets/audio/create.wav similarity index 100% rename from scripts/shapes/assets/audio/create.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/create.wav diff --git a/scripts/shapes/assets/audio/delete.wav b/unpublishedScripts/marketplace/shapes/assets/audio/delete.wav similarity index 100% rename from scripts/shapes/assets/audio/delete.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/delete.wav diff --git a/scripts/shapes/assets/audio/drop.wav b/unpublishedScripts/marketplace/shapes/assets/audio/drop.wav similarity index 100% rename from scripts/shapes/assets/audio/drop.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/drop.wav diff --git a/scripts/shapes/assets/audio/equip.wav b/unpublishedScripts/marketplace/shapes/assets/audio/equip.wav similarity index 100% rename from scripts/shapes/assets/audio/equip.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/equip.wav diff --git a/scripts/shapes/assets/audio/error.wav b/unpublishedScripts/marketplace/shapes/assets/audio/error.wav similarity index 100% rename from scripts/shapes/assets/audio/error.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/error.wav diff --git a/scripts/shapes/assets/audio/select.wav b/unpublishedScripts/marketplace/shapes/assets/audio/select.wav similarity index 100% rename from scripts/shapes/assets/audio/select.wav rename to unpublishedScripts/marketplace/shapes/assets/audio/select.wav diff --git a/scripts/shapes/assets/blue-header-bar.fbx b/unpublishedScripts/marketplace/shapes/assets/blue-header-bar.fbx similarity index 100% rename from scripts/shapes/assets/blue-header-bar.fbx rename to unpublishedScripts/marketplace/shapes/assets/blue-header-bar.fbx diff --git a/scripts/shapes/assets/create/circle.fbx b/unpublishedScripts/marketplace/shapes/assets/create/circle.fbx similarity index 100% rename from scripts/shapes/assets/create/circle.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/circle.fbx diff --git a/scripts/shapes/assets/create/cone.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cone.fbx similarity index 100% rename from scripts/shapes/assets/create/cone.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/cone.fbx diff --git a/scripts/shapes/assets/create/create-heading.svg b/unpublishedScripts/marketplace/shapes/assets/create/create-heading.svg similarity index 100% rename from scripts/shapes/assets/create/create-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/create/create-heading.svg diff --git a/scripts/shapes/assets/create/cube.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cube.fbx similarity index 100% rename from scripts/shapes/assets/create/cube.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/cube.fbx diff --git a/scripts/shapes/assets/create/cylinder.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cylinder.fbx similarity index 100% rename from scripts/shapes/assets/create/cylinder.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/cylinder.fbx diff --git a/scripts/shapes/assets/create/dodecahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/dodecahedron.fbx similarity index 100% rename from scripts/shapes/assets/create/dodecahedron.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/dodecahedron.fbx diff --git a/scripts/shapes/assets/create/hexagon.fbx b/unpublishedScripts/marketplace/shapes/assets/create/hexagon.fbx similarity index 100% rename from scripts/shapes/assets/create/hexagon.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/hexagon.fbx diff --git a/scripts/shapes/assets/create/icosahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/icosahedron.fbx similarity index 100% rename from scripts/shapes/assets/create/icosahedron.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/icosahedron.fbx diff --git a/scripts/shapes/assets/create/octagon.fbx b/unpublishedScripts/marketplace/shapes/assets/create/octagon.fbx similarity index 100% rename from scripts/shapes/assets/create/octagon.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/octagon.fbx diff --git a/scripts/shapes/assets/create/octahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/octahedron.fbx similarity index 100% rename from scripts/shapes/assets/create/octahedron.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/octahedron.fbx diff --git a/scripts/shapes/assets/create/prism.fbx b/unpublishedScripts/marketplace/shapes/assets/create/prism.fbx similarity index 100% rename from scripts/shapes/assets/create/prism.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/prism.fbx diff --git a/scripts/shapes/assets/create/sphere.fbx b/unpublishedScripts/marketplace/shapes/assets/create/sphere.fbx similarity index 100% rename from scripts/shapes/assets/create/sphere.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/sphere.fbx diff --git a/scripts/shapes/assets/create/tetrahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/tetrahedron.fbx similarity index 100% rename from scripts/shapes/assets/create/tetrahedron.fbx rename to unpublishedScripts/marketplace/shapes/assets/create/tetrahedron.fbx diff --git a/scripts/shapes/assets/gray-header.fbx b/unpublishedScripts/marketplace/shapes/assets/gray-header.fbx similarity index 100% rename from scripts/shapes/assets/gray-header.fbx rename to unpublishedScripts/marketplace/shapes/assets/gray-header.fbx diff --git a/scripts/shapes/assets/green-header-bar.fbx b/unpublishedScripts/marketplace/shapes/assets/green-header-bar.fbx similarity index 100% rename from scripts/shapes/assets/green-header-bar.fbx rename to unpublishedScripts/marketplace/shapes/assets/green-header-bar.fbx diff --git a/scripts/shapes/assets/green-header.fbx b/unpublishedScripts/marketplace/shapes/assets/green-header.fbx similarity index 100% rename from scripts/shapes/assets/green-header.fbx rename to unpublishedScripts/marketplace/shapes/assets/green-header.fbx diff --git a/scripts/shapes/assets/horizontal-rule.svg b/unpublishedScripts/marketplace/shapes/assets/horizontal-rule.svg similarity index 100% rename from scripts/shapes/assets/horizontal-rule.svg rename to unpublishedScripts/marketplace/shapes/assets/horizontal-rule.svg diff --git a/scripts/shapes/assets/shapes-a.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-a.svg similarity index 100% rename from scripts/shapes/assets/shapes-a.svg rename to unpublishedScripts/marketplace/shapes/assets/shapes-a.svg diff --git a/scripts/shapes/assets/shapes-d.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-d.svg similarity index 100% rename from scripts/shapes/assets/shapes-d.svg rename to unpublishedScripts/marketplace/shapes/assets/shapes-d.svg diff --git a/scripts/shapes/assets/shapes-i.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-i.svg similarity index 100% rename from scripts/shapes/assets/shapes-i.svg rename to unpublishedScripts/marketplace/shapes/assets/shapes-i.svg diff --git a/scripts/shapes/assets/tools/back-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/back-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/back-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/back-heading.svg diff --git a/scripts/shapes/assets/tools/back-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/back-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/back-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/back-icon.svg diff --git a/scripts/shapes/assets/tools/clone-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/clone-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/clone-icon.svg diff --git a/scripts/shapes/assets/tools/clone-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-label.svg similarity index 100% rename from scripts/shapes/assets/tools/clone-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/clone-label.svg diff --git a/scripts/shapes/assets/tools/clone-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/clone-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/clone-tool-heading.svg diff --git a/scripts/shapes/assets/tools/color-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/color-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/color-icon.svg diff --git a/scripts/shapes/assets/tools/color-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-label.svg similarity index 100% rename from scripts/shapes/assets/tools/color-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/color-label.svg diff --git a/scripts/shapes/assets/tools/color-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/color-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/color-tool-heading.svg diff --git a/scripts/shapes/assets/tools/color/color-circle-black.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle-black.png similarity index 100% rename from scripts/shapes/assets/tools/color/color-circle-black.png rename to unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle-black.png diff --git a/scripts/shapes/assets/tools/color/color-circle.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle.png similarity index 100% rename from scripts/shapes/assets/tools/color/color-circle.png rename to unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle.png diff --git a/scripts/shapes/assets/tools/color/pick-color-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color/pick-color-label.svg similarity index 100% rename from scripts/shapes/assets/tools/color/pick-color-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/color/pick-color-label.svg diff --git a/scripts/shapes/assets/tools/color/slider-alpha.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-alpha.png similarity index 100% rename from scripts/shapes/assets/tools/color/slider-alpha.png rename to unpublishedScripts/marketplace/shapes/assets/tools/color/slider-alpha.png diff --git a/scripts/shapes/assets/tools/color/slider-white.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-white.png similarity index 100% rename from scripts/shapes/assets/tools/color/slider-white.png rename to unpublishedScripts/marketplace/shapes/assets/tools/color/slider-white.png diff --git a/scripts/shapes/assets/tools/color/swatches-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color/swatches-label.svg similarity index 100% rename from scripts/shapes/assets/tools/color/swatches-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/color/swatches-label.svg diff --git a/scripts/shapes/assets/tools/common/actions-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/actions-label.svg similarity index 100% rename from scripts/shapes/assets/tools/common/actions-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/common/actions-label.svg diff --git a/scripts/shapes/assets/tools/common/down-arrow.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/down-arrow.svg similarity index 100% rename from scripts/shapes/assets/tools/common/down-arrow.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/common/down-arrow.svg diff --git a/scripts/shapes/assets/tools/common/finish-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/finish-label.svg similarity index 100% rename from scripts/shapes/assets/tools/common/finish-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/common/finish-label.svg diff --git a/scripts/shapes/assets/tools/common/info-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/info-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/common/info-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/common/info-icon.svg diff --git a/scripts/shapes/assets/tools/common/up-arrow.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/up-arrow.svg similarity index 100% rename from scripts/shapes/assets/tools/common/up-arrow.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/common/up-arrow.svg diff --git a/scripts/shapes/assets/tools/delete-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/delete-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/delete-icon.svg diff --git a/scripts/shapes/assets/tools/delete-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-label.svg similarity index 100% rename from scripts/shapes/assets/tools/delete-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/delete-label.svg diff --git a/scripts/shapes/assets/tools/delete-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/delete-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/delete-tool-heading.svg diff --git a/scripts/shapes/assets/tools/delete/info-text.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete/info-text.svg similarity index 100% rename from scripts/shapes/assets/tools/delete/info-text.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/delete/info-text.svg diff --git a/scripts/shapes/assets/tools/group-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/group-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group-icon.svg diff --git a/scripts/shapes/assets/tools/group-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-label.svg similarity index 100% rename from scripts/shapes/assets/tools/group-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group-label.svg diff --git a/scripts/shapes/assets/tools/group-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/group-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group-tool-heading.svg diff --git a/scripts/shapes/assets/tools/group/clear-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/clear-label.svg similarity index 100% rename from scripts/shapes/assets/tools/group/clear-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group/clear-label.svg diff --git a/scripts/shapes/assets/tools/group/group-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/group-label.svg similarity index 100% rename from scripts/shapes/assets/tools/group/group-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group/group-label.svg diff --git a/scripts/shapes/assets/tools/group/selection-box-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/selection-box-label.svg similarity index 100% rename from scripts/shapes/assets/tools/group/selection-box-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group/selection-box-label.svg diff --git a/scripts/shapes/assets/tools/group/ungroup-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/ungroup-label.svg similarity index 100% rename from scripts/shapes/assets/tools/group/ungroup-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/group/ungroup-label.svg diff --git a/scripts/shapes/assets/tools/physics-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/physics-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics-icon.svg diff --git a/scripts/shapes/assets/tools/physics-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics-label.svg diff --git a/scripts/shapes/assets/tools/physics-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/physics-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics-tool-heading.svg diff --git a/scripts/shapes/assets/tools/physics/buttons/collisions-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/collisions-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/buttons/collisions-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/collisions-label.svg diff --git a/scripts/shapes/assets/tools/physics/buttons/grabbable-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/grabbable-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/buttons/grabbable-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/grabbable-label.svg diff --git a/scripts/shapes/assets/tools/physics/buttons/gravity-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/gravity-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/buttons/gravity-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/gravity-label.svg diff --git a/scripts/shapes/assets/tools/physics/buttons/off-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/off-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/buttons/off-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/off-label.svg diff --git a/scripts/shapes/assets/tools/physics/buttons/on-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/on-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/buttons/on-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/on-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/balloon-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/balloon-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/balloon-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/balloon-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/cotton-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/cotton-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/cotton-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/cotton-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/custom-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/custom-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/custom-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/custom-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/default-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/default-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/default-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/default-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/ice-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/ice-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/ice-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/ice-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/lead-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/lead-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/lead-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/lead-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/rubber-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/rubber-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/rubber-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/rubber-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/tumbleweed-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/tumbleweed-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/tumbleweed-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/tumbleweed-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/wood-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/wood-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/wood-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/wood-label.svg diff --git a/scripts/shapes/assets/tools/physics/presets/zero-g-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/zero-g-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/presets/zero-g-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/zero-g-label.svg diff --git a/scripts/shapes/assets/tools/physics/properties-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/properties-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/properties-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/properties-label.svg diff --git a/scripts/shapes/assets/tools/physics/sliders/bounce-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/bounce-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/sliders/bounce-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/bounce-label.svg diff --git a/scripts/shapes/assets/tools/physics/sliders/density-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/density-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/sliders/density-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/density-label.svg diff --git a/scripts/shapes/assets/tools/physics/sliders/friction-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/friction-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/sliders/friction-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/friction-label.svg diff --git a/scripts/shapes/assets/tools/physics/sliders/gravity-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/gravity-label.svg similarity index 100% rename from scripts/shapes/assets/tools/physics/sliders/gravity-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/gravity-label.svg diff --git a/scripts/shapes/assets/tools/redo-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/redo-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/redo-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/redo-icon.svg diff --git a/scripts/shapes/assets/tools/redo-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/redo-label.svg similarity index 100% rename from scripts/shapes/assets/tools/redo-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/redo-label.svg diff --git a/scripts/shapes/assets/tools/stretch-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/stretch-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/stretch-icon.svg diff --git a/scripts/shapes/assets/tools/stretch-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-label.svg similarity index 100% rename from scripts/shapes/assets/tools/stretch-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/stretch-label.svg diff --git a/scripts/shapes/assets/tools/stretch-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-tool-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/stretch-tool-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/stretch-tool-heading.svg diff --git a/scripts/shapes/assets/tools/stretch/info-text.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch/info-text.svg similarity index 100% rename from scripts/shapes/assets/tools/stretch/info-text.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/stretch/info-text.svg diff --git a/scripts/shapes/assets/tools/tool-icon.fbx b/unpublishedScripts/marketplace/shapes/assets/tools/tool-icon.fbx similarity index 100% rename from scripts/shapes/assets/tools/tool-icon.fbx rename to unpublishedScripts/marketplace/shapes/assets/tools/tool-icon.fbx diff --git a/scripts/shapes/assets/tools/tool-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/tool-label.svg similarity index 100% rename from scripts/shapes/assets/tools/tool-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/tool-label.svg diff --git a/scripts/shapes/assets/tools/tools-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/tools-heading.svg similarity index 100% rename from scripts/shapes/assets/tools/tools-heading.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/tools-heading.svg diff --git a/scripts/shapes/assets/tools/undo-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/undo-icon.svg similarity index 100% rename from scripts/shapes/assets/tools/undo-icon.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/undo-icon.svg diff --git a/scripts/shapes/assets/tools/undo-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/undo-label.svg similarity index 100% rename from scripts/shapes/assets/tools/undo-label.svg rename to unpublishedScripts/marketplace/shapes/assets/tools/undo-label.svg diff --git a/scripts/shapes/modules/createPalette.js b/unpublishedScripts/marketplace/shapes/modules/createPalette.js similarity index 100% rename from scripts/shapes/modules/createPalette.js rename to unpublishedScripts/marketplace/shapes/modules/createPalette.js diff --git a/scripts/shapes/modules/feedback.js b/unpublishedScripts/marketplace/shapes/modules/feedback.js similarity index 100% rename from scripts/shapes/modules/feedback.js rename to unpublishedScripts/marketplace/shapes/modules/feedback.js diff --git a/scripts/shapes/modules/groups.js b/unpublishedScripts/marketplace/shapes/modules/groups.js similarity index 100% rename from scripts/shapes/modules/groups.js rename to unpublishedScripts/marketplace/shapes/modules/groups.js diff --git a/scripts/shapes/modules/hand.js b/unpublishedScripts/marketplace/shapes/modules/hand.js similarity index 100% rename from scripts/shapes/modules/hand.js rename to unpublishedScripts/marketplace/shapes/modules/hand.js diff --git a/scripts/shapes/modules/handles.js b/unpublishedScripts/marketplace/shapes/modules/handles.js similarity index 100% rename from scripts/shapes/modules/handles.js rename to unpublishedScripts/marketplace/shapes/modules/handles.js diff --git a/scripts/shapes/modules/highlights.js b/unpublishedScripts/marketplace/shapes/modules/highlights.js similarity index 100% rename from scripts/shapes/modules/highlights.js rename to unpublishedScripts/marketplace/shapes/modules/highlights.js diff --git a/scripts/shapes/modules/history.js b/unpublishedScripts/marketplace/shapes/modules/history.js similarity index 100% rename from scripts/shapes/modules/history.js rename to unpublishedScripts/marketplace/shapes/modules/history.js diff --git a/scripts/shapes/modules/laser.js b/unpublishedScripts/marketplace/shapes/modules/laser.js similarity index 100% rename from scripts/shapes/modules/laser.js rename to unpublishedScripts/marketplace/shapes/modules/laser.js diff --git a/scripts/shapes/modules/selection.js b/unpublishedScripts/marketplace/shapes/modules/selection.js similarity index 100% rename from scripts/shapes/modules/selection.js rename to unpublishedScripts/marketplace/shapes/modules/selection.js diff --git a/scripts/shapes/modules/toolIcon.js b/unpublishedScripts/marketplace/shapes/modules/toolIcon.js similarity index 100% rename from scripts/shapes/modules/toolIcon.js rename to unpublishedScripts/marketplace/shapes/modules/toolIcon.js diff --git a/scripts/shapes/modules/toolsMenu.js b/unpublishedScripts/marketplace/shapes/modules/toolsMenu.js similarity index 100% rename from scripts/shapes/modules/toolsMenu.js rename to unpublishedScripts/marketplace/shapes/modules/toolsMenu.js diff --git a/scripts/shapes/modules/uit.js b/unpublishedScripts/marketplace/shapes/modules/uit.js similarity index 100% rename from scripts/shapes/modules/uit.js rename to unpublishedScripts/marketplace/shapes/modules/uit.js diff --git a/scripts/shapes/shapes.js b/unpublishedScripts/marketplace/shapes/shapes.js similarity index 100% rename from scripts/shapes/shapes.js rename to unpublishedScripts/marketplace/shapes/shapes.js diff --git a/scripts/shapes/utilities/utilities.js b/unpublishedScripts/marketplace/shapes/utilities/utilities.js similarity index 100% rename from scripts/shapes/utilities/utilities.js rename to unpublishedScripts/marketplace/shapes/utilities/utilities.js From 4d2f16efc3f1d37521f761c30d4c513a4b9f06b1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 8 Oct 2017 18:01:41 -0700 Subject: [PATCH 467/504] allow project to be built with clang on Linux --- .../src/audio/AudioMixerSlavePool.cpp | 4 ++++ .../src/avatars/AvatarMixerSlavePool.cpp | 4 ++++ cmake/externals/glew/CMakeLists.txt | 2 +- cmake/externals/nvtt/CMakeLists.txt | 2 +- cmake/macros/MemoryDebugger.cmake | 14 +++++++++++--- interface/CMakeLists.txt | 2 +- libraries/networking/src/LimitedNodeList.cpp | 2 +- 7 files changed, 23 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp index d2c19d97ba..e28c96e259 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.cpp +++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp @@ -97,7 +97,11 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 8afbc1cfe4..25b88686b7 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -97,7 +97,11 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glew/CMakeLists.txt index 28a599bfa6..6c3208981d 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glew/CMakeLists.txt @@ -9,7 +9,7 @@ ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/glew_simple_1.13.0.zip URL_MD5 73f833649e904257b35bf4e84f8bdfb5 - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/nvtt/CMakeLists.txt b/cmake/externals/nvtt/CMakeLists.txt index fa9d7b5ea1..00722bd1e0 100644 --- a/cmake/externals/nvtt/CMakeLists.txt +++ b/cmake/externals/nvtt/CMakeLists.txt @@ -31,7 +31,7 @@ else () ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 6df41257f2..29634b90b7 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -14,9 +14,17 @@ endif () if (HIFI_MEMORY_DEBUGGING) if (UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # for clang on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address") + else () + # for gcc on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + endif() endif (UNIX) endif () endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 305a6475f6..5f34ecf199 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -262,7 +262,7 @@ target_link_libraries( ) if (UNIX) - target_link_libraries(${TARGET_NAME} pthread) + target_link_libraries(${TARGET_NAME} pthread atomic) endif(UNIX) # assume we are using a Qt build without bearer management diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 574ec7f054..300a445ebd 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -676,7 +676,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t } // insert the new node and release our read lock -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX)) _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); #else _nodeHash.emplace(newNode->getUUID(), newNodePointer); From 0f4e7fb53236b7e9cbaf5536e92b93dfdb0b4508 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 3 Oct 2017 18:03:25 -0700 Subject: [PATCH 468/504] workaround for bad physics polling on login --- interface/src/Application.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0fc8c46cdc..f1e1f918e2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5639,10 +5639,10 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { return false; } + AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(0.5f * PHYSICS_READY_RANGE), glm::vec3(PHYSICS_READY_RANGE)); QVector entities; entityTree->withReadLock([&] { - AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); - entityTree->findEntities(box, entities); + entityTree->findEntities(avatarBox, entities); }); // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply @@ -5662,11 +5662,18 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { bool result = true; foreach (EntityItemPointer entity, entities) { if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); - qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); - // don't break here because we want all the relevant entities to start their downloads - result = false; + // BUG: the findEntities() query above is sometimes returning objects that don't actually overlap + // TODO: investigate and fix findQueries() but in the meantime... + // WORKAROUND: test the overlap of each entity to verify it matters + bool success = false; + AACube entityCube = entity->getQueryAACube(success); + if (success && avatarBox.touches(entityCube)) { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); + qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); + // don't break here because we want all the relevant entities to start their downloads + result = false; + } } } return result; From 0bcecdbe668466b09e209c40c3ca70b331f89292 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 5 Oct 2017 11:17:40 -0700 Subject: [PATCH 469/504] be picky when finding nearby entities at login --- interface/src/Application.cpp | 49 +++++++++++++------- libraries/entities/src/EntityTree.cpp | 6 +++ libraries/entities/src/EntityTree.h | 5 ++ libraries/entities/src/EntityTreeElement.cpp | 8 ++++ libraries/entities/src/EntityTreeElement.h | 6 +++ 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f1e1f918e2..835a2fed56 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5639,14 +5639,38 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { return false; } - AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(0.5f * PHYSICS_READY_RANGE), glm::vec3(PHYSICS_READY_RANGE)); + // We don't want to use EntityTree::findEntities(AABox, ...) method because that scan will snarf parented entities + // whose bounding boxes cannot be computed (it is too loose for our purposes here). Instead we manufacture + // custom filters and use the general-purpose EntityTree::findEntities(filter, ...) QVector entities; entityTree->withReadLock([&] { - entityTree->findEntities(avatarBox, entities); + AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); + + // create two functions that use avatarBox (entityScan and elementScan), the second calls the first + std::function entityScan = [=](EntityItemPointer& entity) { + if (entity->shouldBePhysical()) { + bool success = false; + AABox entityBox = entity->getAABox(success); + // important: bail for entities that cannot supply a valid AABox + return success && avatarBox.touches(entityBox); + } + return false; + }; + std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { + if (element->getAACube().touches(avatarBox)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->getEntities(entityScan, entities); + return true; + } + return false; + }; + + // Pass the second function to the general-purpose EntityTree::findEntities() + // which will traverse the tree, apply the two filter functions (to element, then to entities) + // as it traverses. The end result will be a list of entities that match. + entityTree->findEntities(elementScan, entities); }); - // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply - // a heuristic to try to decide when we actually know about all of the nearby entities. uint32_t nearbyCount = entities.size(); if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { _nearbyEntitiesStabilityCount++; @@ -5662,18 +5686,11 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { bool result = true; foreach (EntityItemPointer entity, entities) { if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { - // BUG: the findEntities() query above is sometimes returning objects that don't actually overlap - // TODO: investigate and fix findQueries() but in the meantime... - // WORKAROUND: test the overlap of each entity to verify it matters - bool success = false; - AACube entityCube = entity->getQueryAACube(success); - if (success && avatarBox.touches(entityCube)) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); - qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); - // don't break here because we want all the relevant entities to start their downloads - result = false; - } + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); + qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); + // don't break here because we want all the relevant entities to start their downloads + result = false; } } return result; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bf37a08386..fa386ae090 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -864,6 +864,12 @@ void EntityTree::findEntities(const ViewFrustum& frustum, QVector& foundEntities) { + recurseTreeWithOperation(elementFilter, nullptr); +} + EntityItemPointer EntityTree::findEntityByID(const QUuid& id) { EntityItemID entityID(id); return findEntityByEntityItemID(entityID); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d0448f438a..53e36bc7c7 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -165,6 +165,11 @@ public: /// \param foundEntities[out] vector of EntityItemPointer void findEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match scanOperator + /// \parameter scanOperator function that scans entities that match criteria + /// \parameter foundEntities[out] vector of EntityItemPointer + void findEntities(RecurseOctreeOperation& scanOperator, QVector& foundEntities); + void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 0c33855a61..2696377028 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -869,6 +869,14 @@ void EntityTreeElement::getEntities(const ViewFrustum& frustum, QVector& foundEntities) { + forEachEntity([&](EntityItemPointer entity) { + if (filter(entity)) { + foundEntities.push_back(entity); + } + }); +} + EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { EntityItemPointer foundEntity = NULL; withReadLock([&] { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index c7fb80c330..cafae9941a 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -27,6 +27,7 @@ class EntityTreeElement; using EntityItems = QVector; using EntityTreeElementWeakPointer = std::weak_ptr; using EntityTreeElementPointer = std::shared_ptr; +using EntityItemFilter = std::function; class EntityTreeUpdateArgs { public: @@ -199,6 +200,11 @@ public: /// \param entities[out] vector of non-const EntityItemPointer void getEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match filter + /// \param filter function that adds matching entities to foundEntities + /// \param entities[out] vector of non-const EntityItemPointer + void getEntities(EntityItemFilter& filter, QVector& foundEntities); + EntityItemPointer getEntityWithID(uint32_t id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); From bbde1bcd632767466478fdc001350f7257f5cd80 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 9 Oct 2017 10:42:42 -0700 Subject: [PATCH 470/504] move stuff out of writelock when possible --- interface/src/Application.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 835a2fed56..527e3607f5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5643,20 +5643,18 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { // whose bounding boxes cannot be computed (it is too loose for our purposes here). Instead we manufacture // custom filters and use the general-purpose EntityTree::findEntities(filter, ...) QVector entities; - entityTree->withReadLock([&] { - AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); - - // create two functions that use avatarBox (entityScan and elementScan), the second calls the first - std::function entityScan = [=](EntityItemPointer& entity) { - if (entity->shouldBePhysical()) { - bool success = false; - AABox entityBox = entity->getAABox(success); - // important: bail for entities that cannot supply a valid AABox - return success && avatarBox.touches(entityBox); - } - return false; - }; - std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { + AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); + // create two functions that use avatarBox (entityScan and elementScan), the second calls the first + std::function entityScan = [=](EntityItemPointer& entity) { + if (entity->shouldBePhysical()) { + bool success = false; + AABox entityBox = entity->getAABox(success); + // important: bail for entities that cannot supply a valid AABox + return success && avatarBox.touches(entityBox); + } + return false; + }; + std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { if (element->getAACube().touches(avatarBox)) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); entityTreeElement->getEntities(entityScan, entities); @@ -5665,6 +5663,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { return false; }; + entityTree->withReadLock([&] { // Pass the second function to the general-purpose EntityTree::findEntities() // which will traverse the tree, apply the two filter functions (to element, then to entities) // as it traverses. The end result will be a list of entities that match. From cd97ab0fdfd4f23ee5d90d37c979590a377c2dc7 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Mon, 9 Oct 2017 11:39:57 -0700 Subject: [PATCH 471/504] fixed looping sample sound bug --- .../qml/hifi/audio/PlaySampleSound.qml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 99f3648ec3..fdf579420d 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -29,12 +29,22 @@ RowLayout { function playSound() { // FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap // FIXME: Audio.playSystemSound should not require position - sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); - isPlaying = true; - sample.finished.connect(function() { isPlaying = false; sample = null; }); + if (sample === null && !isPlaying) { + sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); + isPlaying = true; + sample.finished.connect(reset); + } } function stopSound() { - sample && sample.stop(); + if (sample && isPlaying) { + sample.stop(); + } + } + + function reset() { + sample.finished.disconnect(reset); + isPlaying = false; + sample = null; } Component.onCompleted: createSampleSound(); From 3acbd7820b19caa8676f4555fff9fd79bd9ca625 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 6 Oct 2017 17:22:07 -0700 Subject: [PATCH 472/504] build error --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 8 -------- libraries/render/src/render/Item.cpp | 5 +++++ libraries/render/src/render/Item.h | 8 ++++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 053f3171be..0785226836 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -49,14 +49,6 @@ static const glm::mat4 IDENTITY_MATRIX; //#define LIVE_SHADER_RELOAD 1 extern glm::vec3 getPoint(float yaw, float pitch); -static QString readFile(const QString& filename) { - QFile file(filename); - file.open(QFile::Text | QFile::ReadOnly); - QString result; - result.append(QTextStream(&file).readAll()); - return result; -} - glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; } diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index f0fbbb1973..036c7d3a99 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -29,6 +29,11 @@ const float Item::Status::Value::CYAN = 180.0f; const float Item::Status::Value::BLUE = 240.0f; const float Item::Status::Value::MAGENTA = 300.0f; +const int Item::LAYER_2D = 0; +const int Item::LAYER_3D = 1; +const int Item::LAYER_3D_FRONT = 2; +const int Item::LAYER_3D_HUD = 3; + void Item::Status::Value::setScale(float scale) { _scale = (std::numeric_limits::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f)); } diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 8cdb41de45..2b02db81f9 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -359,10 +359,10 @@ public: // Get the layer where the item belongs. int getLayer() const { return _payload->getLayer(); } - static const int LAYER_2D = 0; - static const int LAYER_3D = 1; - static const int LAYER_3D_FRONT = 2; - static const int LAYER_3D_HUD = 3; + static const int LAYER_2D; + static const int LAYER_3D; + static const int LAYER_3D_FRONT; + static const int LAYER_3D_HUD; // Render call for the item void render(RenderArgs* args) const { _payload->render(args); } From acdd1e32e4080ea1844a07b616e22f4ecb867e22 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 12:11:07 -0700 Subject: [PATCH 473/504] First round --- .../qml/hifi/commerce/checkout/Checkout.qml | 4 +++- .../qml/hifi/commerce/purchases/Purchases.qml | 12 ++++++------ .../resources/qml/hifi/commerce/wallet/Wallet.qml | 4 +++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 09c2f6fa76..38a938c535 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -79,6 +79,8 @@ Rectangle { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; } else { + root.itemHref = result.data.download_url; + console.log("ZRF TEST " + root.itemHref); root.activeView = "checkoutSuccess"; } } @@ -125,7 +127,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 990fd348c6..ea32c139d4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -78,6 +78,10 @@ Rectangle { onInventoryResult: { purchasesReceived = true; + if (root.pendingInventoryReply) { + inventoryTimer.start(); + } + if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { @@ -98,10 +102,6 @@ Rectangle { previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); - - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } } root.pendingInventoryReply = false; @@ -112,7 +112,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'purchases_walletNotSetUp'}); } } @@ -426,7 +426,7 @@ Rectangle { itemName: title; itemId: id; itemPreviewImageUrl: preview; - itemHref: root_file_url; + itemHref: download_url; purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 9056d5bed3..9beadd3361 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -176,6 +176,8 @@ Rectangle { commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); + } else { + sendToScript({method: 'goToMarketplaceItemPage', itemId: msg.referrer}); } } else if (msg.method === 'walletSetup_raiseKeyboard') { root.keyboardRaised = true; @@ -283,7 +285,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - root.activeView = "walletHome"; + commerce.getWalletStatus(); } else { sendToScript(msg); } From 733df8391f367b6d779aa0f65d55b4d6f21c1ae3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 12:17:26 -0700 Subject: [PATCH 474/504] Round 2 --- .../qml/hifi/commerce/checkout/Checkout.qml | 1 - interface/src/commerce/QmlCommerce.cpp | 32 ++-------------- interface/src/commerce/QmlCommerce.h | 7 ---- interface/src/commerce/Wallet.cpp | 38 +++++++++++++++++++ interface/src/commerce/Wallet.h | 13 +++++++ .../scripting/WalletScriptingInterface.cpp | 5 +++ .../src/scripting/WalletScriptingInterface.h | 3 ++ .../ui/overlays/ContextOverlayInterface.cpp | 8 +++- scripts/system/commerce/wallet.js | 5 +++ scripts/system/html/js/marketplacesInject.js | 1 - scripts/system/marketplaces/marketplaces.js | 19 ++++++++-- scripts/system/notifications.js | 5 ++- 12 files changed, 93 insertions(+), 44 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 38a938c535..e2ecf927a5 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -80,7 +80,6 @@ Rectangle { root.activeView = "checkoutFailure"; } else { root.itemHref = result.data.download_url; - console.log("ZRF TEST " + root.itemHref); root.activeView = "checkoutSuccess"; } } diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 9f8847e8c7..ee75bc59e3 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -15,7 +15,6 @@ #include "Ledger.h" #include "Wallet.h" #include -#include "scripting/WalletScriptingInterface.h" HIFI_QML_DEF(QmlCommerce) @@ -29,37 +28,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult); connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); - connect(ledger.data(), &Ledger::accountResult, this, [&]() { - auto wallet = DependencyManager::get(); - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; - } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; - } else { - status = (uint)WalletStatus::WALLET_STATUS_READY; - } - - walletScriptingInterface->setWalletStatus(status); - emit walletStatusResult(status); - }); + connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); } void QmlCommerce::getWalletStatus() { - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (DependencyManager::get()->isLoggedIn()) { - // This will set account info for the wallet, allowing us to decrypt and display the security image. - account(); - } else { - status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; - emit walletStatusResult(status); - walletScriptingInterface->setWalletStatus(status); - return; - } + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); } void QmlCommerce::getLoginStatus() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 8e6af6da65..45a5360680 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -27,13 +27,6 @@ class QmlCommerce : public OffscreenQmlDialog { public: QmlCommerce(QQuickItem* parent = nullptr); - enum WalletStatus { - WALLET_STATUS_NOT_LOGGED_IN = 0, - WALLET_STATUS_NOT_SET_UP, - WALLET_STATUS_NOT_AUTHENTICATED, - WALLET_STATUS_READY - }; - signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 079e3a9479..fc16b85439 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -282,9 +282,27 @@ void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArra Wallet::Wallet() { auto nodeList = DependencyManager::get(); + auto ledger = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + + connect(ledger.data(), &Ledger::accountResult, this, [&]() { + auto wallet = DependencyManager::get(); + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; + } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; + } else { + status = (uint)WalletStatus::WALLET_STATUS_READY; + } + + walletScriptingInterface->setWalletStatus(status); + emit walletStatusResult(status); + }); } Wallet::~Wallet() { @@ -682,3 +700,23 @@ bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString decryptedText = QString("hello"); return true; } + +void Wallet::account() { + auto ledger = DependencyManager::get(); + ledger->account(); +} + +void Wallet::getWalletStatus() { + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (DependencyManager::get()->isLoggedIn()) { + // This will set account info for the wallet, allowing us to decrypt and display the security image. + account(); + } else { + status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; + emit walletStatusResult(status); + walletScriptingInterface->setWalletStatus(status); + return; + } +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index acf9f8e45e..38c5299810 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -17,6 +17,7 @@ #include #include #include +#include "scripting/WalletScriptingInterface.h" #include @@ -50,10 +51,20 @@ public: void reset(); + void getWalletStatus(); + enum WalletStatus { + WALLET_STATUS_NOT_LOGGED_IN = 0, + WALLET_STATUS_NOT_SET_UP, + WALLET_STATUS_NOT_AUTHENTICATED, + WALLET_STATUS_READY + }; + signals: void securityImageResult(bool exists); void keyFilePathIfExistsResult(const QString& path); + void walletStatusResult(uint walletStatus); + private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); @@ -71,6 +82,8 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText); + + void account(); }; #endif // hifi_Wallet_h diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 555e9477b0..99fdd5fbde 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -18,6 +18,11 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q WalletScriptingInterface::WalletScriptingInterface() { } +void WalletScriptingInterface::refreshWalletStatus() { + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); +} + static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { if (QThread::currentThread() != thread()) { diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 31b42094cf..038c580197 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -20,6 +20,7 @@ #include #include #include "Application.h" +#include "commerce/Wallet.h" class CheckoutProxy : public QmlWrapper { Q_OBJECT @@ -36,6 +37,7 @@ class WalletScriptingInterface : public QObject, public Dependency { public: WalletScriptingInterface(); + Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } void setWalletStatus(const uint& status) { _walletStatus = status; } @@ -43,6 +45,7 @@ public: signals: void walletStatusChanged(); + void walletNotSetup(); private: uint _walletStatus; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 39fd4f9377..8cbb214857 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -41,6 +41,7 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_MARKETPLACE_ID; _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; + _entityPropertyFlags += PROP_CERTIFICATE_ID; auto entityTreeRenderer = DependencyManager::get().data(); connect(entityTreeRenderer, SIGNAL(mousePressOnEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(createOrDestroyContextOverlay(const EntityItemID&, const PointerEvent&))); @@ -176,7 +177,12 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); - return (entityProperties.getMarketplaceID().length() != 0); + Setting::Handle _settingSwitch{ "commerce", false }; + if (_settingSwitch.get()) { + return (entityProperties.getCertificateID().length() != 0); + } else { + return (entityProperties.getMarketplaceID().length() != 0); + } } bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 7553ca4eeb..04b67ec14f 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -16,6 +16,8 @@ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); + var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; + // Function Name: onButtonClicked() // // Description: @@ -88,6 +90,9 @@ case 'goToPurchases': tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; + case 'goToMarketplaceItemPage': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index ded4542c51..41db724c3f 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -334,7 +334,6 @@ $('body').addClass("code-injected"); maybeAddLogInButton(); - maybeAddSetupWalletButton(); changeDropdownMenu(); var purchaseButton = $('#side-info').find('.btn').first(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e94b227a4a..deeae0d299 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -129,6 +129,10 @@ } } + function openWallet() { + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + } + function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) { wireEventBridge(true); tablet.sendToQml({ @@ -158,6 +162,7 @@ Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.refreshWalletStatus(); function onMessage(message) { @@ -214,7 +219,7 @@ } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); } else if (parsedJsonMessage.type === "MY_ITEMS") { referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; @@ -281,16 +286,22 @@ case 'purchases_openWallet': case 'checkout_openWallet': case 'checkout_setUpClicked': - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); break; case 'purchases_walletNotSetUp': - case 'checkout_walletNotSetUp': wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', referrer: "purchases" }); - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); + case 'checkout_walletNotSetUp': + wireEventBridge(true); + tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: message.itemId + }); + openWallet(); break; case 'checkout_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index ce693b6339..dc0fb1daf9 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -95,13 +95,15 @@ EDIT_ERROR: 4, TABLET: 5, CONNECTION: 6, + WALLET: 7, properties: [ { text: "Snapshot" }, { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" }, { text: "Tablet" }, - { text: "Connection" } + { text: "Connection" }, + { text: "Wallet" } ], getTypeFromMenuItem: function (menuItemName) { var type; @@ -691,6 +693,7 @@ Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); + Wallet.walletNotSetup.connect(walletNotSetup); Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); Messages.messageReceived.connect(onMessageReceived); From 873ae9b9d650244fd1bb303492759c92912b3ca0 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 9 Oct 2017 13:36:38 -0700 Subject: [PATCH 475/504] model asks for renderUpdate if animating --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 19da8a77f4..df78bd9439 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1244,6 +1244,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce mapJoints(entity, model->getJointNames()); } animate(entity); + emit requestRenderUpdate(); } } From ee02fa9a91ffb14aa5f04a9f34013f885e9d437f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 9 Oct 2017 13:36:38 -0700 Subject: [PATCH 476/504] model asks for renderUpdate if animating (cherry picked from commit 873ae9b9d650244fd1bb303492759c92912b3ca0) --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 064eacdb35..9120cd1788 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1240,6 +1240,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce mapJoints(entity, model->getJointNames()); } animate(entity); + emit requestRenderUpdate(); } } From 38b95b54031ae9e4bd2a90ce6b4d9d330f1bd520 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 15:07:14 -0700 Subject: [PATCH 477/504] Instructions HTML file and flow --- .../html/commerce/backup_instructions.html | 609 ++++++++++++++++++ .../qml/hifi/commerce/wallet/Help.qml | 8 +- .../qml/hifi/commerce/wallet/Security.qml | 15 +- .../qml/hifi/commerce/wallet/WalletSetup.qml | 19 +- interface/src/commerce/Wallet.cpp | 37 +- 5 files changed, 672 insertions(+), 16 deletions(-) create mode 100644 interface/resources/html/commerce/backup_instructions.html diff --git a/interface/resources/html/commerce/backup_instructions.html b/interface/resources/html/commerce/backup_instructions.html new file mode 100644 index 0000000000..560894e33d --- /dev/null +++ b/interface/resources/html/commerce/backup_instructions.html @@ -0,0 +1,609 @@ + + + + + +Backing Up Your Private Keys | High Fidelity + + + +

    + + diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 21548ea788..fb0fd7a76e 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -30,12 +30,14 @@ Item { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } - Component.onCompleted: { - commerce.getKeyFilePathIfExists(); + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } } RalewaySemiBold { diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 9b70bb1f71..0f2edbe913 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -25,13 +25,13 @@ Item { HifiConstants { id: hifi; } id: root; - property string keyFilePath: ""; + property string keyFilePath; Hifi.QmlCommerce { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } @@ -232,6 +232,12 @@ Item { anchors.rightMargin: 55; anchors.bottom: parent.bottom; + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } + } + HiFiGlyphs { id: yourPrivateKeysImage; text: hifi.glyphs.walletKey; @@ -320,8 +326,9 @@ Item { height: 40; onClicked: { - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); removeHmdContainer.visible = true; removeHmdContainerTimer.start(); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 898cdf0ef2..0075e86bdc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -30,6 +30,7 @@ Item { property string lastPage; property bool hasShownSecurityImageTip: false; property string referrer; + property string keyFilePath; Image { anchors.fill: parent; @@ -58,7 +59,7 @@ Item { } onKeyFilePathIfExistsResult: { - keyFilePath.text = path; + root.keyFilePath = path; } } @@ -608,7 +609,7 @@ Item { anchors.fill: parent; RalewaySemiBold { - id: keyFilePathText; + id: keyFilePathHelperText; text: "Private Key File Location:"; size: 18; anchors.top: parent.top; @@ -627,7 +628,7 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.left: parent.left; anchors.leftMargin: 30; - anchors.top: keyFilePathText.bottom; + anchors.top: keyFilePathHelperText.bottom; anchors.topMargin: 8; height: 24; width: height; @@ -643,11 +644,12 @@ Item { } onClicked: { - Qt.openUrlExternally("file:///" + keyFilePath.text.substring(0, keyFilePath.text.lastIndexOf('/'))); + Qt.openUrlExternally("file:///" + keyFilePath.substring(0, keyFilePath.lastIndexOf('/'))); } } RalewayRegular { - id: keyFilePath; + id: keyFilePathText; + text: root.keyFilePath; size: 18; anchors.top: clipboardButton.top; anchors.left: clipboardButton.right; @@ -670,7 +672,7 @@ Item { id: openInstructionsButton; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; - anchors.top: keyFilePath.bottom; + anchors.top: keyFilePathText.bottom; anchors.topMargin: 30; anchors.left: parent.left; anchors.leftMargin: 30; @@ -682,8 +684,9 @@ Item { instructions01Container.visible = false; instructions02Container.visible = true; keysReadyPageFinishButton.visible = true; - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); } } } diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fc16b85439..fe73adf487 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -41,6 +41,7 @@ #endif static const char* KEY_FILE = "hifikey"; +static const char* INSTRUCTIONS_FILE = "backup_instructions.html"; static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; @@ -104,6 +105,38 @@ RSA* readKeys(const char* filename) { return key; } +bool writeBackupInstructions() { + QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); + QString filename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile outputFile(filename); + bool retval = false; + + if (QFile::exists(filename)) + { + QFile::remove(filename); + } + QFile::copy(inputFilename, filename); + + if (QFile::exists(filename) && outputFile.open(QIODevice::ReadWrite)) { + + QByteArray fileData = outputFile.readAll(); + QString text(fileData); + + text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); + + outputFile.seek(0); // go to the beginning of the file + outputFile.write(text.toUtf8()); // write the new text back to the file + + outputFile.close(); // close the file handle. + + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << filename; + } + return retval; +} + bool writeKeys(const char* filename, RSA* keys) { FILE* fp; bool retval = false; @@ -121,6 +154,8 @@ bool writeKeys(const char* filename, RSA* keys) { QFile(QString(filename)).remove(); return retval; } + + writeBackupInstructions(); retval = true; qCDebug(commerce) << "wrote keys successfully"; @@ -488,7 +523,6 @@ bool Wallet::generateKeyPair() { // TODO: redo this soon -- need error checking and so on writeSecurityImage(_securityImage, keyFilePath()); - emit keyFilePathIfExistsResult(getKeyFilePath()); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); _publicKeys.push_back(key); @@ -646,6 +680,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(QString(keyFilePath())).remove(); QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; + emit keyFilePathIfExistsResult(getKeyFilePath()); return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; From b81a8a95eab181f794929c2ee9e1baee41435b72 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 15:23:22 -0700 Subject: [PATCH 478/504] Final fixes --- .../resources/qml/hifi/commerce/checkout/Checkout.qml | 6 +++--- interface/src/Application.cpp | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index e2ecf927a5..8d94e284ed 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -115,7 +115,7 @@ Rectangle { } onItemHrefChanged: { - itemIsJson = root.itemHref.indexOf('.json') !== -1; + itemIsJson = root.itemHref.endsWith('.json'); } onItemPriceChanged: { @@ -575,8 +575,8 @@ Rectangle { anchors.right: parent.right; text: "Rez It" onClicked: { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); } rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0fc8c46cdc..504e91ff58 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2689,15 +2689,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) - // if they come from the High Fidelity Marketplace Assets CDN QUrl jsonURL { urlString }; - if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { - emit svoImportRequested(urlString); - return true; - } else { - return false; - } + emit svoImportRequested(urlString); + return true; } bool Application::importSVOFromURL(const QString& urlString) { From aa08053a283a5d88d0610bbcfef331d140845e95 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 9 Oct 2017 17:34:43 -0700 Subject: [PATCH 479/504] fix osx build --- cmake/macros/MemoryDebugger.cmake | 6 +++--- interface/CMakeLists.txt | 8 +++++++- interface/src/scripting/WalletScriptingInterface.cpp | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 29634b90b7..ed80e03c6b 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -16,9 +16,9 @@ if (HIFI_MEMORY_DEBUGGING) if (UNIX) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # for clang on Linux - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") else () # for gcc on Linux SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 5f34ecf199..b16ad58431 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -262,7 +262,13 @@ target_link_libraries( ) if (UNIX) - target_link_libraries(${TARGET_NAME} pthread atomic) + if (CMAKE_SYSTEM_NAME MATCHES "Linux") + # Linux + target_link_libraries(${TARGET_NAME} pthread atomic) + else () + # OSX + target_link_libraries(${TARGET_NAME} pthread) + endif () endif(UNIX) # assume we are using a Qt build without bearer management diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 555e9477b0..0529cc2d4e 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -18,7 +18,6 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q WalletScriptingInterface::WalletScriptingInterface() { } -static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href)); @@ -28,6 +27,7 @@ void WalletScriptingInterface::buy(const QString& name, const QString& id, const auto tabletScriptingInterface = DependencyManager::get(); auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; tablet->loadQMLSource(CHECKOUT_QML_PATH); DependencyManager::get()->openTablet(); @@ -44,4 +44,4 @@ void WalletScriptingInterface::buy(const QString& name, const QString& id, const checkout->writeProperty("itemId", id); checkout->writeProperty("itemPrice", price); checkout->writeProperty("itemHref", href); -} \ No newline at end of file +} From e9f4dee56deed6c3b309fef0bcad6308e94967d5 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Tue, 10 Oct 2017 03:39:13 +0100 Subject: [PATCH 480/504] WL 21562 - Fix the error in the Asset Browser --- interface/resources/qml/windows/TabletModalWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml index 05f192f7a7..0c64ce67ce 100644 --- a/interface/resources/qml/windows/TabletModalWindow.qml +++ b/interface/resources/qml/windows/TabletModalWindow.qml @@ -15,7 +15,7 @@ import "." Rectangle { id: modalWindow layer.enabled: true - property var title: "Modal" + property var title: "Model" width: tabletRoot.width height: tabletRoot.height color: "#80000000" From 583b0c521ea734646c67b80f31ec3c7bc3cc5252 Mon Sep 17 00:00:00 2001 From: Cain Kilgore Date: Tue, 10 Oct 2017 03:40:48 +0100 Subject: [PATCH 481/504] Open --- interface/resources/qml/windows/TabletModalWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml index 0c64ce67ce..e21cb6b224 100644 --- a/interface/resources/qml/windows/TabletModalWindow.qml +++ b/interface/resources/qml/windows/TabletModalWindow.qml @@ -15,7 +15,7 @@ import "." Rectangle { id: modalWindow layer.enabled: true - property var title: "Model" + property var title: "Open" width: tabletRoot.width height: tabletRoot.height color: "#80000000" From fc41155ce1be98f42b85b3985541e3947dfc1cae Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 10 Oct 2017 09:17:01 -0700 Subject: [PATCH 482/504] saving work --- .../controllerModules/farActionGrabEntity.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0ef0e67471..8f6a0be163 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -119,6 +119,7 @@ Script.include("/~/system/libraries/controllers.js"); this.reticleMaxX; this.reticleMinY = MARGIN; this.reticleMaxY; + this.madeDynamic = false; var ACTION_TTL = 15; // seconds @@ -344,6 +345,13 @@ Script.include("/~/system/libraries/controllers.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + if (this.madeDynamic) { + var props = Entities.getEntityProperties(this.grabbedThingID, ["dynamic", "localVelocity"]); + props.dynamic = false; + props.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.grabbedThingID, props); + this.madeDynamic = false; + } this.actionID = null; this.grabbedThingID = null; }; @@ -503,12 +511,19 @@ Script.include("/~/system/libraries/controllers.js"); this.destroyContextOverlay(); } - if (entityIsDistanceGrabbable(targetProps)) { + if (entityIsGrabbable(targetProps)) { + if (!entityIsDistanceGrabbable(targetProps)) { + targetProps.dynamic = true; + Entities.editEntity(entityID, targetProps); + this.madeDynamic = true; + // make distance grabbale + } + if (!this.distanceRotating) { this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; } - + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && otherFarGrabModule.distanceHolding) { this.prepareDistanceRotatingData(controllerData); this.distanceRotate(otherFarGrabModule); From 003395b256303798b8430f8aa0bbf3144333183b Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 10 Oct 2017 09:43:02 -0700 Subject: [PATCH 483/504] removing whitespace --- .../system/controllers/controllerModules/farActionGrabEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 8f6a0be163..4b5a489531 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -523,7 +523,7 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; } - + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && otherFarGrabModule.distanceHolding) { this.prepareDistanceRotatingData(controllerData); this.distanceRotate(otherFarGrabModule); From 9c120f1194e9994abbfe89257de16466e3485440 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 10 Oct 2017 09:44:13 -0700 Subject: [PATCH 484/504] Update AssetMappingsScriptingInterface to not open file for activity event --- .../src/scripting/AssetMappingsScriptingInterface.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 2d288a8804..b8fd8f0f0d 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -105,12 +105,8 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, startedCallback.call(); - QFile file { path }; - int64_t size { 0 }; - if (file.open(QIODevice::ReadOnly)) { - size = file.size(); - file.close(); - } + QFileInfo fileInfo { path }; + int64_t size { fileInfo.size() }; QString extension = ""; auto idx = path.lastIndexOf("."); From d0eb6a3ad090694a2d2735fbaf6e08816d501180 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 10 Oct 2017 09:45:41 -0700 Subject: [PATCH 485/504] remove comments --- .../system/controllers/controllerModules/farActionGrabEntity.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 4b5a489531..1ebed27b0e 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -516,7 +516,6 @@ Script.include("/~/system/libraries/controllers.js"); targetProps.dynamic = true; Entities.editEntity(entityID, targetProps); this.madeDynamic = true; - // make distance grabbale } if (!this.distanceRotating) { From a179df29fbc4d0d0ac506598aac1e1b2aa9de709 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 10 Oct 2017 10:31:40 -0700 Subject: [PATCH 486/504] mouse grab non-dynamic entities --- scripts/system/controllers/grab.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 0e9b8569ae..40b1bfd23b 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -15,12 +15,13 @@ // /* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller, - isInEditMode, HMD */ + isInEditMode, HMD entityIsGrabbable*/ (function() { // BEGIN LOCAL_SCOPE -Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds @@ -330,9 +331,11 @@ Grabber.prototype.pressEvent = function(event) { return; } - var isDynamic = Entities.getEntityProperties(pickResults.objectID, "dynamic").dynamic; - if (!isDynamic) { - // only grab dynamic objects + var props = Entities.getEntityProperties(pickResults.objectID, ["dynamic", "userData", "locked", "type"]); + var isDynamic = props.dynamic; + var isGrabbable = props.grabbable; + if (!entityIsGrabbable(props)) { + // only grab grabbable objects return; } @@ -350,6 +353,7 @@ Grabber.prototype.pressEvent = function(event) { var entityProperties = Entities.getEntityProperties(clickedEntity); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; + this.madeDynamic = false; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); @@ -361,6 +365,11 @@ Grabber.prototype.pressEvent = function(event) { return; } + if (entityIsGrabbable(props) && !isDynamic) { + entityProperties.dynamic = true; + Entities.editEntity(clickedEntity, entityProperties); + this.madeDynamic = true; + } // this.activateEntity(clickedEntity, entityProperties); this.isGrabbing = true; @@ -416,6 +425,14 @@ Grabber.prototype.releaseEvent = function(event) { if (this.actionID) { Entities.deleteAction(this.entityID, this.actionID); } + + if (this.madeDynamic) { + var entityProps = Entities.getEntityProperties(this.entityID, ["dynamic", "localVelocity"]); + entityProps.dynamic = false; + entityProps.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.entityID, entityProps); + } + this.actionID = null; LaserPointers.setRenderState(this.mouseRayEntities, ""); From 8d0d9a159fd35514777f8e22af700662bde1d595 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 10:38:19 -0700 Subject: [PATCH 487/504] Bugfixes --- .../qml/hifi/commerce/wallet/Help.qml | 2 +- interface/src/commerce/Wallet.cpp | 5 + scripts/system/html/js/marketplacesInject.js | 109 ++++++++++-------- scripts/system/marketplaces/marketplaces.js | 25 ++-- 4 files changed, 80 insertions(+), 61 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index fb0fd7a76e..65c06994f8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -105,7 +105,7 @@ Item { ListElement { isExpanded: false; question: "What is a 'Security Pic'?" - answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

    When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

    If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

    The Pic is stored on your hard drive inside the same file as your private keys."); + answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

    When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

    If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

    The encrypted Pic is stored on your hard drive inside the same file as your private keys."); } ListElement { isExpanded: false; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fe73adf487..d7227a58f7 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -338,6 +338,11 @@ Wallet::Wallet() { walletScriptingInterface->setWalletStatus(status); emit walletStatusResult(status); }); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { + getWalletStatus(); + }); } Wallet::~Wallet() { diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 41db724c3f..fc16eae8bf 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -26,7 +26,7 @@ var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free + var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; @@ -99,7 +99,9 @@ } function maybeAddSetupWalletButton() { - if (userIsLoggedIn && walletNeedsSetup) { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + var resultsElement = document.getElementById('results'); var setupWalletElement = document.createElement('div'); setupWalletElement.classList.add("row"); @@ -135,7 +137,8 @@ } function maybeAddLogInButton() { - if (!userIsLoggedIn) { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); var resultsElement = document.getElementById('results'); var logInElement = document.createElement('div'); logInElement.classList.add("row"); @@ -300,68 +303,72 @@ } function injectHiFiCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); maybeAddSetupWalletButton(); - changeDropdownMenu(); - var target = document.getElementById('templated-items'); - // MutationObserver is necessary because the DOM is populated after the page is loaded. - // We're searching for changes to the element whose ID is '#templated-items' - this is - // the element that gets filled in by the AJAX. - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - injectBuyButtonOnMainPage(); + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); }); - //observer.disconnect(); - }); - var config = { attributes: true, childList: true, characterData: true }; - observer.observe(target, config); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); - // Try this here in case it works (it will if the user just pressed the "back" button, - // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage(); - maybeAddPurchasesButton(); + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + maybeAddPurchasesButton(); + } } } function injectHiFiItemPageCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); - changeDropdownMenu(); - var purchaseButton = $('#side-info').find('.btn').first(); + if (!$('body').hasClass("code-injected")) { - var href = purchaseButton.attr('href'); - purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + $('body').addClass("code-injected"); + changeDropdownMenu(); - var cost = $('.item-cost').text(); + var purchaseButton = $('#side-info').find('.btn').first(); - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { - purchaseButton.html('PURCHASE ' + cost); + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href); + }); + maybeAddPurchasesButton(); } - - purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); - maybeAddPurchasesButton(); } } @@ -622,7 +629,7 @@ if (parsedJsonMessage.type === "marketplaces") { if (parsedJsonMessage.action === "commerceSetting") { - confirmAllPurchases = !!parsedJsonMessage.data.commerceMode; + commerceMode = !!parsedJsonMessage.data.commerceMode; userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; injectCode(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index deeae0d299..bf9822ba19 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -98,6 +98,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + Wallet.refreshWalletStatus(); var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); showMarketplace(); @@ -157,11 +158,24 @@ } } + function sendCommerceSettings() { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", false), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1 + } + })); + } + marketplaceButton.clicked.connect(onClick); tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.walletStatusChanged.connect(sendCommerceSettings); Wallet.refreshWalletStatus(); function onMessage(message) { @@ -203,15 +217,7 @@ canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified }); } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", false), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1 - } - })); + sendCommerceSettings(); } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; @@ -244,6 +250,7 @@ tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); }); From 013e15c9a2cac45f6256d4a1c101bee396bf696b Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 10 Oct 2017 10:54:56 -0700 Subject: [PATCH 488/504] made requested changes --- .../system/controllers/controllerModules/farActionGrabEntity.js | 2 +- scripts/system/controllers/grab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 1ebed27b0e..fb380f992b 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -346,7 +346,7 @@ Script.include("/~/system/libraries/controllers.js"); Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); if (this.madeDynamic) { - var props = Entities.getEntityProperties(this.grabbedThingID, ["dynamic", "localVelocity"]); + var props = {}; props.dynamic = false; props.localVelocity = {x: 0, y: 0, z: 0}; Entities.editEntity(this.grabbedThingID, props); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 40b1bfd23b..2f046cbce3 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -427,7 +427,7 @@ Grabber.prototype.releaseEvent = function(event) { } if (this.madeDynamic) { - var entityProps = Entities.getEntityProperties(this.entityID, ["dynamic", "localVelocity"]); + var entityProps = {}; entityProps.dynamic = false; entityProps.localVelocity = {x: 0, y: 0, z: 0}; Entities.editEntity(this.entityID, entityProps); From be2b2416293dabbe702414766eec607a2a250d95 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 12:20:48 -0700 Subject: [PATCH 489/504] Lots of progress; don't yet have some things --- .../InspectionCertificate.qml | 77 ++++++++++++++----- .../hifi/commerce/purchases/PurchasedItem.qml | 5 +- .../qml/hifi/commerce/purchases/Purchases.qml | 3 +- interface/src/commerce/Ledger.cpp | 20 +++-- interface/src/commerce/Ledger.h | 7 +- interface/src/commerce/QmlCommerce.cpp | 6 ++ interface/src/commerce/QmlCommerce.h | 3 + scripts/system/marketplaces/marketplaces.js | 20 ++--- 8 files changed, 93 insertions(+), 48 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 19728daa82..2a20f7fa9b 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -25,7 +25,8 @@ Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceId: ""; + property string marketplaceUrl; + property string certificateId; property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; @@ -35,6 +36,26 @@ Rectangle { color: hifi.colors.faintGray; Hifi.QmlCommerce { id: commerce; + + onCertificateInfoResult: { + if (result.status !== 'success') { + console.log("Failed to get certificate info", result.message); + } else { + root.marketplaceUrl = result.data.marketplace_item_url; + root.itemOwner = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); + root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); + + if (result.data.invalid_reason) { + errorText.text = "Here we will display some text if there's an error with the certificate " + + "(DMCA takedown, invalid cert, location of item updated)"; + } + } + } + } + + onCertificateIdChanged: { + commerce.certificateInfo(certificateId); } // This object is always used in a popup. @@ -126,7 +147,7 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; onExited: itemName.color = hifi.colors.blueAccent; @@ -199,11 +220,10 @@ Rectangle { RalewayRegular { id: dateOfPurchaseHeader; text: "DATE OF PURCHASE"; - visible: root.dateOfPurchase !== ""; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; + anchors.top: edition.bottom; anchors.topMargin: 20; anchors.left: parent.left; anchors.leftMargin: 45; @@ -216,14 +236,13 @@ Rectangle { AnonymousProRegular { id: dateOfPurchase; text: root.dateOfPurchase; - visible: root.dateOfPurchase !== ""; // Text size size: 22; // Anchors - anchors.top: editionHeader.bottom; + anchors.top: dateOfPurchaseHeader.bottom; anchors.topMargin: 4; - anchors.left: editionHeader.left; - anchors.right: editionHeader.right; + anchors.left: dateOfPurchaseHeader.left; + anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -231,15 +250,13 @@ Rectangle { RalewayRegular { id: errorText; - text: "Here we will display some text if there's an error with the certificate " + - "(DMCA takedown, invalid cert, location of item updated)"; // Text size size: 20; // Anchors - anchors.top: root.dateOfPurchase !== "" ? dateOfPurchase.bottom : edition.bottom; + anchors.top: dateOfPurchase.bottom; anchors.topMargin: 40; - anchors.left: root.dateOfPurchase !== "" ? dateOfPurchase.left : edition.left; - anchors.right: root.dateOfPurchase !== "" ? dateOfPurchase.right : edition.right; + anchors.left: dateOfPurchase.left; + anchors.right: dateOfPurchase.right; anchors.bottom: parent.bottom; // Style wrapMode: Text.WordWrap; @@ -290,7 +307,7 @@ Rectangle { height: 50; text: "View In Market" onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } } } @@ -313,19 +330,37 @@ Rectangle { // function fromScript(message) { switch (message.method) { - case 'inspectionCertificate_setMarketplaceId': - root.marketplaceId = message.marketplaceId; - break; - case 'inspectionCertificate_setItemInfo': - root.itemName = message.itemName; - root.itemOwner = message.itemOwner; - root.itemEdition = message.itemEdition; + case 'inspectionCertificate_setCertificateId': + root.certificateId = message.certificateId; break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } } signal sendToScript(var message); + + function getFormattedDate(timestamp) { + var a = new Date(timestamp); + var year = a.getFullYear(); + var month = a.getMonth(); + var day = a.getDate(); + var hour = a.getHours(); + var drawnHour = hour; + if (hour === 0) { + drawnHour = 12; + } else if (hour > 12) { + drawnHour -= 12; + } + + var amOrPm = "AM"; + if (hour >= 12) { + amOrPm = "PM"; + } + + var min = a.getMinutes(); + var sec = a.getSeconds(); + return year + '-' + month + '-' + day + ' ' + drawnHour + ':' + min + amOrPm; + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 5eb5516519..e7e16668fe 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -34,6 +34,7 @@ Item { property string itemId; property string itemPreviewImageUrl; property string itemHref; + property string certificateId; property int displayedItemCount; property int itemEdition; property int numberSold; @@ -168,7 +169,7 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToPurchases({method: 'purchases_itemCertificateClicked', itemMarketplaceId: root.itemId}); + sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); } onEntered: { certificateIcon.color = hifi.colors.black; @@ -225,7 +226,7 @@ Item { } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" } else if (root.numberSold !== -1) { - ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "INFTY" : root.limitedRun)) + ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) } else { "" } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index ea32c139d4..54abe2d5fc 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -684,8 +684,7 @@ Rectangle { titleBarContainer.referrerURL = message.referrerURL; filterBar.text = message.filterText ? message.filterText : ""; break; - case 'inspectionCertificate_setMarketplaceId': - case 'inspectionCertificate_setItemInfo': + case 'inspectionCertificate_setCertificateId': inspectionCertificate.fromScript(message); break; case 'purchases_showMyItems': diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index a68a6fe929..9a6840c76a 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -13,7 +13,6 @@ #include #include #include -#include "AccountManager.h" #include "Wallet.h" #include "Ledger.h" #include "CommerceLogging.h" @@ -47,14 +46,15 @@ Handler(buy) Handler(receiveAt) Handler(balance) Handler(inventory) +Handler(certificateInfo) -void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) { +void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; JSONCallbackParameters callbackParams(this, success, this, fail); qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); accountManager->sendRequest(URL + endpoint, - AccountManagerAuth::Required, + authType, method, callbackParams, QJsonDocument(request).toJson()); @@ -70,14 +70,14 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con } else { request["signature"] = QString("controlled failure!"); } - send(endpoint, success, fail, QNetworkAccessManager::PutOperation, request); + send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { auto wallet = DependencyManager::get(); QJsonObject request; request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, request); + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { @@ -192,7 +192,7 @@ void Ledger::history(const QStringList& keys) { void Ledger::resetSuccess(QNetworkReply& reply) { apiResponse("reset", reply); } void Ledger::resetFailure(QNetworkReply& reply) { failResponse("reset", reply); } void Ledger::reset() { - send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, QJsonObject()); + send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, QJsonObject()); } void Ledger::accountSuccess(QNetworkReply& reply) { @@ -217,7 +217,7 @@ void Ledger::accountFailure(QNetworkReply& reply) { failResponse("account", reply); } void Ledger::account() { - send("hfc_account", "accountSuccess", "accountFailure", QNetworkAccessManager::PutOperation, QJsonObject()); + send("hfc_account", "accountSuccess", "accountFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, QJsonObject()); } // The api/failResponse is called just for the side effect of logging. @@ -234,3 +234,9 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure); } + +void Ledger::certificateInfo(const QString& certificateId) { + QString endpoint = "proof_of_purchase_status/transfer/" + certificateId; + QJsonObject request; + send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index da6c67224f..ae001010f0 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -17,6 +17,7 @@ #include #include #include +#include "AccountManager.h" class Ledger : public QObject, public Dependency { @@ -32,6 +33,7 @@ public: void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); + void certificateInfo(const QString& certificateId); signals: void buyResult(QJsonObject result); @@ -41,6 +43,7 @@ signals: void historyResult(QJsonObject result); void accountResult(QJsonObject result); void locationUpdateResult(QJsonObject result); + void certificateInfoResult(QJsonObject result); public slots: void buySuccess(QNetworkReply& reply); @@ -59,11 +62,13 @@ public slots: void accountFailure(QNetworkReply& reply); void updateLocationSuccess(QNetworkReply& reply); void updateLocationFailure(QNetworkReply& reply); + void certificateInfoSuccess(QNetworkReply& reply); + void certificateInfoFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); - void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request); + void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); void keysQuery(const QString& endpoint, const QString& success, const QString& fail); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index ee75bc59e3..803264fa9f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -29,6 +29,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); + connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); } void QmlCommerce::getWalletStatus() { @@ -125,3 +126,8 @@ void QmlCommerce::account() { auto ledger = DependencyManager::get(); ledger->account(); } + +void QmlCommerce::certificateInfo(const QString& certificateId) { + auto ledger = DependencyManager::get(); + ledger->certificateInfo(certificateId); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 45a5360680..ae63133425 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -43,6 +43,7 @@ signals: void inventoryResult(QJsonObject result); void historyResult(QJsonObject result); void accountResult(QJsonObject result); + void certificateInfoResult(QJsonObject result); protected: Q_INVOKABLE void getWalletStatus(); @@ -63,6 +64,8 @@ protected: Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void account(); + + Q_INVOKABLE void certificateInfo(const QString& certificateId); }; #endif // hifi_QmlCommerce_h diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index bf9822ba19..6880d10c18 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -134,22 +134,12 @@ tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); } - function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) { + function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { wireEventBridge(true); tablet.sendToQml({ - method: 'inspectionCertificate_setMarketplaceId', - marketplaceId: itemMarketplaceId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['marketplaceID']).marketplaceID + method: 'inspectionCertificate_setCertificateId', + certificateId: itemCertificateId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID }); - // ZRF FIXME! Make a call to the endpoint to get item info instead of this silliness - Script.setTimeout(function () { - var randomNumber = Math.floor((Math.random() * 150) + 1); - tablet.sendToQml({ - method: 'inspectionCertificate_setItemInfo', - itemName: "The Greatest Item", - itemOwner: "ABCDEFG1234567", - itemEdition: (Math.floor(Math.random() * randomNumber) + " / " + randomNumber) - }); - }, 500); } function onUsernameChanged() { @@ -358,13 +348,13 @@ tablet.loadQMLSource("TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': - setCertificateInfo("", message.itemMarketplaceId); + setCertificateInfo("", message.itemCertificateId); break; case 'inspectionCertificate_closeClicked': tablet.gotoHomeScreen(); break; case 'inspectionCertificate_showInMarketplaceClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'header_myItemsClicked': referrerURL = MARKETPLACE_URL_INITIAL; From 702528a8cc492c5a17e0125eb030fa07a7d2990c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 13:27:17 -0700 Subject: [PATCH 490/504] Fully integrate cert properties --- .../InspectionCertificate.qml | 37 ++++++++++++++++--- .../qml/hifi/commerce/purchases/Purchases.qml | 1 + 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 2a20f7fa9b..7e3c665543 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -30,7 +30,7 @@ Rectangle { property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; - property string dateOfPurchase: ""; + property string dateOfPurchase: "--"; property bool isLightbox: false; // Style color: hifi.colors.faintGray; @@ -45,17 +45,43 @@ Rectangle { root.itemOwner = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); + root.itemName = result.data.marketplace_item_name; - if (result.data.invalid_reason) { - errorText.text = "Here we will display some text if there's an error with the certificate " + - "(DMCA takedown, invalid cert, location of item updated)"; + if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + popText.text = ""; + if (result.data.invalid_reason) { + errorText.text = result.data.invalid_reason; + } + } else if (result.data.transfer_status[0] === "pending") { + titleBarText.text = "Certificate Pending"; + errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " + + "this entity will be cleaned up by the domain."; + errorText.color = hifi.colors.baseGray; } } } } onCertificateIdChanged: { - commerce.certificateInfo(certificateId); + if (certificateId !== "") { + commerce.certificateInfo(certificateId); + } + } + + onVisibleChanged: { + if (!visible) { + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PURCHASE"; + root.certificateId = ""; + root.itemName = "--"; + root.itemOwner = "--"; + root.itemEdition = "--"; + root.dateOfPurchase = "--"; + root.marketplaceUrl = ""; + errorText.text = ""; + } } // This object is always used in a popup. @@ -298,6 +324,7 @@ Rectangle { // "Show In Marketplace" button HifiControlsUit.Button { id: showInMarketplaceButton; + enabled: root.marketplaceUrl; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 54abe2d5fc..b5697f687d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -427,6 +427,7 @@ Rectangle { itemId: id; itemPreviewImageUrl: preview; itemHref: download_url; + certificateId: certificate_id; purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; From 5e383e2b1c3f96380aeb0777cbe7dc8e72f76d68 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 14:56:17 -0700 Subject: [PATCH 491/504] My Item certificates --- .../InspectionCertificate.qml | 63 +++++++++++++------ interface/src/commerce/Ledger.cpp | 19 +++++- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 7e3c665543..90c8c318a5 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -32,6 +32,7 @@ Rectangle { property string itemEdition: "--"; property string dateOfPurchase: "--"; property bool isLightbox: false; + property bool isMyCert: false; // Style color: hifi.colors.faintGray; Hifi.QmlCommerce { @@ -42,7 +43,9 @@ Rectangle { console.log("Failed to get certificate info", result.message); } else { root.marketplaceUrl = result.data.marketplace_item_url; - root.itemOwner = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + root.isMyCert = result.isMyCert ? result.isMyCert : false; + root.itemOwner = root.isMyCert ? Account.username : + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); root.itemName = result.data.marketplace_item_name; @@ -80,6 +83,7 @@ Rectangle { root.itemEdition = "--"; root.dateOfPurchase = "--"; root.marketplaceUrl = ""; + root.isMyCert = false; errorText.text = ""; } } @@ -162,7 +166,7 @@ Rectangle { size: 28; // Anchors anchors.top: itemNameHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: itemNameHeader.left; anchors.right: itemNameHeader.right; height: paintedHeight; @@ -187,14 +191,20 @@ Rectangle { size: 16; // Anchors anchors.top: itemName.bottom; - anchors.topMargin: 20; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; + } + FontLoader { id: ralewayRegular; source: "../../../../fonts/Raleway-Regular.ttf"; } + TextMetrics { + id: textMetrics; + font.family: ralewayRegular.name + text: root.itemOwner; } RalewayRegular { id: ownedBy; @@ -203,14 +213,31 @@ Rectangle { size: 22; // Anchors anchors.top: ownedByHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: ownedByHeader.left; - anchors.right: ownedByHeader.right; - height: paintedHeight; + height: textMetrics.height; + width: root.isMyCert ? textMetrics.width + 25 : ownedByHeader.width; // Style color: hifi.colors.darkGray; elide: Text.ElideRight; } + AnonymousProRegular { + id: isMyCertText; + visible: root.isMyCert; + text: "(Private)"; + size: 18; + // Anchors + anchors.top: ownedBy.top; + anchors.topMargin: 4; + anchors.bottom: ownedBy.bottom; + anchors.left: ownedBy.right; + anchors.leftMargin: 4; + anchors.right: ownedByHeader.right; + // Style + color: hifi.colors.lightGray; + elide: Text.ElideRight; + verticalAlignment: Text.AlignVCenter; + } RalewayRegular { id: editionHeader; @@ -219,23 +246,23 @@ Rectangle { size: 16; // Anchors anchors.top: ownedBy.bottom; - anchors.topMargin: 20; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; } AnonymousProRegular { id: edition; text: root.itemEdition; // Text size - size: 22; + size: 18; // Anchors anchors.top: editionHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: editionHeader.left; anchors.right: editionHeader.right; height: paintedHeight; @@ -250,23 +277,23 @@ Rectangle { size: 16; // Anchors anchors.top: edition.bottom; - anchors.topMargin: 20; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; } AnonymousProRegular { id: dateOfPurchase; text: root.dateOfPurchase; // Text size - size: 22; + size: 18; // Anchors anchors.top: dateOfPurchaseHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: dateOfPurchaseHeader.left; anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; @@ -280,7 +307,7 @@ Rectangle { size: 20; // Anchors anchors.top: dateOfPurchase.bottom; - anchors.topMargin: 40; + anchors.topMargin: 36; anchors.left: dateOfPurchase.left; anchors.right: dateOfPurchase.right; anchors.bottom: parent.bottom; @@ -297,7 +324,7 @@ Rectangle { Item { id: buttonsContainer; anchors.bottom: parent.bottom; - anchors.bottomMargin: 50; + anchors.bottomMargin: 30; anchors.left: parent.left; anchors.right: parent.right; height: 50; @@ -386,7 +413,7 @@ Rectangle { var min = a.getMinutes(); var sec = a.getSeconds(); - return year + '-' + month + '-' + day + ' ' + drawnHour + ':' + min + amOrPm; + return year + '-' + month + '-' + day + '
    ' + drawnHour + ':' + min + amOrPm; } // // FUNCTION DEFINITIONS END diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 9a6840c76a..b63eff0eee 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -46,7 +46,6 @@ Handler(buy) Handler(receiveAt) Handler(balance) Handler(inventory) -Handler(certificateInfo) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -235,6 +234,24 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure); } +void Ledger::certificateInfoSuccess(QNetworkReply& reply) { + auto wallet = DependencyManager::get(); + auto accountManager = DependencyManager::get(); + + QByteArray response = reply.readAll(); + QJsonObject replyObject = QJsonDocument::fromJson(response).object(); + + QStringList keys = wallet->listPublicKeys(); + if (keys.count() != 0) { + QJsonObject data = replyObject["data"].toObject(); + if (data["transfer_recipient_key"].toString() == keys[0]) { + replyObject.insert("isMyCert", true); + } + } + qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact); + emit certificateInfoResult(replyObject); +} +void Ledger::certificateInfoFailure(QNetworkReply& reply) { failResponse("certificateInfo", reply); } void Ledger::certificateInfo(const QString& certificateId) { QString endpoint = "proof_of_purchase_status/transfer/" + certificateId; QJsonObject request; From cea0f48e5c73e3e98835122f590c5e4e8cade46d Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 10 Oct 2017 15:19:09 -0700 Subject: [PATCH 492/504] Fix warnings --- libraries/image/src/image/Image.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index a2f5f93e9b..58b920da4d 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -74,9 +74,7 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { -enum { - QIMAGE_HDR_FORMAT = QImage::Format_RGB30 -}; +QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { @@ -440,10 +438,8 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom auto mipFormat = texture->getStoredMipFormat(); std::function unpackFunc; - nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::RoundMode roundMode = nvtt::RoundMode_None; nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; nvtt::CompressionOptions compressionOptions; From 4ebccfa65c8205ea0b35c523b5034237b88e4d48 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 16:23:43 -0700 Subject: [PATCH 493/504] Use put instead of get sob --- interface/src/commerce/Ledger.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index b63eff0eee..80e599fb24 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -253,7 +253,8 @@ void Ledger::certificateInfoSuccess(QNetworkReply& reply) { } void Ledger::certificateInfoFailure(QNetworkReply& reply) { failResponse("certificateInfo", reply); } void Ledger::certificateInfo(const QString& certificateId) { - QString endpoint = "proof_of_purchase_status/transfer/" + certificateId; + QString endpoint = "proof_of_purchase_status/transfer"; QJsonObject request; - send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); + request["certificate_id"] = certificateId; + send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request); } From 026195223a0da05106744fbd1d83e380980c015f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 10 Oct 2017 12:58:37 -0700 Subject: [PATCH 494/504] more correct use of BufferView in GeometryCache --- libraries/render-utils/src/GeometryCache.cpp | 75 ++++++++++++-------- libraries/render-utils/src/GeometryCache.h | 8 +-- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 658eab7210..93f3002fe8 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -84,67 +84,83 @@ std::vector polygon() { } void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { + gpu::Buffer::Size offset = vertexBuffer->getSize(); vertexBuffer->append(vertices); - _positionView = gpu::BufferView(vertexBuffer, 0, - vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); - _normalView = gpu::BufferView(vertexBuffer, SHAPE_NORMALS_OFFSET, - vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); + gpu::Buffer::Size viewSize = vertices.size() * 2 * sizeof(glm::vec3); + + _positionView = gpu::BufferView(vertexBuffer, offset, + viewSize, SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); + _normalView = gpu::BufferView(vertexBuffer, offset + SHAPE_NORMALS_OFFSET, + viewSize, SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); } void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices) { - _indices = indexBuffer; + gpu::Buffer::Size offset = indexBuffer->getSize(); if (!indices.empty()) { - _indexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; - _indexCount = indices.size(); - indexBuffer->append(indices); + for (uint32_t i = 0; i < indices.size(); ++i) { + indexBuffer->append((uint16_t)indices[i]); + } } + gpu::Size viewSize = indices.size() * sizeof(uint16_t); + _indicesView = gpu::BufferView(indexBuffer, offset, viewSize, gpu::Element::INDEX_UINT16); + offset = indexBuffer->getSize(); if (!wireIndices.empty()) { - _wireIndexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; - _wireIndexCount = wireIndices.size(); - indexBuffer->append(wireIndices); + for (uint32_t i = 0; i < wireIndices.size(); ++i) { + indexBuffer->append((uint16_t)wireIndices[i]); + } } + viewSize = wireIndices.size() * sizeof(uint16_t); + _wireIndicesView = gpu::BufferView(indexBuffer, offset, viewSize, gpu::Element::INDEX_UINT16); } void GeometryCache::ShapeData::setupBatch(gpu::Batch& batch) const { batch.setInputBuffer(gpu::Stream::POSITION, _positionView); batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); - batch.setIndexBuffer(SHAPE_INDEX_TYPE, _indices, 0); + batch.setIndexBuffer(_indicesView); } void GeometryCache::ShapeData::draw(gpu::Batch& batch) const { - if (_indexCount) { + gpu::Buffer::Size numIndices = _indicesView.getNumElements(); + if (numIndices > 0) { setupBatch(batch); - batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_indexCount, (gpu::uint32)_indexOffset); + batch.drawIndexed(gpu::TRIANGLES, numIndices, 0); } } void GeometryCache::ShapeData::drawWire(gpu::Batch& batch) const { - if (_wireIndexCount) { - setupBatch(batch); - batch.drawIndexed(gpu::LINES, (gpu::uint32)_wireIndexCount, (gpu::uint32)_wireIndexOffset); + gpu::Buffer::Size numIndices = _wireIndicesView.getNumElements(); + if (numIndices > 0) { + batch.setInputBuffer(gpu::Stream::POSITION, _positionView); + batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); + batch.setIndexBuffer(_wireIndicesView); + batch.drawIndexed(gpu::LINES, numIndices, 0); } } void GeometryCache::ShapeData::drawInstances(gpu::Batch& batch, size_t count) const { - if (_indexCount) { + gpu::Buffer::Size numIndices = _indicesView.getNumElements(); + if (numIndices > 0) { setupBatch(batch); - batch.drawIndexedInstanced((gpu::uint32)count, gpu::TRIANGLES, (gpu::uint32)_indexCount, (gpu::uint32)_indexOffset); + batch.drawIndexedInstanced((gpu::uint32)count, gpu::TRIANGLES, numIndices, 0); } } void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count) const { - if (_wireIndexCount) { - setupBatch(batch); - batch.drawIndexedInstanced((gpu::uint32)count, gpu::LINES, (gpu::uint32)_wireIndexCount, (gpu::uint32)_wireIndexOffset); + gpu::Buffer::Size numIndices = _wireIndicesView.getNumElements(); + if (numIndices > 0) { + batch.setInputBuffer(gpu::Stream::POSITION, _positionView); + batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); + batch.setIndexBuffer(_wireIndicesView); + batch.drawIndexedInstanced((gpu::uint32)count, gpu::LINES, numIndices, 0); } } static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3; size_t GeometryCache::getShapeTriangleCount(Shape shape) { - return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE; + return _shapes[shape]._indicesView.getNumElements() / VERTICES_PER_TRIANGLE; } size_t GeometryCache::getSphereTriangleCount() { @@ -168,7 +184,6 @@ static IndexPair indexToken(geometry::Index a, geometry::Index b) { template void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; IndexPairs wireSeenIndices; @@ -179,6 +194,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& face = shape.faces[f]; // Compute the face normal @@ -219,7 +235,6 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; vertices.reserve(shape.vertices.size() * 2); @@ -236,6 +251,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid solidIndices.reserve(faceIndexCount * faceCount); + Index baseVertex = 0; for (size_t f = 0; f < faceCount; f++) { const Face& face = shape.faces[f]; // Create the wire indices for unseen edges @@ -265,7 +281,6 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid template void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; @@ -286,6 +301,7 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver vertices.push_back(vec3(v.x, -0.5f, v.z)); vertices.push_back(vec3(0.0f, -1.0f, 0.0f)); } + Index baseVertex = 0; for (uint32_t i = 2; i < N; i++) { solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + i); @@ -343,7 +359,6 @@ void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexB // Draw a circle with radius 1/4th the size of the bounding box using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; const int NUM_CIRCLE_VERTICES = 64; @@ -354,6 +369,7 @@ void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexB vertices.push_back(vec3(0.0f, 0.0f, 0.0f)); } + Index baseVertex = 0; for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) { solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + i); @@ -403,7 +419,6 @@ void GeometryCache::buildShapes() { // Line { - Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); ShapeData& shapeData = _shapes[Line]; shapeData.setupVertices(_shapeVertices, VertexVector { vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f), @@ -411,8 +426,8 @@ void GeometryCache::buildShapes() { }); IndexVector wireIndices; // Only two indices - wireIndices.push_back(0 + baseVertex); - wireIndices.push_back(1 + baseVertex); + wireIndices.push_back(0); + wireIndices.push_back(1); shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 288ab363f0..5a437cf5e9 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -339,14 +339,10 @@ public: void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); struct ShapeData { - size_t _indexOffset{ 0 }; - size_t _indexCount{ 0 }; - size_t _wireIndexOffset{ 0 }; - size_t _wireIndexCount{ 0 }; - gpu::BufferView _positionView; gpu::BufferView _normalView; - gpu::BufferPointer _indices; + gpu::BufferView _indicesView; + gpu::BufferView _wireIndicesView; void setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices); void setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices); From 4bf99fe9d2aef8253a627426d3097f66fc8eaeff Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 10 Oct 2017 17:05:54 -0700 Subject: [PATCH 495/504] remove unused variable --- libraries/render-utils/src/GeometryCache.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 93f3002fe8..c8fa73947a 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -69,7 +69,6 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals 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() { From 886422cbef2a2573b5349791a8c28baa11c3b710 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 10 Oct 2017 17:07:17 -0700 Subject: [PATCH 496/504] remove unused variable --- libraries/render-utils/src/GeometryCache.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index c8fa73947a..c7bc09a5e3 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -68,7 +68,6 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); -static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; template std::vector polygon() { From 69b6a8c1639638acf333c9878d177dce33dade0f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 10 Oct 2017 15:22:32 -0700 Subject: [PATCH 497/504] Fix .fst texdir not being followed Although the texdir was being acknowledged and used as the _textureBaseURL inside of the Geometry* classes, it was being overwritten in code meant to handle redirects. Basically, when a geometry resource request is redirected (via ATP, HTTP, etc.), we needed to update the _textureBaseURL to take the new location into account. Previously we were overwriting the _textureBaseURL all the time, even when not being redirected, but this updates it to only be overwritten when the request is redirected. There is at least 1 known case that this does not handle: a .fst with its `texdir` set, that points at an fbx that gets redirected. --- .../src/model-networking/ModelCache.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2756334a1a..b62ad7b366 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -71,14 +71,14 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } else { QUrl url = _url.resolved(filename); - QString texdir = mapping.value("texdir").toString(); + QString texdir = mapping.value(TEXDIR_FIELD).toString(); if (!texdir.isNull()) { if (!texdir.endsWith('/')) { texdir += '/'; } _textureBaseUrl = resolveTextureBaseUrl(url, _url.resolved(texdir)); } else { - _textureBaseUrl = _effectiveBaseURL; + _textureBaseUrl = url.resolved(QUrl(".")); } auto animGraphVariant = mapping.value("animGraphUrl"); @@ -241,8 +241,10 @@ private: }; void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { - _url = _effectiveBaseURL; - _textureBaseUrl = _effectiveBaseURL; + if (_url != _effectiveBaseURL) { + _url = _effectiveBaseURL; + _textureBaseUrl = _effectiveBaseURL; + } QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts)); } From d9ba75ca72c0413e98bf6870f45439d24a28facf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 11 Oct 2017 06:07:07 -0700 Subject: [PATCH 498/504] fix warning about implicit cast from 64 to 32 bits --- libraries/render-utils/src/GeometryCache.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index c7bc09a5e3..cf8e268681 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -120,7 +120,7 @@ void GeometryCache::ShapeData::setupBatch(gpu::Batch& batch) const { } void GeometryCache::ShapeData::draw(gpu::Batch& batch) const { - gpu::Buffer::Size numIndices = _indicesView.getNumElements(); + uint32_t numIndices = (uint32_t)_indicesView.getNumElements(); if (numIndices > 0) { setupBatch(batch); batch.drawIndexed(gpu::TRIANGLES, numIndices, 0); @@ -128,7 +128,7 @@ void GeometryCache::ShapeData::draw(gpu::Batch& batch) const { } void GeometryCache::ShapeData::drawWire(gpu::Batch& batch) const { - gpu::Buffer::Size numIndices = _wireIndicesView.getNumElements(); + uint32_t numIndices = (uint32_t)_wireIndicesView.getNumElements(); if (numIndices > 0) { batch.setInputBuffer(gpu::Stream::POSITION, _positionView); batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); @@ -138,7 +138,7 @@ void GeometryCache::ShapeData::drawWire(gpu::Batch& batch) const { } void GeometryCache::ShapeData::drawInstances(gpu::Batch& batch, size_t count) const { - gpu::Buffer::Size numIndices = _indicesView.getNumElements(); + uint32_t numIndices = (uint32_t)_indicesView.getNumElements(); if (numIndices > 0) { setupBatch(batch); batch.drawIndexedInstanced((gpu::uint32)count, gpu::TRIANGLES, numIndices, 0); @@ -146,7 +146,7 @@ void GeometryCache::ShapeData::drawInstances(gpu::Batch& batch, size_t count) co } void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count) const { - gpu::Buffer::Size numIndices = _wireIndicesView.getNumElements(); + uint32_t numIndices = (uint32_t)_wireIndicesView.getNumElements(); if (numIndices > 0) { batch.setInputBuffer(gpu::Stream::POSITION, _positionView); batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); From c2733a4186f622aa68fdf94240f2e9fb195e6d33 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 11 Oct 2017 09:47:16 -0700 Subject: [PATCH 499/504] Fix notification bug --- scripts/system/notifications.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index dc0fb1daf9..ef2c674a41 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -576,6 +576,10 @@ createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); } + function processingGif() { + createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET); + } + function connectionAdded(connectionName) { createNotification(connectionName, NotificationType.CONNECTION); } From 3f0ec73be687c7292a51c2b99ba2592dc68a92c2 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 11 Oct 2017 10:55:14 -0700 Subject: [PATCH 500/504] Fix date displays --- .../inspectionCertificate/InspectionCertificate.qml | 13 +++++++++---- .../qml/hifi/commerce/wallet/WalletHome.qml | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 90c8c318a5..fe1f049e5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -394,10 +394,14 @@ Rectangle { signal sendToScript(var message); function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + var a = new Date(timestamp); var year = a.getFullYear(); - var month = a.getMonth(); - var day = a.getDate(); + var month = addLeadingZero(a.getMonth()); + var day = addLeadingZero(a.getDate()); var hour = a.getHours(); var drawnHour = hour; if (hour === 0) { @@ -405,14 +409,15 @@ Rectangle { } else if (hour > 12) { drawnHour -= 12; } + drawnHour = addLeadingZero(drawnHour); var amOrPm = "AM"; if (hour >= 12) { amOrPm = "PM"; } - var min = a.getMinutes(); - var sec = a.getSeconds(); + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); return year + '-' + month + '-' + day + '
    ' + drawnHour + ':' + min + amOrPm; } // diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index b94616bd7a..50891deb60 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -299,10 +299,14 @@ Item { // function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + var a = new Date(timestamp); var year = a.getFullYear(); - var month = a.getMonth(); - var day = a.getDate(); + var month = addLeadingZero(a.getMonth()); + var day = addLeadingZero(a.getDate()); var hour = a.getHours(); var drawnHour = hour; if (hour === 0) { @@ -310,14 +314,15 @@ Item { } else if (hour > 12) { drawnHour -= 12; } + drawnHour = addLeadingZero(drawnHour); var amOrPm = "AM"; if (hour >= 12) { amOrPm = "PM"; } - var min = a.getMinutes(); - var sec = a.getSeconds(); + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); return year + '-' + month + '-' + day + '
    ' + drawnHour + ':' + min + amOrPm; } From 69f869b339100aae670fadfff530c26b8554d44c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 11 Oct 2017 09:15:53 -0700 Subject: [PATCH 501/504] Remove unecessary blocking call --- interface/src/ui/overlays/Overlays.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 0280cf2038..b59bcdb9b2 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -741,13 +741,6 @@ void Overlays::sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& ev } OverlayID Overlays::getKeyboardFocusOverlay() { - if (QThread::currentThread() != thread()) { - OverlayID result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getKeyboardFocusOverlay", Q_RETURN_ARG(OverlayID, result)); - return result; - } - return qApp->getKeyboardFocusOverlay(); } From 480047548d5e76a0ccd6e9fe3142b36322b5011a Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 11 Oct 2017 18:04:29 -0700 Subject: [PATCH 502/504] exposing the Render interface to the overlay web3d --- interface/src/ui/overlays/Web3DOverlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 526890b9c1..8f9f84642f 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -245,6 +245,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); From 8994200d0115fb471de62e9d3c68b6958a2b48a4 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 12 Oct 2017 17:57:30 -0700 Subject: [PATCH 503/504] Trying to integrate the tool scripts as an app --- .../src/EntityTreeRenderer.cpp | 6 +- .../utilities/lib/configprop/ConfigSlider.qml | 72 ++++ .../lib/hifi-qml/controls-uit/Button.qml | 73 ++++ .../lib/hifi-qml/controls-uit/CheckBox.qml | 101 +++++ .../lib/hifi-qml/controls-uit/ComboBox.qml | 249 +++++++++++ .../hifi-qml/controls-uit/ConfigSlider.qml | 72 ++++ .../hifi-qml/controls-uit/ContentSection.qml | 138 ++++++ .../lib/hifi-qml/controls-uit/GlyphButton.qml | 78 ++++ .../hifi-qml/controls-uit/HorizontalRule.qml | 20 + .../controls-uit/HorizontalSpacer.qml | 21 + .../lib/hifi-qml/controls-uit/Label.qml | 22 + .../hifi-qml/controls-uit/QueuedButton.qml | 43 ++ .../lib/hifi-qml/controls-uit/RadioButton.qml | 71 +++ .../lib/hifi-qml/controls-uit/Separator.qml | 38 ++ .../lib/hifi-qml/controls-uit/Slider.qml | 98 +++++ .../lib/hifi-qml/controls-uit/SpinBox.qml | 117 +++++ .../lib/hifi-qml/controls-uit/Switch.qml | 156 +++++++ .../lib/hifi-qml/controls-uit/Table.qml | 179 ++++++++ .../hifi-qml/controls-uit/VerticalSpacer.qml | 21 + .../styles-uit/AnonymousProRegular.qml | 23 + .../lib/hifi-qml/styles-uit/ButtonLabel.qml | 18 + .../hifi-qml/styles-uit/FiraSansRegular.qml | 23 + .../hifi-qml/styles-uit/FiraSansSemiBold.qml | 23 + .../hifi-qml/styles-uit/FiraSansSemiBold.qmlc | Bin 0 -> 4097 bytes .../lib/hifi-qml/styles-uit/HiFiGlyphs.qml | 25 ++ .../lib/hifi-qml/styles-uit/HiFiGlyphs.qmlc | Bin 0 -> 4897 bytes .../lib/hifi-qml/styles-uit/HifiConstants.qml | 342 +++++++++++++++ .../hifi-qml/styles-uit/HifiConstants.qmlc | Bin 0 -> 51320 bytes .../lib/hifi-qml/styles-uit/IconButton.qml | 20 + .../lib/hifi-qml/styles-uit/InfoItem.qml | 19 + .../lib/hifi-qml/styles-uit/InputLabel.qml | 18 + .../lib/hifi-qml/styles-uit/ListItem.qml | 18 + .../lib/hifi-qml/styles-uit/Logs.qml | 18 + .../lib/hifi-qml/styles-uit/OverlayTitle.qml | 18 + .../lib/hifi-qml/styles-uit/RalewayBold.qml | 24 ++ .../lib/hifi-qml/styles-uit/RalewayLight.qml | 23 + .../hifi-qml/styles-uit/RalewayRegular.qml | 23 + .../hifi-qml/styles-uit/RalewaySemiBold.qml | 23 + .../hifi-qml/styles-uit/RalewaySemiBold.qmlc | Bin 0 -> 4081 bytes .../lib/hifi-qml/styles-uit/SectionName.qml | 19 + .../lib/hifi-qml/styles-uit/Separator.qml | 41 ++ .../lib/hifi-qml/styles-uit/Separator.qmlc | Bin 0 -> 6855 bytes .../lib/hifi-qml/styles-uit/ShortcutText.qml | 18 + .../lib/hifi-qml/styles-uit/TabName.qml | 19 + .../hifi-qml/styles-uit/TextFieldInput.qml | 18 + .../utilities/render/deferredLighting.qml | 405 ++++++++++-------- scripts/developer/utilities/render/luci.js | 98 +++++ 47 files changed, 2663 insertions(+), 188 deletions(-) create mode 100644 scripts/developer/utilities/lib/configprop/ConfigSlider.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HiFiGlyphs.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HiFiGlyphs.qmlc create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/IconButton.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/InfoItem.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/InputLabel.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/ListItem.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/Logs.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/OverlayTitle.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayBold.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayLight.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayRegular.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewaySemiBold.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewaySemiBold.qmlc create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/SectionName.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/Separator.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/Separator.qmlc create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml create mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml create mode 100644 scripts/developer/utilities/render/luci.js diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9e700b0efe..92a82908e2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -72,7 +72,11 @@ EntityRendererPointer EntityTreeRenderer::renderableForEntityId(const EntityItem render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& id) const { auto renderable = renderableForEntityId(id); - return renderable ? renderable->getRenderItemID() : render::Item::INVALID_ITEM_ID; + if (renderable) { + return renderable->getRenderItemID(); + } else { + return render::Item::INVALID_ITEM_ID; + } } int EntityTreeRenderer::_entitiesScriptEngineCount = 0; diff --git a/scripts/developer/utilities/lib/configprop/ConfigSlider.qml b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml new file mode 100644 index 0000000000..516193b81c --- /dev/null +++ b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml @@ -0,0 +1,72 @@ +// +// ConfigSlider.qml +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "../hifi-qml/styles-uit" +import "../hifi-qml/controls-uit" as HifiControls + + +Item { + id: root + width: 400 + height: 24 + property bool integral: false + property var config + property string property + property alias label: labelControl.text + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + Component.onCompleted: { + // Binding favors qml value, so set it first + sliderControl.value = root.config[root.property]; + bindingControl.when = true; + } + + HifiControls.Label { + id: labelControl + text: root.label + anchors.left: root.left + anchors.leftMargin: 8 + anchors.top: root.top + anchors.topMargin: 7 + } + + HifiControls.Label { + id: labelValue + text: sliderControl.value.toFixed(root.integral ? 0 : 2) + anchors.right: root.right + anchors.rightMargin: 8 + anchors.top: root.top + anchors.topMargin: 15 + } + + Binding { + id: bindingControl + target: root.config + property: root.property + value: sliderControl.value + when: false + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + width: root.width-130 + height: 20 + anchors.right: root.right + anchors.rightMargin: 8 + anchors.top: root.top + anchors.topMargin: 3 + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml new file mode 100644 index 0000000000..59f8a63238 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml @@ -0,0 +1,73 @@ +// +// Button.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Original.Button { + property int color: 0 + property int colorScheme: hifi.colorSchemes.light + + width: 120 + height: hifi.dimensions.controlLineHeight + + HifiConstants { id: hifi } + + style: ButtonStyle { + + background: Rectangle { + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + label: RalewayBold { + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + size: hifi.fontSizes.buttonLabel + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml new file mode 100644 index 0000000000..b279b7ca8d --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml @@ -0,0 +1,101 @@ +// +// CheckBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Original.CheckBox { + id: checkBox + + property int colorScheme: hifi.colorSchemes.light + property string color: hifi.colors.lightGrayText + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool isRedCheck: false + property int boxSize: 14 + property int boxRadius: 3 + property bool wrap: true; + readonly property int checkSize: Math.max(boxSize - 8, 10) + readonly property int checkRadius: 2 + activeFocusOnPress: true + + style: CheckBoxStyle { + indicator: Rectangle { + id: box + width: boxSize + height: boxSize + radius: boxRadius + border.width: 1 + border.color: pressed || hovered + ? hifi.colors.checkboxCheckedBorder + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + visible: pressed || hovered + anchors.centerIn: parent + id: innerBox + width: checkSize - 4 + height: width + radius: checkRadius + color: hifi.colors.checkboxCheckedBorder + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked + border.width: 2 + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + + Rectangle { + id: disabledOverlay + visible: !enabled + width: boxSize + height: boxSize + radius: boxRadius + border.width: 1 + border.color: hifi.colors.baseGrayHighlight + color: hifi.colors.baseGrayHighlight + opacity: 0.5 + } + } + + label: Label { + text: control.text + color: control.color + x: 2 + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight + enabled: checkBox.enabled + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml new file mode 100644 index 0000000000..d672fa6387 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml @@ -0,0 +1,249 @@ +// +// ComboBox.qml +// +// Created by Bradley Austin David on 27 Jan 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +FocusScope { + id: root + HifiConstants { id: hifi } + + property alias model: comboBox.model; + property alias editable: comboBox.editable + property alias comboBox: comboBox + readonly property alias currentText: comboBox.currentText; + property alias currentIndex: comboBox.currentIndex; + + property int dropdownHeight: 480 + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0) + + readonly property ComboBox control: comboBox + + property bool isDesktop: true + + signal accepted(); + + implicitHeight: comboBox.height; + focus: true + + Rectangle { + id: background + gradient: Gradient { + GradientStop { + position: 0.2 + color: popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) + } + GradientStop { + position: 1.0 + color: popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) + } + } + anchors.fill: parent + } + + SystemPalette { id: palette } + + ComboBox { + id: comboBox + anchors.fill: parent + visible: false + height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. + } + + FiraSansSemiBold { + id: textField + anchors { + left: parent.left + leftMargin: hifi.dimensions.textPadding + right: dropIcon.left + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.textFieldInput + text: comboBox.currentText + elide: Text.ElideRight + color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) + } + + Item { + id: dropIcon + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + height: background.height + width: height + Rectangle { + width: 1 + height: parent.height + anchors.top: parent.top + anchors.left: parent.left + color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray + } + HiFiGlyphs { + anchors { + top: parent.top + topMargin: -11 + horizontalCenter: parent.horizontalCenter + } + size: hifi.dimensions.spinnerSize + text: hifi.glyphs.caratDn + color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) + } + } + + MouseArea { + id: controlHover + hoverEnabled: true + anchors.fill: parent + onClicked: toggleList(); + } + + function toggleList() { + if (popup.visible) { + hideList(); + } else { + showList(); + } + } + + function showList() { + var r; + if (isDesktop) { + r = desktop.mapFromItem(root, 0, 0, root.width, root.height); + } else { + r = mapFromItem(root, 0, 0, root.width, root.height); + } + var y = r.y + r.height; + var bottom = y + scrollView.height; + var height = isDesktop ? desktop.height : tabletRoot.height; + if (bottom > height) { + y -= bottom - height + 8; + } + scrollView.x = r.x; + scrollView.y = y; + popup.visible = true; + popup.forceActiveFocus(); + listView.currentIndex = root.currentIndex; + scrollView.hoverEnabled = true; + } + + function hideList() { + popup.visible = false; + scrollView.hoverEnabled = false; + root.accepted(); + } + + FocusScope { + id: popup + parent: isDesktop ? desktop : root + anchors.fill: parent + z: isDesktop ? desktop.zLevels.menu : 12 + visible: false + focus: true + + MouseArea { + anchors.fill: parent + onClicked: hideList(); + } + + function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; } + function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; } + function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); } + function selectSpecificItem(index) { root.currentIndex = index; hideList(); } + + Keys.onUpPressed: previousItem(); + Keys.onDownPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + Keys.onEscapePressed: hideList(); + + ScrollView { + id: scrollView + height: root.dropdownHeight + width: root.width + 4 + property bool hoverEnabled: false; + + style: ScrollViewStyle { + decrementControl: Item { + visible: false + } + incrementControl: Item { + visible: false + } + scrollBarBackground: Rectangle{ + implicitWidth: 20 + color: hifi.colors.baseGray + } + + handle: + Rectangle { + implicitWidth: 16 + anchors.left: parent.left + anchors.leftMargin: 3 + anchors.top: parent.top + anchors.bottom: parent.bottom + radius: 6 + color: hifi.colors.lightGrayText + } + } + + ListView { + id: listView + height: textField.height * count * 1.4 + model: root.model + delegate: Rectangle { + width: root.width + 4 + height: popupText.implicitHeight * 1.4 + color: (listView.currentIndex === index) ? hifi.colors.primaryHighlight : + (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + FiraSansSemiBold { + anchors.left: parent.left + anchors.leftMargin: hifi.dimensions.textPadding + anchors.verticalCenter: parent.verticalCenter + id: popupText + text: listView.model[index] ? listView.model[index] : (listView.model.get && listView.model.get(index).text ? listView.model.get(index).text : "") + size: hifi.fontSizes.textFieldInput + color: hifi.colors.baseGray + } + MouseArea { + id: popupHover + anchors.fill: parent; + hoverEnabled: scrollView.hoverEnabled; + onEntered: listView.currentIndex = index; + onClicked: popup.selectSpecificItem(index); + } + } + } + } + } + + HifiControls.Label { + id: comboBoxLabel + text: root.label + colorScheme: root.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } + + Component.onCompleted: { + isDesktop = (typeof desktop !== "undefined"); + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml new file mode 100644 index 0000000000..9c38b66ed8 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml @@ -0,0 +1,72 @@ +// +// ConfigSlider.qml +// examples/utilities/tools/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Item { + id: root + width: 400 + height: 24 + property bool integral: false + property var config + property string property + property alias label: labelControl.text + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + Component.onCompleted: { + // Binding favors qml value, so set it first + sliderControl.value = root.config[root.property]; + bindingControl.when = true; + } + + HifiControls.Label { + id: labelControl + text: root.label + anchors.left: root.left + anchors.leftMargin: 8 + anchors.top: root.top + anchors.topMargin: 7 + } + + HifiControls.Label { + id: labelValue + text: sliderControl.value.toFixed(root.integral ? 0 : 2) + anchors.right: root.right + anchors.rightMargin: 8 + anchors.top: root.top + anchors.topMargin: 15 + } + + Binding { + id: bindingControl + target: root.config + property: root.property + value: sliderControl.value + when: false + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + width: root.width-130 + height: 20 + anchors.right: root.right + anchors.rightMargin: 8 + anchors.top: root.top + anchors.topMargin: 3 + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml new file mode 100644 index 0000000000..47a13e9262 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml @@ -0,0 +1,138 @@ +// +// ContentSection.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../styles-uit" + +Column { + property string name: "Content Section" + property bool isFirst: false + property bool isCollapsible: false // Set at creation. + property bool isCollapsed: false + + spacing: 0 // Defer spacing decisions to individual controls. + + anchors { + left: parent.left + leftMargin: hifi.dimensions.contentMargin.x + right: parent.right + rightMargin: hifi.dimensions.contentMargin.x + } + + function toggleCollapsed() { + if (isCollapsible) { + isCollapsed = !isCollapsed; + for (var i = 1; i < children.length; i++) { + children[i].visible = !isCollapsed; + } + } + } + + Item { + id: sectionName + anchors.left: parent.left + anchors.right: parent.right + height: leadingSpace.height + topBar.height + heading.height + bottomBar.height + + Item { + id: leadingSpace + width: 1 + height: isFirst ? 7 : 0 + anchors.top: parent.top + } + + Item { + id: topBar + visible: !isFirst + height: visible ? 2 : 0 + anchors.top: leadingSpace.bottom + + Rectangle { + id: shadow + width: frame.width + height: 1 + color: hifi.colors.baseGrayShadow + x: -hifi.dimensions.contentMargin.x + } + + Rectangle { + width: frame.width + height: 1 + color: hifi.colors.baseGrayHighlight + x: -hifi.dimensions.contentMargin.x + anchors.top: shadow.bottom + } + } + + Item { + id: heading + anchors { + left: parent.left + right: parent.right + top: topBar.bottom + } + height: isCollapsible ? 36 : 28 + + RalewayRegular { + id: title + anchors { + left: parent.left + top: parent.top + topMargin: 12 + } + size: hifi.fontSizes.sectionName + font.capitalization: Font.AllUppercase + text: name + color: hifi.colors.lightGrayText + } + + HiFiGlyphs { + anchors { + top: title.top + topMargin: -9 + right: parent.right + rightMargin: -4 + } + size: hifi.fontSizes.disclosureButton + text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse + color: hifi.colors.lightGrayText + visible: isCollapsible + } + + MouseArea { + // Events are propogated so that any active control is defocused. + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + toggleCollapsed(); + mouse.accepted = false; + } + } + } + + LinearGradient { + id: bottomBar + visible: desktop.gradientsSupported && isCollapsible + width: frame.width + height: visible ? 4 : 0 + x: -hifi.dimensions.contentMargin.x + anchors.top: heading.bottom + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. + } + cached: true + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml new file mode 100644 index 0000000000..ac353b5a52 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml @@ -0,0 +1,78 @@ +// +// GlyphButton.qml +// +// Created by Clement on 3/7/16 +// Copyright 2016 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 +// + + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Original.Button { + property int color: 0 + property int colorScheme: hifi.colorSchemes.light + property string glyph: "" + property int size: 32 + + width: 120 + height: 28 + + style: ButtonStyle { + + background: Rectangle { + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + label: HiFiGlyphs { + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors { + // Tweak horizontal alignment so that it looks right. + left: parent.left + leftMargin: -0.5 + } + text: control.glyph + size: control.size + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml new file mode 100644 index 0000000000..425500f1d4 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml @@ -0,0 +1,20 @@ +// +// HorizontalRule.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: hifi.colors.lightGray +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml new file mode 100644 index 0000000000..545154ab44 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml @@ -0,0 +1,21 @@ +// +// HorizontalSpacer.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 + +import "../styles-uit" + +Item { + id: root + property alias size: root.width + + width: hifi.dimensions.controlInterlineHeight + height: 1 // Must be non-zero +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml new file mode 100644 index 0000000000..330d74fa14 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml @@ -0,0 +1,22 @@ +// +// Label.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 + +import "../styles-uit" + +RalewaySemiBold { + HifiConstants { id: hifi } + property int colorScheme: hifi.colorSchemes.light + + size: hifi.fontSizes.inputLabel + color: enabled ? (colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGray : hifi.colors.lightGrayText) + : (colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight); +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml new file mode 100644 index 0000000000..36ffbe582f --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml @@ -0,0 +1,43 @@ +// +// QueuedButton.qml +// -- original Button.qml + signal timer workaround --ht +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "." as HifiControls + +HifiControls.Button { + // FIXME: THIS WORKAROUND MIGRATED/CONSOLIDATED FROM RUNNINGSCRIPTS.QML + + // For some reason trigginer an API that enters + // an internal event loop directly from the button clicked + // trigger below causes the appliction to behave oddly. + // Most likely because the button onClicked handling is never + // completed until the function returns. + // FIXME find a better way of handling the input dialogs that + // doesn't trigger this. + + // NOTE: dialogs that need to use this workaround can connect via + // onQueuedClicked: ... + // instead of: + // onClicked: ... + + signal clickedQueued() + Timer { + id: fromTimer + interval: 5 + repeat: false + running: false + onTriggered: clickedQueued() + } + onClicked: fromTimer.running = true +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml new file mode 100644 index 0000000000..ab11ec68b1 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml @@ -0,0 +1,71 @@ +// +// RadioButton.qml +// +// Created by Cain Kilgore on 20th July 2017 +// Copyright 2017 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Original.RadioButton { + id: radioButton + HifiConstants { id: hifi } + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + readonly property int boxSize: 14 + readonly property int boxRadius: 3 + readonly property int checkSize: 10 + readonly property int checkRadius: 2 + + style: RadioButtonStyle { + indicator: Rectangle { + id: box + width: boxSize + height: boxSize + radius: 7 + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: 7 + anchors.centerIn: parent + color: "#00B4EF" + border.width: 1 + border.color: "#36CDFF" + visible: checked && !pressed || !checked && pressed + } + } + + label: RalewaySemiBold { + text: control.text + size: hifi.fontSizes.inputLabel + color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText + x: radioButton.boxSize / 2 + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml new file mode 100644 index 0000000000..5a775221f6 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml @@ -0,0 +1,38 @@ +// +// Separator.qml +// +// Created by Zach Fox on 2017-06-06 +// Copyright 2017 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 +// + +import QtQuick 2.5 +import "../styles-uit" + +Item { + // Size + height: 2; + Rectangle { + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: height; + // Style + color: hifi.colors.baseGrayShadow; + } + Rectangle { + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + // Style + color: hifi.colors.baseGrayHighlight; + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml new file mode 100644 index 0000000000..89bae9bcde --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml @@ -0,0 +1,98 @@ +// +// Slider.qml +// +// Created by David Rowe on 27 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Slider { + id: slider + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0) + + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. + y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 + + style: SliderStyle { + + groove: Rectangle { + implicitWidth: 50 + implicitHeight: hifi.dimensions.sliderGrooveHeight + radius: height / 2 + color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark + + Rectangle { + width: parent.height - 2 + height: slider.width * (slider.value - slider.minimumValue) / (slider.maximumValue - slider.minimumValue) - 1 + radius: height / 2 + anchors { + top: parent.top + topMargin: width + 1 + left: parent.left + leftMargin: 1 + } + transformOrigin: Item.TopLeft + rotation: -90 + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.blueAccent } + GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } + } + } + } + + handle: Rectangle { + implicitWidth: hifi.dimensions.sliderHandleSize + implicitHeight: hifi.dimensions.sliderHandleSize + radius: height / 2 + border.width: 1 + border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark + gradient: Gradient { + GradientStop { + position: 0.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) + : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) + : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) + } + } + + Rectangle { + height: parent.height - 2 + width: height + radius: height / 2 + anchors.centerIn: parent + color: hifi.colors.transparent + border.width: 1 + border.color: hifi.colors.black + } + } + } + + HifiControls.Label { + id: sliderLabel + text: slider.label + colorScheme: slider.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 2 + visible: label != "" + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml new file mode 100644 index 0000000000..a1237d4bc7 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml @@ -0,0 +1,117 @@ +// +// SpinBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +SpinBox { + id: spinBox + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property string labelInside: "" + property color colorLabelInside: hifi.colors.white + property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) + + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + font.family: firaSansSemiBold.name + font.pixelSize: hifi.fontSizes.textFieldInput + height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. + + y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 + + style: SpinBoxStyle { + id: spinStyle + background: Rectangle { + color: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) + border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight + border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 + } + + textColor: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + + horizontalAlignment: Qt.AlignLeft + padding.left: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding + padding.right: hifi.dimensions.spinnerSize + padding.top: 0 + + incrementControl: HiFiGlyphs { + id: incrementButton + text: hifi.glyphs.caratUp + x: 10 + y: 1 + size: hifi.dimensions.spinnerSize + color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + + decrementControl: HiFiGlyphs { + text: hifi.glyphs.caratDn + x: 10 + y: -1 + size: hifi.dimensions.spinnerSize + color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + } + + HifiControls.Label { + id: spinBoxLabel + text: spinBox.label + colorScheme: spinBox.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } + + HifiControls.Label { + id: spinBoxLabelInside + text: spinBox.labelInside + anchors.left: parent.left + anchors.leftMargin: 10 + font.bold: true + anchors.verticalCenter: parent.verticalCenter + color: spinBox.colorLabelInside + visible: spinBox.labelInside != "" + } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onWheel: { + if(spinBox.activeFocus) + wheel.accepted = false + else + wheel.accepted = true + } + onPressed: { + mouse.accepted = false + } + onReleased: { + mouse.accepted = false + } + onClicked: { + mouse.accepted = false + } + onDoubleClicked: { + mouse.accepted = false + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml new file mode 100644 index 0000000000..d54f986717 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml @@ -0,0 +1,156 @@ +// +// Switch.qml +// +// Created by Zach Fox on 2017-06-06 +// Copyright 2017 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Item { + id: rootSwitch; + + property int colorScheme: hifi.colorSchemes.light; + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light; + property int switchWidth: 70; + readonly property int switchRadius: height/2; + property string labelTextOff: ""; + property string labelGlyphOffText: ""; + property int labelGlyphOffSize: 32; + property string labelTextOn: ""; + property string labelGlyphOnText: ""; + property int labelGlyphOnSize: 32; + property alias checked: originalSwitch.checked; + signal onCheckedChanged; + signal clicked; + + Original.Switch { + id: originalSwitch; + activeFocusOnPress: true; + anchors.top: rootSwitch.top; + anchors.left: rootSwitch.left; + anchors.leftMargin: rootSwitch.width/2 - rootSwitch.switchWidth/2; + onCheckedChanged: rootSwitch.onCheckedChanged(); + onClicked: rootSwitch.clicked(); + + style: SwitchStyle { + + padding { + top: 3; + left: 3; + right: 3; + bottom: 3; + } + + groove: Rectangle { + color: "#252525"; + implicitWidth: rootSwitch.switchWidth; + implicitHeight: rootSwitch.height; + radius: rootSwitch.switchRadius; + } + + handle: Rectangle { + id: switchHandle; + implicitWidth: rootSwitch.height - padding.top - padding.bottom; + implicitHeight: implicitWidth; + radius: implicitWidth/2; + border.color: hifi.colors.lightGrayText; + color: hifi.colors.lightGray; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = hifi.colors.lightGray; + } + } + } + } + + // OFF Label + Item { + anchors.right: originalSwitch.left; + anchors.rightMargin: 10; + anchors.top: rootSwitch.top; + height: rootSwitch.height; + + RalewaySemiBold { + id: labelOff; + text: labelTextOff; + size: hifi.fontSizes.inputLabel; + color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF"; + anchors.top: parent.top; + anchors.right: parent.right; + width: paintedWidth; + height: parent.height; + verticalAlignment: Text.AlignVCenter; + } + + HiFiGlyphs { + id: labelGlyphOff; + text: labelGlyphOffText; + size: labelGlyphOffSize; + color: labelOff.color; + anchors.top: parent.top; + anchors.topMargin: 2; + anchors.right: labelOff.left; + anchors.rightMargin: 4; + } + + MouseArea { + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: labelGlyphOff.left; + anchors.right: labelOff.right; + onClicked: { + originalSwitch.checked = false; + } + } + } + + // ON Label + Item { + anchors.left: originalSwitch.right; + anchors.leftMargin: 10; + anchors.top: rootSwitch.top; + height: rootSwitch.height; + + RalewaySemiBold { + id: labelOn; + text: labelTextOn; + size: hifi.fontSizes.inputLabel; + color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText; + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: parent.height; + verticalAlignment: Text.AlignVCenter; + } + + HiFiGlyphs { + id: labelGlyphOn; + text: labelGlyphOnText; + size: labelGlyphOnSize; + color: labelOn.color; + anchors.top: parent.top; + anchors.left: labelOn.right; + } + + MouseArea { + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: labelOn.left; + anchors.right: labelGlyphOn.right; + onClicked: { + originalSwitch.checked = true; + } + } + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml new file mode 100644 index 0000000000..11d1920f95 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml @@ -0,0 +1,179 @@ +// +// Table.qml +// +// Created by David Rowe on 18 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +TableView { + id: tableView + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool expandSelectedRow: false + property bool centerHeaderText: false + + model: ListModel { } + + headerVisible: false + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + RalewayRegular { + id: titleText + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + } + + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: hifi.colors.darkGray + opacity: 0.6; + size: hifi.fontSizes.tableHeadingIcon + anchors { + left: titleText.right + leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 5 : 0) + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: titleText.verticalCenter + } + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } + + // Use rectangle to draw border with rounded corners. + frameVisible: false + Rectangle { + color: "#00000000" + anchors { fill: parent; margins: -2 } + border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + border.width: 2 + } + anchors.margins: 2 // Shrink TableView to lie within border. + + backgroundVisible: true + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + + style: TableViewStyle { + // Needed in order for rows to keep displaying rows after end of table entries. + backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + + padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 + + handle: Item { + id: scrollbarHandle + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors { + fill: parent + topMargin: 3 + bottomMargin: 3 // "" + leftMargin: 1 // Move it right + rightMargin: -1 // "" + } + radius: hifi.dimensions.scrollbarHandleWidth/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } + + scrollBarBackground: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { + fill: parent + margins: -1 // Expand + topMargin: -1 + } + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight : hifi.colors.tableScrollBackgroundDark + + // Extend header color above scrollbar background + Rectangle { + anchors { + top: parent.top + topMargin: -hifi.dimensions.tableHeaderHeight + left: parent.left + right: parent.right + } + height: hifi.dimensions.tableHeaderHeight + color: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + visible: headerVisible + } + Rectangle { + // Extend header bottom border + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: 1 + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: headerVisible + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + + rowDelegate: Rectangle { + height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight + color: styleData.selected + ? hifi.colors.primaryHighlight + : tableView.isLightColorScheme + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml new file mode 100644 index 0000000000..2df65f1002 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml @@ -0,0 +1,21 @@ +// +// VerticalSpacer.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 + +import "../styles-uit" + +Item { + id: root + property alias size: root.height + + width: 1 // Must be non-zero + height: hifi.dimensions.controlInterlineHeight +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml new file mode 100644 index 0000000000..789689973b --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml @@ -0,0 +1,23 @@ +// +// AnonymousProRegular.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Text { + id: root + FontLoader { id: anonymousProRegular; source: "../../fonts/AnonymousPro-Regular.ttf"; } + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: anonymousProRegular.name +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml new file mode 100644 index 0000000000..aade5fb439 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml @@ -0,0 +1,18 @@ +// +// ButtonLabel.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import "." + +RalewayBold { + font.pixelSize: hifi.fontSizes.buttonLabel +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml new file mode 100644 index 0000000000..4ae0826772 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml @@ -0,0 +1,23 @@ +// +// FiraSansRegular.qml +// +// Created by David Rowe on 12 May 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Text { + id: root + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: firaSansRegular.name +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml new file mode 100644 index 0000000000..b3f3324090 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml @@ -0,0 +1,23 @@ +// +// FiraSansSemiBold.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Text { + id: root + FontLoader { id: firaSansSemiBold; source: pathToFonts + "fonts/FiraSans-SemiBold.ttf"; } + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: firaSansSemiBold.name +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc new file mode 100644 index 0000000000000000000000000000000000000000..62898a891eb53784c3db5fba53a950848e4ef57a GIT binary patch literal 4097 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 literal 0 HcmV?d00001 diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml new file mode 100644 index 0000000000..4a26d11128 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml @@ -0,0 +1,342 @@ +// +// HiFiConstants.qml +// +// Created by Bradley Austin Davis on 28 Apr 2015 +// Copyright 2015 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 +// + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +Item { + readonly property alias colors: colors + readonly property alias colorSchemes: colorSchemes + readonly property alias dimensions: dimensions + readonly property alias fontSizes: fontSizes + readonly property alias glyphs: glyphs + readonly property alias icons: icons + readonly property alias buttons: buttons + readonly property alias effects: effects + + function glyphForIcon(icon) { + // Translates icon enum to glyph char. + var glyph; + switch (icon) { + case hifi.icons.information: + glyph = hifi.glyphs.info; + break; + case hifi.icons.question: + glyph = hifi.glyphs.question; + break; + case hifi.icons.warning: + glyph = hifi.glyphs.alert; + break; + case hifi.icons.critical: + glyph = hifi.glyphs.error; + break; + case hifi.icons.placemark: + glyph = hifi.glyphs.placemark; + break; + default: + glyph = hifi.glyphs.noIcon; + } + return glyph; + } + + Item { + id: colors + + // Base colors + readonly property color baseGray: "#393939" + readonly property color darkGray: "#121212" + readonly property color baseGrayShadow: "#252525" + readonly property color baseGrayHighlight: "#575757" + readonly property color lightGray: "#6a6a6a" + readonly property color lightGrayText: "#afafaf" + readonly property color faintGray: "#e3e3e3" + readonly property color primaryHighlight: "#00b4ef" + readonly property color blueHighlight: "#00b4ef" + readonly property color blueAccent: "#0093C5" + readonly property color redHighlight: "#EA4C5F" + readonly property color redAccent: "#C62147" + readonly property color greenHighlight: "#1ac567" + readonly property color greenShadow: "#359D85" + readonly property color orangeHighlight: "#FFC49C" + readonly property color orangeAccent: "#FF6309" + readonly property color indigoHighlight: "#C0D2FF" + readonly property color indigoAccent: "#9495FF" + readonly property color magentaHighlight: "#EF93D1" + readonly property color magentaAccent: "#A2277C" + readonly property color checkboxCheckedRed: "#FF0000" + readonly property color checkboxCheckedBorderRed: "#D00000" + readonly property color lightBlueHighlight: "#d6f6ff" + + // Semitransparent + readonly property color darkGray30: "#4d121212" + readonly property color darkGray0: "#00121212" + readonly property color baseGrayShadow60: "#99252525" + readonly property color baseGrayShadow50: "#80252525" + readonly property color baseGrayShadow25: "#40252525" + readonly property color baseGrayHighlight40: "#66575757" + readonly property color baseGrayHighlight15: "#26575757" + readonly property color lightGray50: "#806a6a6a" + readonly property color lightGrayText80: "#ccafafaf" + readonly property color faintGray80: "#cce3e3e3" + readonly property color faintGray50: "#80e3e3e3" + + // Other colors + readonly property color white: "#ffffff" + readonly property color gray: "#808080" + readonly property color black: "#000000" + readonly property color locked: "#252525" + // Semitransparent + readonly property color white50: "#80ffffff" + readonly property color white30: "#4dffffff" + readonly property color white25: "#40ffffff" + readonly property color transparent: "#00ffffff" + + // Control specific colors + readonly property color tableRowLightOdd: "#fafafa" + readonly property color tableRowLightEven: "#eeeeee" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: "#DDDDDD" + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightOdd + readonly property color tableScrollBackgroundDark: "#323232" + readonly property color checkboxLightStart: "#ffffff" + readonly property color checkboxLightFinish: "#afafaf" + readonly property color checkboxDarkStart: "#7d7d7d" + readonly property color checkboxDarkFinish: "#6b6a6b" + readonly property color checkboxChecked: primaryHighlight + readonly property color checkboxCheckedBorder: "#36cdff" + readonly property color sliderGutterLight: "#d4d4d4" + readonly property color sliderGutterDark: "#252525" + readonly property color sliderBorderLight: "#afafaf" + readonly property color sliderBorderDark: "#7d7d7d" + readonly property color sliderLightStart: "#ffffff" + readonly property color sliderLightFinish: "#afafaf" + readonly property color sliderDarkStart: "#7d7d7d" + readonly property color sliderDarkFinish: "#6b6a6b" + readonly property color dropDownPressedLight: "#d4d4d4" + readonly property color dropDownPressedDark: "#afafaf" + readonly property color dropDownLightStart: "#ffffff" + readonly property color dropDownLightFinish: "#afafaf" + readonly property color dropDownDarkStart: "#7d7d7d" + readonly property color dropDownDarkFinish: "#6b6a6b" + readonly property color textFieldLightBackground: "#d4d4d4" + readonly property color tabBackgroundDark: "#252525" + readonly property color tabBackgroundLight: "#d4d4d4" + } + + Item { + id: colorSchemes + readonly property int light: 0 + readonly property int dark: 1 + } + + Item { + id: dimensions + readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 + readonly property real borderRadius: largeScreen ? 7.5 : 5.0 + readonly property real borderWidth: largeScreen ? 2 : 1 + readonly property vector2d contentMargin: Qt.vector2d(21, 21) + readonly property vector2d contentSpacing: Qt.vector2d(11, 14) + readonly property real labelPadding: 40 + readonly property real textPadding: 8 + readonly property real sliderHandleSize: 18 + readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 + readonly property real spinnerSize: 50 + readonly property real tablePadding: 12 + readonly property real tableRowHeight: largeScreen ? 26 : 23 + readonly property real tableHeaderHeight: 29 + readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) + readonly property real modalDialogTitleHeight: 40 + readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor + readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight + readonly property vector2d menuPadding: Qt.vector2d(14, 102) + readonly property real scrollbarBackgroundWidth: 18 + readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 + readonly property real tabletMenuHeader: 90 + } + + Item { + id: fontSizes // In pixels + readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14 + readonly property real tabName: dimensions.largeScreen ? 12 : 10 + readonly property real sectionName: dimensions.largeScreen ? 12 : 10 + readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 + readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 + readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 + readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33 + readonly property real tableText: dimensions.largeScreen ? 15 : 12 + readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9 + readonly property real iconButton: dimensions.largeScreen ? 13 : 9 + readonly property real listItem: dimensions.largeScreen ? 15 : 11 + readonly property real tabularData: dimensions.largeScreen ? 15 : 11 + readonly property real logs: dimensions.largeScreen ? 16 : 12 + readonly property real code: dimensions.largeScreen ? 16 : 12 + readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 + readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16 + readonly property real menuItem: dimensions.largeScreen ? 15 : 11 + readonly property real shortcutText: dimensions.largeScreen ? 13 : 9 + readonly property real carat: dimensions.largeScreen ? 38 : 30 + readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 + } + + Item { + id: icons + // Values per OffscreenUi::Icon + readonly property int none: 0 + readonly property int question: 1 + readonly property int information: 2 + readonly property int warning: 3 + readonly property int critical: 4 + readonly property int placemark: 5 + } + + Item { + id: buttons + readonly property int white: 0 + readonly property int blue: 1 + readonly property int red: 2 + readonly property int black: 3 + readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white ] + readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434" ] + readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black ] + readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black] ] + readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] + readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] + readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] + readonly property int radius: 5 + } + + QtObject { + id: effects + readonly property int fadeInDuration: 300 + } + Item { + id: glyphs + readonly property string noIcon: "" + readonly property string hmd: "b" + readonly property string screen: "c" + readonly property string keyboard: "d" + readonly property string handControllers: "e" + readonly property string headphonesMic: "f" + readonly property string gamepad: "g" + readonly property string headphones: "h" + readonly property string mic: "i" + readonly property string upload: "j" + readonly property string script: "k" + readonly property string text: "l" + readonly property string cube: "m" + readonly property string sphere: "n" + readonly property string zone: "o" + readonly property string light: "p" + readonly property string web: "q" + readonly property string web2: "r" + readonly property string edit: "s" + readonly property string market: "t" + readonly property string directory: "u" + readonly property string menu: "v" + readonly property string close: "w" + readonly property string closeInverted: "x" + readonly property string pin: "y" + readonly property string pinInverted: "z" + readonly property string resizeHandle: "A" + readonly property string disclosureExpand: "B" + readonly property string reloadSmall: "a" + readonly property string closeSmall: "C" + readonly property string forward: "D" + readonly property string backward: "E" + readonly property string reload: "F" + readonly property string unmuted: "G" + readonly property string muted: "H" + readonly property string minimize: "I" + readonly property string maximize: "J" + readonly property string maximizeInverted: "K" + readonly property string disclosureButtonExpand: "L" + readonly property string disclosureButtonCollapse: "M" + readonly property string scriptStop: "N" + readonly property string scriptReload: "O" + readonly property string scriptRun: "P" + readonly property string scriptNew: "Q" + readonly property string hifiForum: "2" + readonly property string hifiLogoSmall: "S" + readonly property string avatar1: "T" + readonly property string placemark: "U" + readonly property string box: "V" + readonly property string community: "0" + readonly property string grabHandle: "X" + readonly property string search: "Y" + readonly property string disclosureCollapse: "Z" + readonly property string scriptUpload: "R" + readonly property string code: "W" + readonly property string avatar: "<" + readonly property string arrowsH: ":" + readonly property string arrowsV: ";" + readonly property string arrows: "`" + readonly property string compress: "!" + readonly property string expand: "\"" + readonly property string placemark1: "#" + readonly property string circle: "$" + readonly property string handPointer: "9" + readonly property string plusSquareO: "%" + readonly property string sliders: "&" + readonly property string square: "'" + readonly property string alignCenter: "8" + readonly property string alignJustify: ")" + readonly property string alignLeft: "*" + readonly property string alignRight: "^" + readonly property string bars: "7" + readonly property string circleSlash: "," + readonly property string sync: "()" + readonly property string key: "-" + readonly property string link: "." + readonly property string location: "/" + readonly property string caratR: "3" + readonly property string caratL: "4" + readonly property string caratDn: "5" + readonly property string caratUp: "6" + readonly property string folderLg: ">" + readonly property string folderSm: "?" + readonly property string levelUp: "1" + readonly property string info: "[" + readonly property string question: "]" + readonly property string alert: "+" + readonly property string home: "_" + readonly property string error: "=" + readonly property string settings: "@" + readonly property string trash: "{" + readonly property string objectGroup: "\ue000" + readonly property string cm: "}" + readonly property string msvg79: "~" + readonly property string deg: "\\" + readonly property string px: "|" + readonly property string editPencil: "\ue00d" + readonly property string vol_0: "\ue00e" + readonly property string vol_1: "\ue00f" + readonly property string vol_2: "\ue010" + readonly property string vol_3: "\ue011" + readonly property string vol_4: "\ue012" + readonly property string vol_x_0: "\ue013" + readonly property string vol_x_1: "\ue014" + readonly property string vol_x_2: "\ue015" + readonly property string vol_x_3: "\ue016" + readonly property string vol_x_4: "\ue017" + readonly property string source: "\ue01c" + readonly property string playback_play: "\ue01d" + readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" + } +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc new file mode 100644 index 0000000000000000000000000000000000000000..686f84ad173d629beb11885bdba32232ee46d480 GIT binary patch literal 51320 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 literal 0 HcmV?d00001 diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml new file mode 100644 index 0000000000..a3ab351870 --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml @@ -0,0 +1,18 @@ +// +// ShortcutText.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import "." + +RalewayLight { + font.pixelSize: hifi.fontSizes.shortcutText +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml new file mode 100644 index 0000000000..eb4e790e7e --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml @@ -0,0 +1,19 @@ +// +// TabName.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.tabName + font.capitalization: Font.AllUppercase +} diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml new file mode 100644 index 0000000000..010b4d03ad --- /dev/null +++ b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml @@ -0,0 +1,18 @@ +// +// TextFieldInput.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import "." + +FiraSansSemiBold { + font.pixelSize: hifi.fontSizes.textFieldInput +} diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 60929d75c0..1cf88ec5ac 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -7,215 +7,246 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 1.4 -import "configSlider" +import QtQuick.Layouts 1.3 -Column { - spacing: 8 - property var mainViewTask: Render.getConfig("RenderMainView") - - Row { - spacing: 8 - Column { - spacing: 10 - Repeater { - model: [ - "Unlit:LightingModel:enableUnlit", - "Emissive:LightingModel:enableEmissive", - "Lightmap:LightingModel:enableLightmap", - "Background:LightingModel:enableBackground", - "ssao:AmbientOcclusion:enabled", - "Textures:LightingModel:enableMaterialTexturing" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } +import "../lib/hifi-qml/styles-uit" +import "../lib/hifi-qml/controls-uit" as HifiControls +import "../lib/configprop" +Rectangle { + id: render; + HifiConstants { id: hifi;} + color: hifi.colors.baseGray; + property var mainViewTask: Render.getConfig("RenderMainView") - Column { - spacing: 10 - Repeater { - model: [ - "Obscurance:LightingModel:enableObscurance", - "Scattering:LightingModel:enableScattering", - "Diffuse:LightingModel:enableDiffuse", - "Specular:LightingModel:enableSpecular", - "Albedo:LightingModel:enableAlbedo", - "Wireframe:LightingModel:enableWireframe" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } - - Column { - spacing: 10 - Repeater { - model: [ - "Ambient:LightingModel:enableAmbientLight", - "Directional:LightingModel:enableDirectionalLight", - "Point:LightingModel:enablePointLight", - "Spot:LightingModel:enableSpotLight", - "Light Contour:LightingModel:showLightContour", - "Zone Stack:DrawZoneStack:enabled", - "Shadow:RenderShadowTask:enabled" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } - } Column { - spacing: 10 - Repeater { - model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: mainViewTask.getConfig(modelData.split(":")[1]) - property: modelData.split(":")[2] - max: modelData.split(":")[3] - min: modelData.split(":")[4] + spacing: 10 + + Row { + + spacing: 20 + padding: 10 + Column { + spacing: 10 + Repeater { + model: [ + "Unlit:LightingModel:enableUnlit", + "Emissive:LightingModel:enableEmissive", + "Lightmap:LightingModel:enableLightmap", + "Background:LightingModel:enableBackground", + "ssao:AmbientOcclusion:enabled", + "Textures:LightingModel:enableMaterialTexturing" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + + Column { + spacing: 10 + Repeater { + model: [ + "Obscurance:LightingModel:enableObscurance", + "Scattering:LightingModel:enableScattering", + "Diffuse:LightingModel:enableDiffuse", + "Specular:LightingModel:enableSpecular", + "Albedo:LightingModel:enableAlbedo", + "Wireframe:LightingModel:enableWireframe" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + Column { + spacing: 10 + Repeater { + model: [ + "Ambient:LightingModel:enableAmbientLight", + "Directional:LightingModel:enableDirectionalLight", + "Point:LightingModel:enablePointLight", + "Spot:LightingModel:enableSpotLight", + "Light Contour:LightingModel:showLightContour", + "Zone Stack:DrawZoneStack:enabled", + "Shadow:RenderShadowTask:enabled" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } } } + Separator {} + Column { + spacing: 10 + Repeater { + model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: render.mainViewTask.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: modelData.split(":")[4] + } + } + Row { + HifiControls.Label { + text: "Tone Mapping Curve" + anchors.left: root.left + } + + HifiControls.ComboBox { + anchors.right: root.right + currentIndex: 1 + model: ListModel { + id: cbItems + ListElement { text: "RGB"; color: "Yellow" } + ListElement { text: "SRGB"; color: "Green" } + ListElement { text: "Reinhard"; color: "Yellow" } + ListElement { text: "Filmic"; color: "White" } + } + width: 200 + onCurrentIndexChanged: { render.mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex } + } + } + } Row { - Label { - text: "Tone Mapping Curve" + id: framebuffer + spacing: 10 + height: 24 + + HifiControls.Label { + text: "Debug Framebuffer" + height: 24 anchors.left: root.left } + + property var config: render.mainViewTask.getConfig("DebugDeferredBuffer") - ComboBox { + function setDebugMode(mode) { + framebuffer.config.enabled = (mode != 0); + framebuffer.config.mode = mode; + } + + HifiControls.ComboBox { + height: 24 anchors.right: root.right - currentIndex: 1 + currentIndex: 0 model: ListModel { - id: cbItems - ListElement { text: "RGB"; color: "Yellow" } - ListElement { text: "SRGB"; color: "Green" } - ListElement { text: "Reinhard"; color: "Yellow" } - ListElement { text: "Filmic"; color: "White" } + id: cbItemsFramebuffer + ListElement { text: "Off"; color: "Yellow" } + ListElement { text: "Depth"; color: "Green" } + ListElement { text: "Albedo"; color: "Yellow" } + ListElement { text: "Normal"; color: "White" } + ListElement { text: "Roughness"; color: "White" } + ListElement { text: "Metallic"; color: "White" } + ListElement { text: "Emissive"; color: "White" } + ListElement { text: "Unlit"; color: "White" } + ListElement { text: "Occlusion"; color: "White" } + ListElement { text: "Lightmap"; color: "White" } + ListElement { text: "Scattering"; color: "White" } + ListElement { text: "Lighting"; color: "White" } + ListElement { text: "Shadow"; color: "White" } + ListElement { text: "Linear Depth"; color: "White" } + ListElement { text: "Half Linear Depth"; color: "White" } + ListElement { text: "Half Normal"; color: "White" } + ListElement { text: "Mid Curvature"; color: "White" } + ListElement { text: "Mid Normal"; color: "White" } + ListElement { text: "Low Curvature"; color: "White" } + ListElement { text: "Low Normal"; color: "White" } + ListElement { text: "Curvature Occlusion"; color: "White" } + ListElement { text: "Debug Scattering"; color: "White" } + ListElement { text: "Ambient Occlusion"; color: "White" } + ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Custom"; color: "White" } } width: 200 - onCurrentIndexChanged: { mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex } + onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) } } } - } - Row { - id: framebuffer - spacing: 10 - Label { - text: "Debug Framebuffer" - anchors.left: root.left - } - - property var config: mainViewTask.getConfig("DebugDeferredBuffer") + Separator {} + Row { + spacing: 10 + Column { + spacing: 10 - function setDebugMode(mode) { - framebuffer.config.enabled = (mode != 0); - framebuffer.config.mode = mode; - } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques" + checked: render.mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents" + checked: render.mainViewTask.getConfig("DrawTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques in Front" + checked: render.mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents in Front" + checked: render.mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques in HUD" + checked: render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents in HUD" + checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } + } - ComboBox { - anchors.right: root.right - currentIndex: 0 - model: ListModel { - id: cbItemsFramebuffer - ListElement { text: "Off"; color: "Yellow" } - ListElement { text: "Depth"; color: "Green" } - ListElement { text: "Albedo"; color: "Yellow" } - ListElement { text: "Normal"; color: "White" } - ListElement { text: "Roughness"; color: "White" } - ListElement { text: "Metallic"; color: "White" } - ListElement { text: "Emissive"; color: "White" } - ListElement { text: "Unlit"; color: "White" } - ListElement { text: "Occlusion"; color: "White" } - ListElement { text: "Lightmap"; color: "White" } - ListElement { text: "Scattering"; color: "White" } - ListElement { text: "Lighting"; color: "White" } - ListElement { text: "Shadow"; color: "White" } - ListElement { text: "Linear Depth"; color: "White" } - ListElement { text: "Half Linear Depth"; color: "White" } - ListElement { text: "Half Normal"; color: "White" } - ListElement { text: "Mid Curvature"; color: "White" } - ListElement { text: "Mid Normal"; color: "White" } - ListElement { text: "Low Curvature"; color: "White" } - ListElement { text: "Low Normal"; color: "White" } - ListElement { text: "Curvature Occlusion"; color: "White" } - ListElement { text: "Debug Scattering"; color: "White" } - ListElement { text: "Ambient Occlusion"; color: "White" } - ListElement { text: "Ambient Occlusion Blurred"; color: "White" } - ListElement { text: "Custom"; color: "White" } } - width: 200 - onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) } + Column { + spacing: 10 + HifiControls.CheckBox { + boxSize: 20 + text: "Metas" + checked: render.mainViewTask.getConfig("DrawMetaBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawMetaBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Lights" + checked: render.mainViewTask.getConfig("DrawLightBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawLightBounds")["enabled"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Zones" + checked: render.mainViewTask.getConfig("DrawZones")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; render.mainViewTask.getConfig("DrawZones")["enabled"] = checked; } + } + } } } - - Row { - Column { - - CheckBox { - text: "Opaques" - checked: mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] = checked } - } - CheckBox { - text: "Transparents" - checked: mainViewTask.getConfig("DrawTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked } - } - CheckBox { - text: "Opaques in Front" - checked: mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] = checked } - } - CheckBox { - text: "Transparents in Front" - checked: mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] = checked } - } - CheckBox { - text: "Opaques in HUD" - checked: mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked } - } - CheckBox { - text: "Transparents in HUD" - checked: mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } - } - - } - Column { - CheckBox { - text: "Metas" - checked: mainViewTask.getConfig("DrawMetaBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawMetaBounds")["enabled"] = checked } - } - CheckBox { - text: "Lights" - checked: mainViewTask.getConfig("DrawLightBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawLightBounds")["enabled"] = checked; } - } - CheckBox { - text: "Zones" - checked: mainViewTask.getConfig("DrawZones")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; mainViewTask.getConfig("DrawZones")["enabled"] = checked; } - } - } - } } - diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js new file mode 100644 index 0000000000..3e0beb2181 --- /dev/null +++ b/scripts/developer/utilities/render/luci.js @@ -0,0 +1,98 @@ +"use strict"; + +// +// Luci.js +// tablet-engine app +// +// Copyright 2017 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 +// + +(function() { + var TABLET_BUTTON_NAME = "LUCI"; + var QMLAPP_URL = Script.resolvePath("./deferredLighting.qml"); + + + var onLuciScreen = false; + + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + var moveDebugCursor = false; + Controller.mousePressEvent.connect(function (e) { + if (e.isMiddleButton) { + moveDebugCursor = true; + setDebugCursor(e.x, e.y); + } + }); + Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); + Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + function setDebugCursor(x, y) { + nx = (x / Window.innerWidth); + ny = 1.0 - ((y) / (Window.innerHeight - 32)); + + Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; + } + +}()); \ No newline at end of file From 18287ed45bfa47d68c70412861b752212511d7d1 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 13 Oct 2017 01:06:40 -0700 Subject: [PATCH 504/504] Banging my head against qml... --- .../utilities/lib/configprop/ConfigSlider.qml | 29 ++++++++------- .../hifi-qml/styles-uit/FiraSansSemiBold.qmlc | Bin 4097 -> 0 bytes .../lib/hifi-qml/styles-uit/HiFiGlyphs.qmlc | Bin 4897 -> 0 bytes .../hifi-qml/styles-uit/HifiConstants.qmlc | Bin 51320 -> 0 bytes .../hifi-qml/styles-uit/RalewaySemiBold.qmlc | Bin 4081 -> 0 bytes .../lib/hifi-qml/styles-uit/Separator.qmlc | Bin 6855 -> 0 bytes .../utilities/render/deferredLighting.qml | 35 +++++++++++++++--- 7 files changed, 46 insertions(+), 18 deletions(-) delete mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc delete mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HiFiGlyphs.qmlc delete mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc delete mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewaySemiBold.qmlc delete mode 100644 scripts/developer/utilities/lib/hifi-qml/styles-uit/Separator.qmlc diff --git a/scripts/developer/utilities/lib/configprop/ConfigSlider.qml b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml index 516193b81c..f82b381a2d 100644 --- a/scripts/developer/utilities/lib/configprop/ConfigSlider.qml +++ b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml @@ -9,16 +9,19 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.3 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 import "../hifi-qml/styles-uit" import "../hifi-qml/controls-uit" as HifiControls Item { + HifiConstants { id: luci } id: root - width: 400 + + anchors.left: parent.left + anchors.right: parent.right height: 24 property bool integral: false property var config @@ -36,19 +39,19 @@ Item { HifiControls.Label { id: labelControl text: root.label + enabled: true anchors.left: root.left - anchors.leftMargin: 8 - anchors.top: root.top - anchors.topMargin: 7 + anchors.right: root.horizontalCenter + anchors.verticalCenter: root.verticalCenter + //anchors.topMargin: 7 } HifiControls.Label { id: labelValue text: sliderControl.value.toFixed(root.integral ? 0 : 2) anchors.right: root.right - anchors.rightMargin: 8 - anchors.top: root.top - anchors.topMargin: 15 + anchors.bottom: root.bottom + anchors.bottomMargin: 0 } Binding { @@ -62,11 +65,11 @@ Item { HifiControls.Slider { id: sliderControl stepSize: root.integral ? 1.0 : 0.0 - width: root.width-130 - height: 20 + //height: 20 + anchors.left: root.horizontalCenter anchors.right: root.right - anchors.rightMargin: 8 + anchors.rightMargin: 0 anchors.top: root.top - anchors.topMargin: 3 + anchors.topMargin: 0 } } diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qmlc deleted file mode 100644 index 62898a891eb53784c3db5fba53a950848e4ef57a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4097 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 diff --git a/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qmlc deleted file mode 100644 index 686f84ad173d629beb11885bdba32232ee46d480..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51320 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 diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 1cf88ec5ac..7ab7342605 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -16,19 +16,43 @@ import "../lib/hifi-qml/controls-uit" as HifiControls import "../lib/configprop" Rectangle { - id: render; HifiConstants { id: hifi;} + id: render; + anchors.margins: hifi.dimensions.contentMargin.x + color: hifi.colors.baseGray; property var mainViewTask: Render.getConfig("RenderMainView") - + + Row { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 Column { - spacing: 10 - + padding: 10 + spacing: 5 + + // width: parent.width + anchors.left: parent.left + anchors.right: parent.right + // padding: hifi.dimensions.contentMargin.x + + ConfigSlider { + label: qsTr("ToneMapping") + integral: false + config: render.mainViewTask.getConfig("ToneMapping") + property: "exposure" + max: 2 + min: 0 + + anchors.left: parent.left + anchors.right: parent.right + } + Row { spacing: 20 padding: 10 - Column { + Column { spacing: 10 Repeater { model: [ @@ -249,4 +273,5 @@ Rectangle { } } } + } }
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +

    Backing Up Your Private Keys

    +
    + + +

    What are private keys?

    +

    A private key is a secret piece of text that is used to prove ownership, unlock confidential information and sign transactions.

    +

    In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.

    + +
    +

    Where are my private keys stored?"

    +

    By default, your private keys are only stored on your hard drive in High Fidelity Interface's AppData directory.

    +

    Here is the file path of your hifikey - you can browse to it using your file explorer.

    +
    HIFIKEY_PATH_REPLACEME
    + +
    +

    How should I make a backup of my private keys?

    +

    You should backup your .hifikey file above by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. Restore your backup by replacing the file in Interface's AppData directory with your backed-up copy.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    What happens if I lose my passphrase?

    +

    Your passphrase is used to encrypt your private keys. If you lose your passphrase, you will no longer be able to decrypt your private key file nor have access to the contents of your Wallet or My Purchases.

    +

    In order to guarantee your privacy, nobody can help you recover your passphrase, including High Fidelity. + +

    Please write it down and store it securely.

    +

     

    +
    + +

    Want to learn more?

    +

    You can find out much more about the blockchain and about commerce in High Fidelity by visiting our Docs site:

    +

    Visit High Fidelity's Docs

    +
    + +
    +
    + +