From 84e11db772c30cc3f926619067127639187a0953 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 29 Jun 2017 16:58:56 +1200 Subject: [PATCH 001/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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 zcmXV!`#%$kAOGF5E^Im_VoaAqrN}8EwvkGmVs#|SWmx5Y&vl!LG9{)Y<+e&uZX@?Q zx!;w$Vc1+}E@OAwzK_rM`~Bhd54_*c$Mf}gzhB<5vXI}evR^_%LjLBBtN%$zNE-ex zdu1ggBnEIBo)Qugl0pAjT#+d12d_v-NXWRES(-^mln@T=y6lmVkl5#c<3W&w1hD0Q zk?ipM{zyWC0={|G>|U7DnsDEE(TdHHxCVX1&2Rrme>|iibLGlkHwL5RWUqPL$@#wb z;-%!LoQ1(crLhr)(aDOem<-=?Xm0su)ob88SG~3P-eWeO75^SQVJu~E|kCKizxjM+!2b5VGfu23q5@YMfnZYqa283hY zp*Zg44#z=`QzLe{!E3sannkYw!TlK_V)L=cE;!#!%+$)7~-k_H9^M(ey8o_Ciw|Z=o*L5 z1;NU&lerjw0nH5)hHlrIYMjO}!Xd7~(q+wafk&()@7B9nBdzQ)a!_{+xPkN8t0I(q zXeON!)Wg=^s16peC=pZadYqVotlofI(YjTIdZ%#A#@*iJf!%kt6$6;xfJ!$gtATM7 z5$0}^axe*%7PZ!PtjyQ~TXHFQ36fYJ2Mt@#WAvj*95P;xsht&tQA8->{c(+ZBvd?^!Gbgbe>BuzREJN3-~hS=bOvjn!=?Z-{J( z0;X}r0KW|6I}pallJ(mK>0Q$Ge^S%4*Q37AP-{Rdm&&F7uw0`uJq?JI0dy+%tFSF} zx<}@~X7x`LZ z!#``VSG4XZv|P_vWM6*d`rpLJC2g44rqWMW+*?~|Uj#=sA}*Bb9pB8l_PH2ne zz_HT|NQ1B7MJOyla2ownunxhrqy+6Kr)c~2qOLZ~(UAenG+oRF`x>bMD|oI2FLn&o zN2J_TKdUsNT|_Sp9Ck}!g^AVe2oP%jFXG63VuT`{-Hi-T*n(Wfjj-22GgU$e`yMCR zFMN`1ApSjoG_ocft7bki>1N^tesBgY6t{)0w5_8RF=TgXhrX(Rg$n&q!vvWzRH~~r zq?FL6;YnoS70}?T!glJ9brHU3M$}U4OBS_};8?T}RJYPS5h2%^Vla0%i6mOJjpU>@ zEY?$aeQrH>}4R98Ac!6#B0z0v=4bZ5aa|)LXUR zrs5B6RMB%79Tesc16j{o2^DR^8=3Vr7PpGzstBJj+Xjtmv>x1mpAXU*V!q{OOrP(W z8y_Kz*j3tszmkWi?-Ogb-O3T$$IvAyxFf)~(h9h@^XmfAw1`E{su7kV1+5I65Z~y( zP=|fK(7i{zw-X{p5yP1&$gLuV4XK#$dCX~CtXwm1H3TwnYr9Kq&-}Y#UPF7D>Q+u@ z)6hn@nOz(WUscrTz(~UyX51i1++rqTQZzw|{KHlf&!oVOw%)weR^lIg2N&kTsI>gX z)lBj&xG;vCFg9m3(DW}P{x1Bjk#^$$NT8J|vlhy9HAk_XPHTbw360_@8?tUAXCIkR zxAl?C*_{@LE$l*n1ujUzrI?u%-ij7UxEkt9^oU_h=5HUbRIR@%w0ZrI0mT}jv=jLS zAxqyOrv?AHy3J|iV#JKIQ!iOfoE1?E+Ls(jvmBTb{Yo+8#C4?{Kp$N|?7mbY*0H@P z)mXOMbh#s*2;NE0_#*muDE2$~`cF6fqvjO&FLI%~Qk++Dlvi^rRe!I7+i`WR+SqY^g>D2{KYC<rggul zli~46xVA1Yq#%)@k9;Z!p~~}Gq~oUQELaa*6>|5Tv@+mmL`*$pt25j3ZbK|`Bknek!Z_@& zT3PmDMx`V1Z#@63(Xdi_>8ntzO6Ys;tC)hP1mXznNKC<+BH;J{Ja~otbOhMBW8r2(yo}n@%nz22;2(8ww+3$5tUjsoHq{ zn?%Nhf=OF&V))LnRni(tud}=1!+1Ab8*mp#FmBh@NNCykUC&rguWaPSF8Siv8+BXV z53tIC3tuwtZr-Y)>{$AdVVs~1X5;NYe~>$?>~x0Agly1V++&jAjmjSb-NcawaO9E(8sVIT zD(OF%RDbz6V0sTaO)z2%sl-xLvl@j9xUdDp)eKErox#N2K@jdw*M{&X$J<~U3|DIt z9~c(nKAoxB`aS~7hus3QERMSktn<%0sNYCcrA&5GW*Mro?=2>^S(p{ocm_ zNN$rH*i#l8F~fdIhVb(@Y64>koy{MGVX1JDmJ^2AUODxYxq#eWXIxhGjX+8!FBe`z zBS^HsB`44R?)!;>M~XOKNSChqbbAEsM|A{h#1a3tSU9VnRG)!j(QLQs(=-i`p)P2~ zfthmQB(ZvdhKL-w1@!IH{wuJ(hqrYMN8P~u=}N^epet1U>Sp1`+S=xlRV96E-0R8{ zR|t=+#ySi;OTc^umd!@M{{M~2^Z&MDw{2pCVy#1l)X({SdoMQRUE$l>wkspV4MDbk z;$;JvhoFTb=+|DV?n==zOolW2h{4RE#@(u964#tP95VER(dwH#Qojd$qw(AQO66*( ze6@p-<9#D2&IYtz5boH?QqOltsh_Hcp-mnygjy{W@H!$29+5!bI&^=EEyooT!{0ii6n+}crqzm>+XyFtWn(pTeUuBS**tAYoILzm&A~7;>i<#p z|0Lu!slc+07+B|*DK}g;bw``9{&^w}kWU7CZ*~q;Z5IzA#;|elkw@OI@DJFpV%*nU zjj&%{;71Z!H;)!3Q%EOrf=$bCzR7_y(?ZNJ&vI<186f@{*!8>!#4D7#4EB)eCsm3Y1B zT2sJ8ixZ|MEP~qnUe(X`|N9AKa2-0qJCj*z%aHD^>MoVM7e1?h-B|z8ylur%-}E(T z;qMTG@(yQdo*k9Gm@WT{%%j~lZ#u1!8}L7U#n0{AOKiP-zb>%MUfqeAzR~blaI2Ad znv4`^iJP5vI&v{n^_v^~ETxP(w((5G^p2t6h9|616yzS9aU@+w!?!4Y#O)Js_09NUgdN@c%0kQsG5L)d{o;^}fif}B?CBgHT} zN>VB5LRlAe<-!9@t8~hA-&>oq!yL5sVkQTnsaw9Jz!xn&yup|;ZwiM#KoRYLgb_|t z1YKTL#BU0R0Gp$2i<3FNV@~M*q+dZo?wexT4!uT1r+XoYf6Lw#e`xSF3j13Ya-Y}z z?^6C*pFHMKTVMzuC^Hvdq%!%WD)Z6AcSIxCrh!|0tf*P~JcEYbdpVpiqy!!LTVr)& zOMzt@RS>3;3hy3rTF|M&_dAvglS8rdY<)4kcKa7@x3i<|fM^xYHPWJNBE_rh)4Jl> zkc7s4RZ6)<)*{ zkhkpT=OP{UfZomKT+Y`#l@DGm4Xm9E33ny^!Am|}Z-{-^?x^K%t_ghZIjKnt8`l^d z(UN??WJ8udrQof6J%$iaUi8<+=Ico@nr}HK+yaE@<=m(Ou41v=^9tsm!g#)2rD8kv zaUHeAz!x`X3-=M^i+8A0K_FyEmx*Xtf1!ok?x567W5~RSe_IYLIv;P+YxtFSy_tKm zu0+G_ciFL3;x$OZ!aC+dK-ISm@I^!OHs|yQ8J^a@x%bJzr2(@V{l_2`1_)KzunT#> zkPjySHQOsmx^SP+&wH%Yw)OTknQ7Glygf^fWl3+nf9mdy4|#O#`tw+mWyUxnTyc{|EoQ>-xjFx zo9o@g@xa`?>52>$lU{>wmr(s5-{mAI#|_K0`mmq%IgERRSxNmYW55p)8z$F>tO^^* z^GZsdazABn%T`*S01!DDS|3!)Zsk}-Jq7YIPq1BQ^((sR4%-{>P!`(&w>?3I2y6X% z40ce~Fiqr?c(a8V#w0f|D=WN;Sg4uP(Sc8%XDr->FVL?0fL4v`*+e1>SPL$M$ZET$<5f z>>t|giy=)f?0G}#!wB>W$4)BHIrqZWlXvo8&1W1Z*Pj=Kc`jBtMFnv56rIy^NGjGX zKBfS*i}T;1d)_zDp4rG{Fwv|`B$rE3643<28e!1}uCd+-f{sq!)H~M5;WV!D#0-jP zP>skWZ|oprJ8FaFl29hW1T*%0H{y>pS|xB4%-JH+Z*LihNeGCawvqcbPYY0^qpFku z_HmDCpIVY#_Hy#H7+q_hD@}b?;nn{4*l*NodZ4P{xL*x(FD<11eBn3n+L83YLv>FI zg5#RnoL8W85li_K)@PLr%N>z1rzP>}>GT)Uqp<#pRceNku3hr@tB&dtht?VcV2pNfc1 z!?>HYe<$t}4W~2&{ZKrF-+e-T^BrT-XQtXD;4se0D!pAPdD8M~!=XYu<9^V;VDjz) zXr@NUtE^k@hlp(9+b2GZqwu+d%j4)4_yl<@R+xg=9*?^{xE5_dts&!qGsgsiGKj&}P9eF5W z^u#)pj0Z5zqmsTC!rnaZSd*~@M|vu+np0^AhhO3Tucg;+rAHA?f6a(|o^WT!%Wf;P zwpSY1R1wL@>hK}qdUE~XJG$^ok+$pE{A-v6NMu{rn22ZKjli&lL;l9voAVsm!@PRK z5+!NSyIIZ4mK*JZ2V)M@Hs6W41gs-hyj^O-_c?yEr08R;*VE5fx%ygb0X_yU=lfog z!Mo=@qwMIB0k3;+VmC|D!rh5v6hk=I$Z9e0!;QvHfkZt7kyw{*!8pL}_6y^6*@`9%d=U}-CR3Zy zhVKI9zTRkb0unZo)3Zar0LuE3L}?uq zcl;qD_;*C~x3fOZeod0S4EoWOCs^Sd;jv zRHBhn9Pqsfzt-F;=<@h2^m-fmbwK$of9cGdZun%dw_P9pqi0V*dbNc2rb;$KweBTp zWVbu!t7CXqDkxnAd^Kz@D$N?|60bvk-Cs+>Olm_e$(_2<)@gQV)9f2VMJuD?lVN;q zERj8Y$k}T0gNmq3mMGm<78Exm3t@ow7JBnDhsLwyk5%R&faHh{tbs3uH99aJIg5M8NWB9PG5VRm1#1eLyX10>lL!_Fqs;>OXxd|AW+*v`Z%~u59JD-CADrP`XEU zCT||Pi+-3-Ou9lQ+raytUoUbOJ@Vu4L$>p*uZiyYcYBm5=E5g30#yhmK-)^|3zgo5 zwxY8kMY$!XtyF(5RkDuV;lKDS0lWfv8&bIMW}{P3V3ZU7q50^%(jG;*3>~*Q_Ab=& zAfz_@m;O>k>Qo>nq$AmhUxSRG*CHWr2s+Kv;=3o1B0Nk$>+!CdW={z^gs3l-vfg`U zF3en4aIbg#vG2yx>4KQ^qh=FSH;XnYTbJUwF_TY^t9ayAGg(MlOuok+yxrKIf*+Hn zNT0^>vTK6RhxOyC$hORIjD&^TAP!9Q@6_x+&5Z^C|F zJyOt36hOt(s<4%px>dUC=MJ5k0hTE$jG*gYF=ZaL9Xf*!Sbcli*{@~YzciiV<6h_3 z`68BJE(NKXh;{XBM~pm_t^9S>XM{52D?cSl{L%2Ff0>VLOT z_ZeB!`zDf~Lr(wIE}yfl^i&P(9p)tmfLLzj$?3m~Duc#?cOL6_w|`Hd`sb_9elX8+ z7`wV?6DfrsF-p+VOxOSARVR2{nWdA%37keaE<@$&bHca^a~ZafUzg*UWvV;btIhr= z{r=S=0FO;}o7I3M$F>pqw;;yh%yN(~F_4O{;J|nbIJ#{lH8b=&tweWRfN~P;A}Aru z*S0e@@N*;C^;c$80M2Uj!my3cOFQMdIe@2o&(dk)0jU`A^TU}x&XN5hNA9@|VRDQeb$RTV^VzAm$Lft92_HHx_;r6f7xA-nto0kULP8us&fG(~^mI~zRqy2O z&Uv{*A2a*(X$U^cxN`5@4elM9vS0Wn9e-eOXAdj+K9}oq^{kq3z*!q$s=N{2fhjHN z!=X8cDs9CK<84dyS`R8R8@GrwS)hd@0|m_+)h=?*+tV}B^G287FK^kv{grQa%x79_ zM5fr=y=>{rn#}q9Bt-sfpa(kfN4NeD!=R?Nf0J|M?>3fMoP1y5HTl*S_pe!1_pS7Z zuD>-NlW{=q(@xvhbskGAxzK=}>7xzgob&glEGcy7czA%k!p83~hJB2j0$Rm=vl;d2 zF6#T^jF$c&*XrmOsTF||&$mj4FRI$}C>vpXHgQ+TSxo1Tzc)EZu;eA<1V^<7BKsJT zFup$0LExwXoah-cEb0F2)F#>a3gqy(=}t?>9n8svj{m6Ktss>Wk1-4D_T^i^@JL!Z ztl<~puvWG59pFoWh3=L%C2VX3;SdcW-=C7%jLRLsEO)hgb&U<724D0ZizqT5vtoF8 z;kd8$vP$MJ|6_q&7Qqg8o&Z%u2OMuy_{H|K9fwi{=%+Fu6=Vb%T?yG;b(^b*?Cr@8 z{bu#XPl$tW&`KMG1{Gcm;jsM)zswr>i=&^b_m3U}riXhTg5tw-+eR9@m3O7F<U5IGLft0S7N{_(Kd7;81c--eeqUK zBCVO9j#%lU?~nt4=j?9V|NU1UJ=WIH&WdvWYPxJ8XP$uY9!tYw19^(!{8c$h4L4b0 zcNjQ1*n9AMqf&a-zbdvWyXvX1i>}HAD(MjiP32#t^~XIwNP^v;2fXIZeI}@M9Xk_hfye;ATg~>mLozHF`0%U*s2--a z7XS{r=GFKuGkCqyYqnF!ddR8HDWb4H2ltm(wcj7_Gb6U4FD+asQYjX^fX977USjIR zIHQ%05o-CnY%L<+i(HB6>IopjHTT#q>779G0Z!peBNC1pGNffXtc zaFROm*%iT#vM63lA0yY6Y>y%Mm{hk7sp19|AU5GKX<1qGpH^-q+Pvrkk1R|27!7^C zdHz0(kp;qO5fBRcpZ3mqs*V4E0voayZU-x{Y%5MrY6Gix$2ZQC>A~j`q!rrVA)O6; ze`yuJY{9kWLKkvEfHl15{QQI2U~j%-{#x7px(Nd$)&;$l5bC!P(XH(pG2~ab-Eo2W z;h;li$Lrl6xb4>DT-;1%FkRYYQrmfZup>}n@gVq6{^bR9=6tcb*daa6EOw*HCGMK{ zZj4zR*bZed+U94s>>wE#kk^%ey|?$V+~ieZi~~PMv;QlzZ9Rj2=Y-bOV4B}`g09a6 zO;!5IrOsJ1xreAP=>E4sOGz;V@ofspIC=#gzFKRX{H4SXEWoBGZ zHEGaFrR105atVsXl6%7wyk^c+%n!965fMPpaKRVc=Citk>c-3u6N8n)dbbP{u@_|l zH)43{oB0hNFjfNQV~TptcIkgbuU4@wsM2dFGHA6v`cXMVNL2Rov^9W#jaBh?yr46Q z(zFI*-MfM_!P}VWiz!Q%Yv>DGAKdo}Q!E)FqHP(>e`3c>7EJ$W4uBhB z@@;019G5(D?;m#ir9O!p(CJrw?yqcClatYIe&6T`s!hp>^O-T}R<|4sXDyN;7vN84 zM3DJb{v2U`$;42H6*p(f78X3w zX{Liojv?fEUa)Yr2I?C{>y=;V@cEr~%dz69?=4&$c_oRs`y|&sr0r;o=G#!6;lnMs z&ENf&O!nW2dV$u!`N2g&$CbdNV}!K*V!M}M%-7zB0;a*jkWMCMB>TOISt2F5r%Fhd z@>%%yQnB$TXP(!^h0k!fjKAT`&loDiv8z`6`;DLIH^Q)bQ>rt#$rK~8J>TBv zn)f0T>Y!y?{aR0lukplg8o7;$)4f=8fgACCsks;1r#QP1*Hr(SM^mo;mRk<2;q_;XRDnENDjVY7ONvyolUs_s-7aOS|`+T3{B z#F!5jet1^bwNvd7_&Mr$Kml`sfF58wHVUrg+Cb-{^EVHsQdsgN!|>5cVS7lRaE>!w zL$X`QI$~W!weyx}0hlu)dOCFJs~qy8mZ6%ROHlC(NSwpZbmKF2HKo`{VA1Uz8 zEnqoy+uwj>O8F|7YNS|;?I9~!}U)c*^XkK)xdcSyLW-UU#H=D@52f(pZ z?!?%Q{#@4}bi#lO_d@NP8lSr;spR&tBF+KS)=osHi#}u=K~%5jB{PhDu{~^gCdj zK6~Oke>Tmie5*u%E$o&`2uLBS#bpybHN090CZv<_$)Z=;eV5y@c1^LPVP9hRde0dH z3IiYyr?OYNt&Q)Zj}2U&15e#*?+OqrYPnT#lI#jUtMH0Hrd9)4`c_5T=y11@=MC4# zwa-V&HXk#Lch4F})GjtN2(a~YE6~edLU?t+_W)9AZ4n8DFQ(4vYkn+2PBLQ9rfPvsn_y{kH_#|{?H7{@bbEswWFP2C`5HmJ(ja; zeLBFZS&7H0c#8QAPp#lZ(KzL3M#kMFezn0&wGe^in}B~=M<4va_Xl!H$$2I+2YU^wHIS`qO}#_6sZ#{C+9wajtQqXg=djIP>(o67=a}`7z#q-qv4GBF zSsDC-A(QFspfBaJ5%Zt_Uc5xzIf!bQtXRrZ>Z`w9cRVQPVcc0(d-*?(`t_L&W}0b1z+(4NoF_09?Lr zE(bjP2f$RZEZ=OVMrecJMujUiOZP2{3d{2&_l&BZ(Q#NvqL(OgYi#x83`Uer8=4-T z>^A$;VTBBto+gWTYl-9w>H*=(q_z14eag21u5OJz*8wA`r&pLt2u z>M^fc&dEAx#6EzSJ~LN0IKW{=78X`P0kvIi~T5-fCQKGF`aj9q-Iq*`qa=l5Dt0Dli|w`z6L zy@P*T%y^FyT=Vw11#3}!h4u0i40uLK9uJq&Y72i{& z!{(VZKXHN&+5vNs5%UuEl;D8@b$Hg~K*yYuT)uCieZjuq|G)!s4Ld8|qupyv0h3_I8?+q*bR2mA#yf}ibt{MvzO zV_r0N%wqL`U3QSHe}53@i--1rx{X!?xznd^@l87OD~S&!(6v|7Gzd{7jogwHgUk5Kq%ka;KcMTW#OC#USso{{~8BH65*Mx4k5EvamP;HTV;omi4_ew>iO#XkQU5kH1N6J=%ABFpS2Y#(>l_o_UCpzJT7^F zlP6&%gMW-rHh!P&8a_7p2gl`C`jLdcY^UF>ojAi7ac~OXHE3Wcl9M4nTQ18cNsT>T zFb{R_Z)-jL2H*Vbb35@}VAa--m(m1j?WZOaZKm;BP@utLtLcAE0CJXEu!HvQ-2p?t zt1}s!l9M^%3b$@$P4-OD@*#dtJri!$yUu^mopqkQvs}kML*rwi)11Sln5F-`8Uzk_ z`V)ungdjhgM!=8sPbD@w&+8ZyVpo~=`@!8_bxW6u_)=9lHTS<@T9Z&L!;6mhr~ds= z*(q-IQXg+v=U2k@rY-JmzEVFJ-Uku)Cev|C0y>VkZ|7Lql6IpT$-jV7tY>IA%=b?R z^eGz_cg!9wf$kg&b{_)i@HZAr5e8>LsvsZiRZHB*^r?SRd9OcG&^E;3&di8!uuf|r zZy)4JpRUtt1TjMGmvQmZ>k+zM7;^EH7Txd;!OpX9EYhv5Oljx#DT9f5LGd$9i}NIN zY7R*D@1Q@UNaL-kI`xnvu?basfuOIy+pK4khd%hbZ`G1;18gi&# z)DAk8A+>g7&Sk4*fBJbMJ`FGu)a-T+ecQ7*nHIXfw?+#zy1|5dZotM&$tm59dq@r z>aUp5$8SPogiH2C;)IjxMi1(uZ>5f52DFF{!k-{M{Z~};M*eS@e5dq%{nZO3@_weI z+&+g60$n;4mITJ43?0&4uzlmMVqz2flY;TFVIoDK&Vr2!S_d)9k3*lp4|Ky8L$f3rQ?o_Idf=UxJ|^XT<}bj2p3m3MDwJnC z1xcUq^Y46_7Ka~&Wh?n!Y`2pJPqpp{qx7DnAp#$^JUb6v(Cb$=9Q6csIgTDPC>cww z+c+^-M>~&WULfc}ta?!n!r^@IlFEkOcD2%|n%vTB8$xeTS6IOC{&szf?TZZ^=EH87 z6f!DWB?3d>iS+Q02QKlmcc5U8;AIhjghd+?lFBlXHM{*AxX~{5KG5Vda7U3s<9V+O@8*j7VzsUx8&*&(=0khT9_8-wuC5sn{) z(?*yg`ZwiQc{BGQITKoAB3r+iigYUX%&ApvkjrgqTDD2K)D`(=q;$Xd*zH}A>pV^> zCbdtza?3q%hpMa8p?csz8kkqi8%qEYO9oX4I9EK{XdFOozF>QhbkPPIe;5!LF&s@qG=0wB#U&URQqDqB`xATOQ_y=Rhz@^3{aCvW5r3 zJTBw(EAs5M9F<$9K(8FkP?py(!>gQtrratYGe*bMX}hb#Nysv zkC%F*=Zhi7-uM`kE3MZ)vOeDPS$;)tFC#3F%9(V=Lt0+6c&IBMv_B^664fH>C1bE! zhNQiT&tN6}=ez!GdFzd}fjj)F3C05SWTq)4eQSOYZ!}T46rRYn0W|q~?U+PEn>dmA zqWYrUENP@`W+?Cy=)6!ynAh`g5xhm{`voU!m<~@c# z^eNd(x_QoiB?TOBc2@M9F~!!mt-tWHN2{8AD|tY?n3|a(Rtx10sfzcXsLRpwj|E|S zop`j)eC#rH78SRJ`XR~#aP`h#HjImfm_Lt^*}4mXQ6-r!8*Td-`W?=PO5EyfPQcDj z7Y9deKN*(pi?PEy8ZO?`F_eOFcXDC9l4VvP8TvJ)e~_FXyR`Hyw&ac8>P2mXLbI&! zGTRcxi;XSwHlUkU)F_bRp64Npw*wL~pP0Wx?#%mVW*vW_Tinnh1Mo#C)6@Qh73{pQ zoo>KVl^kXoeNKi(crXXODAkTtThVH*<0N24f8nR_=k1rr!rSKu3C)*4`z9~QLZr2_ zxWC*mm{w?OZ_wuJxl8lQ#`Cl)7N(NS4ZA2%K-K#f*s#Ft3K!Fz6Z%4JY|IXE9J(U9_G3At41y(q@Y(e zbwOxW7yZ!q&c#?dpmD*{{B^}DsyNH0Q9=F2URG8ax6d|{B5GnM?&uJlb5l=}Dsn4> zNTQ~Y87C4DQf`X~E=w`iW_!9i`&YeCGmn-&BMZd03|@{iT>hnL;5CWjp7MXNlx7sV zJZsc?9X6l+WKmE&l76T&y#`*r>C9Y_f}skCvbPuYW&|#oGi0CE@5!WcnfjcK9_os@ zt^EA>R&yj01y}-x9r!Rc`uJ7bd7q3~#vHqV*%wqF>Wl{@uS>wScSzk|%N6c76X{MJE0F6Il zkEE7~d+pbcCS|-FirC8-wje#@4<_A*n;9iX{QSpK;9R`VWb{q>>kEL23{tPpKq1g zx~SA5;dft-phuH6jB0nDE8d50Jz;^rqY=odD(RP~u5yWh1E26wK6|KJ5mZK5`dfS) z;C;&{;Q4vlA(Q3Th;rIf|C2}+)`L)mL#hJWC#F_$nSSHiRPrcFt&i^InZW6r$-_F* zl6q9e_WPp^kGRPRJ-lGv5`U-(NZ=`!%OM47Ue3}Fr%ip0u0!)^KeVGhcQ$_ZPSM53 zx>+iS0Av_b#GvCkmd*#>jCO>1S93UxUdCD!^bp}o2kiYH&0%EQ<#!-efQiWeJW!}? zrT6G?bMRHiVcCvje|fz&Xvh?C6Scmu6Q_B<;+-c_T#ze9^d6CicABc1TjN2yZ9^K-9#Kozn(Z{$FyVB+N zE!5-Z`1AyA1S!nhXyC*%R{ER_Vw+|<{-9#k$1NUA4ypx#0bqphu6s@7mF!WMoLvdb zPNItv!lIcV`Sr4b*HB`x>$$#X@L1G_`ab&eiXcjL_`?pukL9Y*Ak>IB@pCa!x zztl94*N@g)cuT6GeWIVi`TU!zZEq-SS+|yZYcz;i{WF)l)pe zZsi3hC)nd>Y3a~H3n_df6t?q~+jCa%>3A{nig3u=y_~b{UquD%mp0*oWxz+w2h1O- zYeM1752WCdNPj4?K`OPTqYA;4UN-OD+BAlbCIri-5md`%&D#TllJm_5cMkVlUUI(h z!~ng}(+4W@Oz(pg**35dP_^sau91ch()K~u5Y=f~Mtd4`)EBQgF>VU8ZY9x6`VSo$ z%KagFv;dP~zkyYG^c;y?Fdob&S?pbk>2DS2%9xm~I1C{s)h9*WOHkrk%aOGmm#*nX zdi@Z>Y9M+Qg_%H}}+sE|9!wrI)bm z@5+JcY(q@+R++XYX5_2?_;Z$=GixPl2Yfk zVxT`~X7xO!g{PHFwylkCS8<94j=zmhiQIU>c^lx!?K>C-64koHJDGGzQZly8`ar| zEpGBow@5|>$P>Q52J2SjOo}g`4ZAblap;xYk8Y2&t?rwS73`B*>{sdDj-`c5TfQ9V z3jeR|hJAsGwpNl(W<^WqVvS?3$nRx#y#Xupb*P|d0DYI>^v)TtbPmE5Zp`#gc>Qh;@$PiN zYo|vpmDv*Z;LX7ZKRtzZYx~x{z>8|9<^Y3)y4AY|{6Tf2>BO`*5cBw{3{_rByS*7& z&bMK-MYJC)??UxS$a)sC*XUH(`JLIZD38d6Tj+5w|u5mT}8pnI7w_PK8adtvAutRn(PnJ|JklEd?U?{uq-cSLg zD+3_rl3QUxuBIBXPN=5Wmw^h4^6-eh$Z-d^oreoMkrHefHG1-x31bghzm-*fdUN!; z_Z4{wPpOjr7Ap!$*%Phz`|$;>Ij^DXCV-RPjJX(V*R_>~R!{iOJ~o1Emeugf)%O*A z1;sA08=%`QoB@2ce6;I)=P1(rTy^*FEKk+*@5*fD&r9fWf9PMIaT!ByYZyM#F8tYX zP*b;hvzY-G?Bd1ojn(O;J?Ms=$_hY{@r3$6_INNL#dC68t?WzCP6~3#S$X!;Hrh{b z+5bB=siXMSGldq3OR=u4vmJw|zT%^{G6wT&mJq?KumXe81lChWhS4Wh&`c)-DjkHO zwGHB-zE_U>-2v`bJKJwi>-r_NWR1KHVixDfeO0mhskE5C;b2eI0G5f`wBG4HLX5dx zBN^P5eo@yNy1g%cWY*Ln5Y(g`WLXXJ2p+nu;Qu~bkQq2zS=ByFf?in)=G2^suIEQu zyKAQgCQo-Y(x+O)PT-K>C%h@r?$j7&b|jSBVXfv5HoZK>Re1+^ms==0<2D)))ggws z$Vp9_13H?|ubF3r_KA95+)qS*@UnoNpP!QE93Gs(oA|xo?9do;pt<)+&|{GX$7iK~ z8N!!obMcPV#RrF6nlUBN)Jv|##(4FsfoaAILr1v&(e+Axk&?mRyDpD)>o>SQXe5U| z^DtGY#}PvBID5fPGGK8L7^$ll@ow^;3OX$5CfL?J>bAto+xei2Mu$lD?kV-)F5xfy z3~{vWKuFM&YHH~Q*A`^LFhii6PbT(ILy+7fzA7`&%?*uQ=GLvsntJAhnZ& zv$2UzZ*sA&jJTMVA$7yWfWkKj-_~~6GN9OR_(dZ3J0won{3I(nd~Sf{0+Bncc5fl~ zKKk2%H0?-vsl{VJWRNvFvOOnbcs==6m~Xt^n1H3sNTC>wYrBioBh;@|5AmWY`W&9XE1|R4x8i#tA6%fEnGqntB#EYz07!{WI+53di$r&?Ah+Wy^`jPl41Qv z5kL)OSFVWm>4A1|4gS?*a6@2z)%Kqj!P{Jf)wZ(ML+|H1sF&*aLHsF%J=1IN zx290wB~eQl(%gGGm-4h*RWkt`71D0E5zarE`jul9$!ee56I=B8W9eI}XT@@{oD_kBL({nI;=HtwgdpN*~u~%Gm8HJH<^FNI&+7vq+*6RRyx90UXtZBnlFyF- zq6U*T(GH9e*awIyuCBhsYIsJ=abtk`fQ-%Aoxuc<5OON6 zoF>=(?k1Mp)wWA*n&W{5PiW`5cz+{b;=?&;X@pjnNHN76s{fgB zx)7mrwt}Iv7{-?5$U9<#Klme% zxruuIIRD`Zs~IkWpN4(@1v& zE~vAoMy7||md=f)?UjN_SD7RJptw2J3jrf3Q?ExRW=Zyin?^-R*&Truw0^?D8wrPU zVl}Q$&0%(^EPbJA{~pPJu!tW~I{`QVX5o}ep&JC4qqo@Y)aSdZMNZ>BgF__b|}<;gb_ z9BH(sQ_b87~RA(pi|_j!0{$3%+I$S+#&B}Pw;IrrPsf! zigGiR+6+ulj3K|M>x})Xr1dTO!G!wRF6-N}NUp6x_l$jAPYe$~!0jk$qNYk9vttTP)d&|B|6Dg2@ix3JEd`}uHl4k{RE?4cQtBM z5M_zU{OoA4mjlR}Y@Bl(R1C2BOW=nPKJd5&u|buunTXm%q+r5Ya+Z|zlj>t&wftD0poV_ke3l+c|&nB znS=A?bO9!&$2G3Hoedc~Ru>&<{6!DwwWQs5K1ChvEwp1anJg7`)7xV@F)NG$dOfZS z{_WNF;hlsIKC!nZp&m5DePT8hQ}=>Vc8Z;AF?bf#A-ywM6D=0h{8D1@60FqXjq8b` z@P~_GQyK0V_RGBtb)=FDx%5*!I~xtE1XUsyR!|7&-s#X_8CvGDjBDJs%)9T@kyASl?^SQNgC=1h$=@~=Bx)V`{nZc*d0va=4O69`Y&MxKvu=V7<@|M zo^{2i;eMl4ck3eJ`dirs&yAc|C`Poy-h`;EFc<|oR{N**eXsFt{r{`B4Y0LC%3&OhNB7dqKTvpzg~_Xy`vwEPO#g-$M|*oU;T8^$L`W=7IR z{v0KZB~E1XV2>tBVE~{|?Q}n(wr~f61_pY#6&AfOQK4n@ z>G*I?P;XaRH*tVN4bhe(-Fvc@E9$dbDg}WUuy(X2l;4X!WZP0zZYSQcuax}#8Q^ag zfAU?;44i>pSBD<+X5Wqa4pOg1SGvtwK*tXh9 z3swn)bR|RGbc=V+)nxk0bq0!&{Uit9)sMtAENlci#GQx|5^VlqhC$H#lBiH3C&P@5qb^2s&bBE`YJiniOiZ5w1`-I3j=5D4*e!*X9C zuYCWult^(|f|Y)&@-X@z)dG_b+lekj-&rPV`YuX&{b<}J2iR#@OO5MD%S_Xee?6Kr z*8YmHlfwl2LhCg{f*ZKab10*x@EztuA?iO^ zsTgcgCAylwKhIQIPbAbmr>0i=uLGAA`nPk+*vph2lapmp16^%=V}1cRegh1&ZBfPS zB(N3yip3+=VfQ+H(i9{`L2xhuG0wmY2NQJGB9D&9Yn+yk*@TG)vI{ zouxuRju4#j;fe31A&rl_C@39&Q&@>f7JNZJcuO^+QWq_~d-mo?J99VpAOdl1hWfIQ6T>TcP z1)Sj@9WEc#J+D?$oXa7l^(rL3)$s9k0Wn7EDKb?wKCYTkTv@XqpEIebtr`JB?0kMICh zG@ImOF1%EtK9iILRU)dInD=^uCCg_OfvHH=eMRz_PFucy^z~k9CQ+o#(qYZz$!145 zthvVAY>F2nQRcui52wsjpkych>J#?{uY|q1ft42?mXR-S~$RwcX z&!>$3BlKCwb@GS4HLk_RCBu!8>;m*W_6@sU=wElG%V^)@i$rjqU1R=qX#2UmP&Zyq zJ?zb7+b%i~mUqHF)Qs5r zg!GH9D}Y!tQtn*K`*XFo!J{J>$<*eIe?L7JCPH_oiu~H# z*FL?Dp%i07>L!nT3?%)9fJ5W<5fDB$sRHuD~8bluq z!9x59k{Z%=^V1Vr@1rG>P>5R|%R=t34)v?J>MfHSA3U7yXsqQQM6O#!yb8+T*5}#n zl+%pCbrMx061N&eDFYgxwjWM?Fc{PSZXPxdyj`*t*D96%d_%dNA_iQ6YCR}K&Ak}x z&kYj`v21-GTpYucmG0{Z@2uXfWY;TT#E0eT|GvE#GKO*>9h{0w|D`GKxsShC5;%)+ zgU<#Bha~PQ&q3B)+P95Zc;g&%;>`u$x>7$c+R8#cK?t7#Prs>oX1qL!sS;?@Roi6R z(H6gBJmko#J7s)%!N?JaZD|z#E>ER^l-QTdb@+?9olz$0@R^h^kubHL$4j;Acrcq3 z`P{{8y3qdMaT89hs4z5U>n-(=``3y7K~Xg{hI_i|$(0`ddzzU@Sr zR~(C7Bd_8zQ45`c!c=VX>ed?At1E(=sh;=kMA0+lpt=_5zNaSbg=54XV4G?|l=ob- zxCdl3$^*loX9}!AK&uHr5@iUPWrR;sL=y)nmHpC?{}~s_)cw(Ru4-Gau-M`NSO}5_ z1W^<4blJ!A5egy)%7JFonJv}}5I3(=t`^{2%9;Z;Bzed&tNH{>gn%9GU6_%i8Pa#Fi^`|K$RVd4^qE0UH!S0*j zDHFGR`gZG7;AYK96x|Z|Rv!fhpoW}%KMl*2 zIniut7ppa*MewB)#;UH+^IxEWLhM-RtnRAm$p1_;r+?3cmpeF0Ug(GPcZRI7j$u3) zLK5T@?RoAOi;)KlVq z1YF%(4!M#d8&ZVz?DfcM8*6ffQd@>Jvo~06O8ci>yIAFqb2sFG>k5=u2+42s%P8EL zDo+tBP5UqUgpbV%GRBT)dbjNL9n$JY*shx$JIzxVOk)f7K&vEVd~@2TgU8vx-R!IZ z*}LOv3P={{h&D4&ux=DH`7lDZ--^r#l$zm6%oyxn0GC$0zLV1xCb;Oej0gi<4+K8_ zc(jYWyw!Eg_oZ6MT*S)nQkS7hBdZ5{n2E+efVjZ>{8CcZme^^;LmK zskYnVU6kmo(cRxoEe1Q6N@gs`V-nWX7sywPK7&U0Z5PtgqYL;AHyhIS2FUmH z*f~v1Db0VqP~87L;tfU3^}$BmM~wn4^T*qt?0*MEQnENNE0;^w0)sVW1(5+#PU(^V z?!O^|Jx2wf_Z!gSm&{gkEcRJ9_BU>UtyiZwq9y-Dc$e3StXL=GSsB{TBx@0gPRuco zIXD!uie3j%0Zn^JSSYG7-XxpQPPlsNK!Jp;`k8~zEr~H1hRI>!Yc&cyZk1v`O3(j5Z~D1bkMg?^c_*CWXA;-+oo&I538gH@Pehc z=inM?VmkZ1M{ljZSmQVZ3wX7!eK#hbKB)Ml^3%S}?&Zvj)R5=8r(NmdrwdVcq+W#B z@}AXuM@ZEq2>%X*lu)4qB*Xy%+E|WOV4b|N9yE!1_zgZ*#WIMAcsHKm6^GS?T*yeS4Xx$?wX_)UY|;yl}Gz;@K$gSxR5U+?25Z zmV?1(vnm-vPM*+czhT{c@NEH0ce)<2Rh8L(dONClXZa7MY}N#|>gqr|(`ka)QJQXr z{-EtYpUJ`@AdUc(8Z}tq?sCq_NgYN_bnF+SN$V?#mAevyYfZNooT5IE-LSLC+8z1Q zV&LLw-dJFc`3eApttb(XlEV4hJuu9=H+rM0&`I0E~QNZT1x#-sj1`+Zi8bzyu!6yE%qX`|f zoKubWUN=2M%r=fGGjdu@;wYpAZnJ4R(l1_KT@<#hpxOnMPrR>Pl_V=)=aRM!FAtzm zXZJWW$qJ!-sZtrN2JdEl|4~>-({AwtO02#S!fnEe!|(OjSj+T>| zh?*SnmC-f-Ha>5eX1u0)njlZNvn=^SZ*Yo}c@Ov`ziN4rJ|kq(ffPEsVFFzjSrE^O z4mQH4&LEvH?a>kXpGM4rGO2I04&Q?p5AQuJ>8u<+e6yPsyl55n>(KugPDs?!sI0YT zaHCQy>-SbKW9$_6vR1V1fKVkwuhIeqa@a_2uu zIZF8{G`uQit(H~}p4gDF_Ij{=Ecp{{_Os8*Cm)6pQ!vB%*I8}S&QVsn&)s6zjIUQA z=6{-P^M@7t$c}vxq$lbB$=xC{ML>RG{o&x}KdEUN>7(^iZ#CIP$7^}#HqmP%{3D|k z-#nj>5$lATrBW??CUV`3l3Jw|ExaHu@PQI@%nm#+ytn0-cs%xW^BK^pydC7st12ti zn}=GO&biiMaeW*io~qBwSwPypOE#2FA%odA>;#iMzoA>}rk)X{#pB`Rd|EWincbLd zBh?@pzObU`K>S-Xk)7htbe_lR+`PQg*Q8USNO38S-Tk0aL`Q#8$4t3_0{pLUv&5&r zft41|K1x=2pm6xu??UJ8A(;}1EXEG*G0@O^j~hy!Qom>|blZy~Zen zzwf@N+_PRc*o}2a;N*u)%5%rCFihfh`)LZYF{-)l`sjVb=$h${At7W(EYyZd=uYqf zwn#a1W#y=k-N$&J?rt;8!UfM9b{?72^m`r80dwjsCw_a)7p(w}Mdi^ILJ}E<%G|r| zFwRe;Pu*vq)klK}H!F?Vj)cX3TTG9gacQ6U^v$9U zu-9>>Yk2$i#nIF6NLhZLA{wEJOrZ%!N~%Uy3N!kMArwm` zDgx61%Az;y=wnPFG4rh6MnB~j#T6}WuT@%8+h9l%%9+JY^}x+``#`V96=zofy0J}0 zpvnOtdzCN;V2<$6Lybyq*r~t-J^XcsZvMQsdC5YL5WEABUw}QI4meZKR4KKzZWml_ z?a2(iR-jUS_)|)Es^37Zd{alu`z+R<1x&v{&oLh)l7{{zdRxB0cb=e%&D5>e-RVyC zzNEZr0Ow|IH}35E+I*Zmu&Bipzdq@C93YP6=k*#41+=v;H!;lzRsf=)vywT>@8xD8 zm8e}}y58!vdUcypn4JiE+jt_i_1-aW$&{pKr|%}Z(qYPB{Q_Z~&XQd zp~>qKn_&WOwV)J0M2Fbp2WF`V@9#;!27)`HwiVV@r~6xHMwoBj`e}bV=^Y z@5FLz()|Qfdagx6y~l#K*XWl&mYR(0_XJ-Ur-j(uo)##I5F?9)G<(fJIrZ~j4=a6$ zxFbQ4zkVI|yvs^&5NB={J%4~M6DC=BQsn{hSeMA=nA)A-8k?2eQzQ}j+%Dmppoq}t z0xojlW;nzRBnA>4m{7fh2#0yORSPmHD!=hL6bbUyWI@^K3Gut6R^{igkaBpsO(6sx zJ=A6h2`g$cb7#9FhZ^teYmH9)O{r1v{IMkXZFB~YW}QNYXIoVyx-eVC$Hwq>U>Cng z-*PfaY_E6Zt2^gN^{cVKiquD$A7)I})S~Qb#dVKVE@kPl{COK6J8b-%E&R`RqEA{3 z^G_=0`5=S5N#C*HaG$H^!sIG=&x>@E@DRIbj_(OuI>fQS~-nvGJ)yPF!$`QveNK6 zoV|T{JgzD4q0vU>sPDwK#GoZCtTFxZ^5VCl!vIC!|2l1bdscyBd*^FHZ7DRnsYvjNzUMji0;;kM~>X+CjbmuU8 z-zST)$I7M9yaNC5nuTAldcd;GN$XX)40nB-tAxO#+n z4!KjLs%9SeoqehS|BBF{WiPMFT&muvm=8RsJby`f+yNgv4`7N8Su z8^@FlkA%iWo#e*LAt|r(5iN|!SevanD&~8%asn<{hMb~56IXK|BR%w4Pe89|o+gy2Yrw2(Zz>D|{O=M6 z9%V4nFdO(dh2N{c%(rqx+5=1687jTDJdrd5Jm_SsJBCh)k>a=Qy)tRG7?Vw!`S+oc zmohP%lzwe6ka1_S=Ft#h#BJH#@a($=38oQ4xjyR@8zP{vaKg_wRKCN=&q>_5Q|%Sh z$VTZ2$0y`0DH|!hjk0zw@;u^fHuBzgb{GfS{e=DS`4z=Au#ZJD>yHfU3+qi&GcZA&y zO^hYFYyn9^KtthA2Giooj~XHcoBO}@-d9~8ii~alD7AuEb22pK;bRo*7V=TPTZs%q zXTv@10z}LH#QCZ|W6LK|0&O{fGxUF|K9VtQieeg~zh||bZ42w3=hAsn&={Db+vUd0 zx*+9eC;V2!yzXny2b|~b$2c8d>XyG2)^HPAeP6;XVIvOgKmaw2o{F)%#+#!zsaJ2k zJ>Kw%CoZXQ5ImeC#YM34!shV5o=v-9_+6== zEIjDCQ*M4!V#mA$(U5i1qSgz2mk5y4`5h3>RDPb;bclGw?>maNS$kqJ9-OM%DpzKH zfT0Jc#1<9aQ8ObOp2&$D19)w;Fii4lh{t?qfrH z*HujnU-&emWqwd<+WWU+r9XgdF(H0oBp3d4+C+OVs!c+vBFO?r$i=WZrjx5waiLux zCO{}8Oes8hUiJwix>!hkHgHkfV&hnhQtX;>@zzJZqpVDwiE=4v8n$_-Q!lDf2DyH&9w@EBTIfv{5v zL^elrJAIK6U#@VmaTZ>z9m)}q_E9jR8(Gdy2{%%La||Ae46EmtEh9ahmjk@lL1}d# zELxU#wqGFp-L>_%lC*bdx*upIpLA!N9gBr2reaPr}izR3yf8L?V}| zkQ*+kyR(8hqHw@EP+0a`ajg1?`_py_O;Agum~TaSq<^1KD|rii&+!tQ_+vqEvj|n0 zyrZs+67?HoRZ~*$X&f;AN+3+SNht@uow>^)t@O6PY|N~GCu#=UcDKHwTQ)HfXbkdi z$z#pLmqZq1A-|#nw&Y%NGlp>+KwO~zSSY0Spf`^ar?-ES<^2~G16!3p)@%)}hM$dw zAt90gRZJ)Zz!ooM#`x9Mdk+-7Cq5pX7zSEV;;VB`Pssk3Kf8h!HI0`a87wul0h(RE zOS%9=Wpbl&U|sYNIMXMX`e);W+R`tbfynaH^u3chp!dZ3^GldZBzwn!<|^-jK{ zB*(cmA;#ZtHky7> zY|#H6cw6#JZ9Z%3Hg7BHhOa@4NKtGJtNlHh@%VMa8Fa7*$3RTm$^!o>UeUtsgYM!` zn3AiqsIl86A3d;Cm~%sW)I0vnGK7DE7hWUn+1OM+Ws>Tkb=-s@x0Jdv2lxgp-V~sO*))qgPGwfXsM-# z9~MO`AY;uGB3U@6B*nC2gMXc*oTULC{zi6p-cMr%DH>n)irM|)4*l!rM;El$%D&W} z*mr_kZEqwNwvMi;$iB<2$_N|)UngG7tb3SZjby3S)7rBH|5mDaw+Xl5?(PvZ(>=eY zXC3OCT1@5VFtbL@OBhb$T7WwJUZED@jc>(BY!~%yM7@sZl&O6-%y-8i*yRX~!r892 z^0Eut>P^CU-dJ#2KZ6lXUd_o}YP&&eo8lRJCiFn%tMLu;8t`&w#|_~;JIQk%{T>O^ z-L$B$RyG)D*Bmb~Vu?_L%x3y>l3_m(5#TI6rzr(S2S{Fx z@38Qx3c86KXn#E*S;)u!+M<|<4koSq@J@#zx;4sQ(h*M2&Hm|3|6%U1_VZ@DfVGx4 zgv+Wv!TFeG4?G!Iev&}|9jUz2f6vi+5n2T*A6V!Ue(yh5I3oOX-t5V`JIabnSfKYE ziBNlVG>G+Vaply8IwI1-cl{(+_hwoxBWLBB)t*y2@DX^ zglP^nZS_@IR}M}1w{C0L$U>w9@9Hy0OZxal?o~$B?x&D@7f_2egIcy%h&poL!)S1G z2Tw82`B#G#e?cDT9-DNj5BF);P4W}D+Se4MS1xjGaLqFDq;7Kdu% z7gbLiBhcmjDb`Bd@V79!d;d-xcZykM~*V=P$R*h!zxA1YPwo$+ppn3^tJYV9^v(Dk}O zBaD>Jm{eK=F$hSzaaZ?HPDr+EoNYW<(MMiqjoZb$QBv|zkH?nhUGJzh1^uXl8nlP( zZ+&|~F1`AWBq3=|N}4e3X5nMvN$NWO*Se5S+Ie+uG+JJ8ytx1RJ7b85VFgU1`^2aa zeqC|q_r%Y@3^-N1`qjt&uvU-a%wLc;9E{q!v)O!tL@LUBJ(5TnI0I&xe2C0uR0#bh z#*Ew{$M{P#+KL5d{D*(s-LeljQ1kp78xOlL4E+b8tZI(Kzwk$OrV2w1ARIrTG8l(v zu-ujf_G03$l#M86r6+8P24qETWC{du_7XkFhR!SMPv39mAw4$s)2R<7HI-caB9dUN z9KG?4x=IDN@<2?v*{%~jje0Yr!RJTX@^pz^^3TV<3uD{P*731}(yMB%@6VeW5B-xm z4;zUh``-EsHmU4^oi$Xi-rzLmvli1E!Z}X=)IFEje0S~18l)y)^p^Ydl~q44?B9RQ~J4~+c#fToQ}8u$ZYMLp|t9o9-A(neBgw%Kw;-6P5n)?y1@dBdU?DQOyrv4 z2I9WZ8QCW(X}}c^rI#hmY{U7$2D_}XvYV5%z^bl<`!_AYZ_w?#99#_GF z0AFBmU+>1*)oO5`Z=|9>&u0@8=&j|jT2YKp>6d6Uaaixm`e`A*c&M7C-Hx$Z<~D0R z$0QGT%{jd3ze=Xqt(o9Pm2VKb#f~)RnAn;?Aa$YcCp>{CyLib*@98Ug7}aE71xc0@P=G(~p)`ywTo0^h#6cLPXLpTI|&8XapVUya{;x?AH?Ep1d_Qm24QAHEylv z|K2Bc59i~!WP8nxR5xYvbCgn4IBU0gqx5%ZqkX=98T8GzUhjb1pJ zzKW0Z%XX?7t5<~y_6RDkhA1$RZjfwUlG6K$kVzq5_iz7#@7w#yZg$Vy^4?VWiwN$` zv4cS#KBh(%fy(a;_P~wJznqlzXuAl?8J;-$XJV1371e>bP854J=%1m?Dq7-QJ`(iy ze_L#>6sx(~0J6ThbuzRBB<#zW8nbUZc?fb?@)CP=JSJ$cS{0#$2a`i_@J+I zdAbSu8b~qS>mkRGN1wLtDpV&x6~uI3?0HxmyEU_3Dw3E9Kz3Le*9?PE{|@#Z0V4io zCok!J986d+HSQg~QK+h1qsd8J)NH9SfBsG5q_?ez`{YB}VQa{*zGjQegJUu9k`Qvd zjAFB^4~@AaRFgS;MOnnND?%;e9e)5Gru(tOEOqsqOUwp% zNwxTt+isLwb!*_$IKe~wXCml_{o<$X$;86t6pkTltBF9#s9zJckD%)l)y+Mr;;WD! zN|QoY7N7aQr4v5d@ABdP&ej@>7Y?FD5wen3#YQxbzSxmGKHT|B4HBx#ld5z5f~!Uq z`3LMnF~#bHT_~5vY0f{#m%!m)OfURt{QRs4gIRG8`)i3}fu%Z}jjV$MK9?a}$A zn!0FPn%@4GYpWLJ^*rxOHm0}QNe?u~LH?;d)$OQ(iDL@iIz5(6O~m)k!D5pYMZ9yr zzJH?)((Sx)Y$SvKnRu_U3JGj;O8$IuvK&{Tt^R&v4*Jh5Vd@8cYK? zc)}CpHKbe7ZLNOzJlLT@aD+j~*c>FfgS{uUgIa33HZKWs!?wOamvg4K^Snxt3!MPx zwy|CRf2^_3>FQ+25hb;^s??9;lLF64wLBZ=3UiZ*#}@S-DLqyH%>fWY<+}AE#D_uh zqnlsLrGV`0){}2d6Q1>#vU|bJ$HKdMobr$vGuh3F%??$+4j+juzW#D2i-IF>U~8+N z5}_iE-D>oex5L$Ao6TBG-}B#TM*YWmHDnUF{WP_bIqhV$^(T`Dc2LY|R`CQ;ld#$P zTg^@IaY96=DJ|2mw86T!=-|{p1u7Wcw(g_$tbYHhKN&O4QdkYDz0seX&i?f#lWeqL ztN@#?H!%qwcizSQX4bW19|KQtKm#w=E7->i;Cx=A%VFircbrA93;nm~Qd(v1=Ets3 z{uiOmIaJr*UGlp9x$!0~I|)TWX(6nivI_a4%aaU~cg-aSBDjT#D#xmf&8z|N%I8AZ zfT5cZ_*mn>MTauBrYA&bsMknL+eUFaNy6L5tb;PT7O>RI+!1cKbUSe0h%AwSOcNW{ z0&O;w$m4Uj@P&0Ow%tHeG6iI~%DBf4<*PqSjWJ*bYmi z6!>i}gtbxnE*{&Bz_b(rfA>S>9%zz8X44Re+325rfmg$I8gl&IFP)_))^6ex7NcsT z>XM%D*6ozn=2SNw8&E*$h-=eUU`__J^f(k<-`E>y3}FeYTLc_Hmwx#-+7n@#LIJ># zk;x&quE%|sm^)lQ={GPmK?fs^EZ~y?d$*wr-4=cW#NOuqiit*n9n{%BluI$JjPOV4 z&Hs7gFfm4Wym5LX*K~O;v%0?^gYCRn5CzI4xS3~%UumJ9OvJ`vy^#Y~r|r-kW1`be z%Ye6NFUbotrS*S)8Bs7vA3GqUUv{KWreLQ(vl1PUYrG>B2OnH2sJ1k{{vHH7DZD~d z*F(MwYFR&Ec{h-gNk9x>h+>m!;0}YA;Qy?ikdXrhTD@A61vE@|F>t6Zv>6Yd1+5m^ z`Co#Df@Nl!#CNsO_|A+qak5k!8@>KE%>hVSmF}u0#Xu*P0Yw?SKS7=GwjGKoYmo(0 zsk!Rx`RyYi#Y($)uOhja%v>0r<+SVjMVRQ{P(}6mKzPhp$j_m<4y9k7Q;N0f)}+hrGb?YFCsZ#tZs<{Bbh<7xB2aW@t@Db&-0+JwRIhYBdlOJb5C{wT}K4zS?@*p#kK+ zRw2;MOF#J^gV5nH{WG->rknR}`wtDyF1YhB!?&ooW|F6SAz(lT;{TaGy!cHt1iSn{ z&&EFydmU%}{k}y3zf$obF`qCq@wD4US_+DSxLNo(M5hoHX-HUVLotXA8Cm41=j@zD z#9j#92=N9I-)E()J_+mlMeev$91>o40^;w%%YIEOhSi@MlCI_OZ54g3 z$4IBc*RBOAHhxF-Ba2Pq?3NK{C$h_K@{KVMn-{B_T^AAQaYtdH6{9g@oBw5pM4Dzg zx6<~?nD`CUP47oojdH;Z!Dur0${D>u8AZo~0{mz}cktGuwB`=X-#bD-Ro;e6sevv> zF_W|Wle0Y34cRRR9&L^0sO@07Fyuu!nRWhMb-8+mW9_Pfhwp{2_#S%Z5N+9VC=&M1 zI_iRv{zOrsNn@RgIZ4=|XB>ErqxN?{waeCfj)hq~r>N+IYm)1PgNCVm zJpra8@})6f#d|C1o&I1HIW8^B#<}!5&UaQS5?{x;zF-2LjC*7T>GyM_IBdr3l;v|( z^@Gm@_b1KRqR?p0#fHz~xJ|u&CHPSUPHq}#QwTW}P3%ra6K@uUNuEADDXmPE2>_{X z^)!lHwII$BS;%)ryoFJI80XaI6neY~1Ew{5Z(^*wiJ8J51s_?vC|5Qa5;-J#FS(N4 zt(L{Xf4{=}ONs+3dCy7G9s0sf2I!c2POsNL8k~Q{W;e*G}3Nm zZ7=HEQ?DXhn=GRI_&tjUy@n(Yuc<2@2_H6oXL>I!46DHDkL`#ZV+O720aCO(?M*3v zu1{TTB(_QIbNkpnoA$&Gj8^`UKZ8Wv^3dVhutWHoKCVheLld$QRw`&peH6alwDhU< zum`PkC`@YzRlj@?)E`3yiK23JfdolItEl**Eo#00mT8FNg1)pKPNKT>^ds-iiNnp4 zkGr)GRG1Noe|Qrc*8|oe>P;m89A0IX^xIYe;tdnP_1Is?4CThc#yEC`otSt_OtC zlzf9>Ei*fv?=KpfJVAaNaoWTUkGx*fReS{RFdNJ&(gr@LevcicO9f2l${`qH(||V3 z$jE>M!&bR8%jX5ZHkQKqKPXu-x7*Hm{jSFE;V$p-AlCVeLm44eWrkc-?y1z~ z#AgtUlKl~;eT&;fL#jSoiE+jpJx~ok7_;4?pE-zCcs9;EoiPX0`Ms&A_XUL9%Vn#} z+s%!2=n^k)<)}snU9dT%4?h$x)HQ|y{C&ST4^&5IRT3!lB=|r~h|kz-)go{A{4U)w z8uMn)fxK7-1SW^{d+QqhC$7SXdC_8Ts>YdK^_NuDTF%=|qY7C|NMx4X$~3H38;B8M zr}Y1}(hvq=&IB2f8T+O)rSU`~@A ziG-!5hzS9xQLSxb;8l-$b*3VW?6`(coR%`zCAmSXqqtl9b#0G^y>{*!&Odi`n2sC5 zChXtqs+vAZ=iTUNgOw%8mNlbunqldJ6tY`<_tMG$xL#8osW-5$`e~Hr9<+Q=M6wI+ zx;=ugib&SX&>Pd8l+Cy_h>K-D*wtNDKl)AXlb=Jssgj*)>~^v#BSU*RZLH*6S4B=} z<0(GuolyK=i!C4X`1O+~4b*uanqP_bk-M%A)5j;@659?cV4i}M&~tQnc=^*>uxR zgmkphDCJdcQsFkQF6qf8tkNG64QPKI-mSewSlHM=uUiv3Lsk@;Vf2$_eqN0{UC1_| zZ32AagU@m<$oMesPt=h;ixZB%(&*dQb|Zf{{RXH()i? zUp3k4SjpxAJ9U63BeaC=(|2CAKN>9QYUv}HC$v#!I6~%Y<=}-^Nx*lt;ENU7Un%>0 z>U8(Q{&Xm&?ML7-yHPE^T-yeu^obq**M71-7bP@<{B9lp$5wr(ljqp=Jfq6)P}oQi z-;9vf4)mcc|)IvR{1x)?g!nx4<628Umf0kc3Twi|9T5!^8p19$b! zNg7-;goQe`WCvzM-8W=nO*7Cr@SDAIP)VTTshj;mer?iY_g0*e3&y_7+y2YXmhvhv z8hHPu@ckVPvL$HdQ!;oEmk+xp3_QIUb$eptbunZcP%`Bw{SbxlK&3~ncZWR(l639L z^>b=rJ4HbbyF~kVMaRM=JHZc?tIX^`O#5yAzO-w?LJm%m`1dC2c2TfM^=ua54Vf=E zP|S8Usr)6iR3>blCOj_GJN-{B!+kTo*%q3IC^vE(Q}tQCuf5gAuX~?g4X9Hy*3cnI z;?`ykY_TZj>9jR&7onv`3GQ)oL};(ufh7BrV6>A2lP_!2AB}EW?{%^Kuz+tYHSzu+ zYB`~69tU{A&Mvj{ip=Q03_>d^nHzN}BgOwsj}iZESl_L;9cjXtoyqukYB`78Qp;O;N$mx8Jkx(^ZI& zFXVv6VI8R|givk@I*?h0sKTN_*2{ivqO`Cn7ud=QN?YKn$r0{X4hk!mbl``WF$Rck ze0|TUn5G+}+-coVscIyY^knL$kF#cp!NST3)6tUny3GyX6!H9))@>GDepOO8#WrrX zeiQFWU4oCw?zcY?^rb|l%EO)rf1__0|EUm5D=RkyW)&Fma<}8$ONa~2_2NYxbqIQ8 zQSpZ5m4IE@G$ z+i?BbrF7lriqs3Q`tlcN_sWbQ7g3(VEq(oa-86;W55oj2lvUYmVYL(Uzy_!h=COz> zk($U=^LM8ytg7l|?+qmfl9jedx-u{WPyXq^@?mzqo-(!NDRxF#g{gAQci@EUrIYz# z{^;Pgd^P6CqIziNzM|dtwNkaP^)|bBsu#P3DwGI(39Qr!v7=9aNr0GihIHKFQ%|NM zo#mdAze=T=l3I($d$dZ)#@gVz?fsfB-#0(n4LZ=|)I$!)dUa^2M+Dt@i94ZS+}@Bxl$B-@AW z7TB1*zdWPD!`y}^Qr+N0Y?!t8>w*D|woUvd}grc?Qyyt0wY2;Fn+hM6Z8&@hAW z-ea~=*ZN3$a)2nW-<*NEH z?7odZ?+*N~KsuHwXj9hT+oG1008~3|N~``Rt-Qt1Cl4!{iyQ7RMv;`v@Iz1GyRQKW z9Or_dQS~_G30AOFeS*8Gf1jmS)MCeOflu9Vo|PfnzV!$D&H4p`FGd%+sVfTd`*8)m z|KpUyM3E|!3|Y@H?+hHbff7OE$J6GMi2oLYT?l~X7;ZJ4NI_as_0EfS#`o}#q!E2+Lp>qa;??$ z?Ruk%V(w;o_Tv4BV`@K9mh&0XtuPg729)(>~7s**C=Pkpy~?5>JEesYfV z+}Tvr#?cpygrRe_5L%>W=75WAh11+zv40F`dK;=BsQ7 zXK{I#V!C*x*NR_4ij!>IU(20+=1cz9Vqzo$n;x$btb0zsj%Tu~dHyVxo(;duLLLo{ zOcRGvB{3!~%ug$D2`U@?B%P zNb|HE-_nhmCK$tXB&O`pxMh5&GW6Sf_z=R#PTg%cN-li-!LDOuWgqhFD=f5LxyM}V znIwva%9cqJ&CWSc4xcuwYyx^s_twi=N*XvwEGRsD)-j%aZm0BoCN&xOfX4#beF)w< zjmNEA=S{$8i&QT%`KKGcb`$=&hj>MaMCJnmmdEC3q#K5}gD zrP|Zg)T?Fs4f`s)ONf*xL>x8UgOhqEUdk;Kg7^Vetj@LewbcgcHKoiueTi6i>KLmj zdOhL+>tEk^4Cu(@;3qdZLL^YBDUHz3c3M~4=2w&l*D@{RDTamU4#^hB?48C$e$-}| zH0GEL;<=7@yj9lvTyYy8yq~#GAeN7v#zWzKV^eu%>kv*PrX%Akuc$$K2z-9wg3EG_ zBX_LOPI*nf$9AS&pujf;RW?qXnlLU16XMN4U%f-OPoM+qE@*K00?vt9;s2xH&i|Qk z95{}X&}CDSYphcFlut?S*jDM2`iNCR<(T7Bj=ANSU8Lx6rrd2wCB!KAHF676j>*l& zFpQ1a?fdxt7q9pGhi4+wK9~L@9A?HU+`yd8t{2KEu3xn{&?r9D;1mC3^38-uV>|Jm zmG@UOpUXI?EJsr@m2Wjrrq5}6ZGjIvzue!NOVdL4m$yR8kN5?pq~>FPc%5Tr79ekt z*zTQ`E0v*I)RSFSe%=c{m^MHvJa|>RP!`nVlVBYG7AydrxcH{W_%6WnPHXPBf3_P0 zkvaO5G-ryEQ)Ox3PQPUzh$8()rJgr*j0@#kIikV)xzM1$X2fBM+i~Ue+-l2vDASY# zuO>_1isX?L%OpT$VY*a?Q4&b*ZCWtb?yf>gF!zfEH`bUem*&mYbM>QGVHbv&H*yen zQ}A+baSf|S8qo1i?5zVNCGyqCwN~?2t?`ORF>$&CDN5$T8F-TXdFboktUXYVFZ-c_ zwxIpQT-@KZy8Lve6*=;U^fep$1?Wg}-3%Z!3-)+DfBVZYuVG|e&C72nnOgk-y?K@% zL_7M|_?rB7o{#n;>w^-Sta^~zn7r{Xa9{Ss}fH+HDUrY%jh#x z%zb?$Ad&$#N6o15nD}42-LHtXw9DQL(5MYNh-kMm>_#Hl{zNk|^T#y+CS*NUgWLSl zX@eKIscQb>-pk>gDA4wa9#Kpph?+`d@{=~KVrYGLL z=K!G%K!5sqLeoM?jbNV_w|1o%u{8h>%>IORM^hDm0Tj%PRggJo&sxci4xoDE_~p=` zFxOw9Qat)Xl!bpAQ0a#pjBusv*;*B}TyX{Kt!*-rG*Py3!jN%ZOEMGnm67ViNX+DF zNV>);S+EWul%S}V>^db6_g8tpx@y8}^f}1er~6W9QzN5MS!Odhl1V#&TdLU7>M$#k zToP0|bQEcU3{l(a-iH2t62i1VcqEdyxk(BPhmM)@9Lt>U!1nIK-s zT}+YAbj;IVeP*@PTOtTQW3bs zggEe@wx@^Bn1n+KR|Zh8I~(~^&We|0yc_Z=90%?-@d5LhN;xlOUc{=0N6SZyemQ0$ zXm?AV@w=$*BNGOM`3=~LIywYQw`=V7U0tj zKR+0)x4Z;=ydVscrl@xZ_ucC-%7D5d(samfAfeP)apY)1Y#OHeBqg*v3>FID$|s#C z^3&2SNU@2|XqSytqf)Hq9je8bbp_SKyLsv$Dde4T^dh6xjG13ccQHWRY7D`W-dc3j zTnm;$#xSpJ=ly7KWg63>H`y1L48&Iqn=cNAVcqhtEkC9_OXcWR6AT)U+_h!%5aJ-e zHp`b71I`#3q$WYvHmm12y$6cESEpwms|?q<1yQrAU3;s z&`XO6s+FhQunoN^uW&Q=Fmt)d?I%GOE}lTn7bG^ljfZ%BZN)$E&*odTR9+$8qag4g zXjrN2{WOcUv)ciVFezQYsCu!g=?Bg-a3+XQ%@aNT5v%cBMc?Cg{Jo%+*5qL4z@e)U zUQ78oc0QPM4ZaZ6IF$hdEj7yYl+3jxAA^}LmOJnwu`yFWG4(B?=2jWiI^nZ>&MkQU0LqShjfy5z`)>z=Z1} z;gfP9dI^_>bw0W^4qLJxwJek1K3}L}b0X5bS1)C7)My;?sA)Ale$CM9wCSvT2yL9q zp-a3b{f@}XML#<>a4ETvBq~?k={m9_VDmmpc=-=DI!T-H2i$?0)3=DByIPYX?{&4p zdIld!)`6Y|;0K;903wqcxmYG3#Pz)ms=W3l9f^)wgdM&`45w@aX45pIq`l@=u0_6e4@l{ITO zkemI+hSmN#lD4Srz#m}ScY4x|0$VgUV*+xw|4l;Vb@}9ONm}=As9ByxmjV5U&abJY z+}b(M)lC+zb3dRZase5ic;dnN;J~WnLRz@BLJR$bm8-ZP{OTD+r-py}T*%zgGx*<% z0{RuJ0A6d7(KU+^PryOQwFJKUD^qlbk*e6pi>|PlI}}FBsdP^&t4Ng^!EH=6XhjhcuHthzjN<#OptB1lu&!h*JXlHuv z07{1)5wV;sr{K$5*CRu)kNb20zIfIhPWQvD6T4@Yt`UPk_u(3KQKy>uN{6sZB&I?2 z+`#Lfn5}n6oK;C-<)c`JRb(`YlFAoXg4&0<8$+;wwS9G(T}n9qn)BZP4{wV@Au_vZ zDl$U}O%@HF&Fy+?zpPwAE0Tj3d{K+?uue~fu>|0nqUTHy{>!VpLmM%*N!jB2CX6Ht zw*%)oX!(H@+LSaXm%}@K8gLAF+&>m9p}^E zi^aC%l#k1zMF;T_%RYG*wr&}vEiWCS?5xt&H17o(L&&4oHfNa}YQO6rAhUk0YVH9{ zhs@>)d#69b_@dbbpg(XUKjo6 z>r0^j=^0CUPVAble)+SVeCD*NxJg65X*&)-ps>>$ENNv`ALMxr{bH7v&CY&{JJspT zRprOVcOY*Y3(lMPMV14vWiqNX5AAdU4v@j0rZd=xfB%7+ac(Eege#c6sJ#YVRlX26 z*vEoNx3|U}!GBwQ!HhP@c}`W)A1h6TBUV>#t78N!Z~)|}oduPSb0AHIzE>z!sg+o_ z^v3$n341{#*<=yjr}OH~zQX>~PJqd6y)I<4|w)P!jRE7v&N2SuV`I zoxC@shCh-ow%3#JBpW|b<#o~0!DZ18Dc^{IHXP?Ox|%Vy4VxPoRmPJY*)<69d$Ef9 zhk);L^HKe-3ZCw??q~2|%*(w=;92_Kj`vvpFQZ8Au=?V?cqmo7v(ida5pnX~PgTQh z(-0p^rR8L*kw@Do%naxE4tapvBoO~~)N3cs2E0)NoRi%CFV|Rj7tzhR7otS0LMFcQ z-%&rfezCns4jk(Ue%8M2ylFkPJzy%2=}@vyt6fuqSYf5H!fMf2Y*3Ldx%|QUIL+{&Y~8K2;Ajn z{m+-oxnW?%X^AFAFq~#qW@qkqZ)1Ozr+Yq(TpfN3cn(Fe1Z7u6Zxkt~P(=^OEwwcj zZ^vnXwPU28mjR^Y4xgOxUzg+2z`D4s4QTI-R}zqvFsO%a`JORN^`S=HVKV8_A=e22 z$UiLkzAa2mD*0SgD>v+;3U-Z>%7o(>L0r-woa9AB4%T@9dm7H0H?J;cwU0kN9Pp${ zFhENoYUanqLz?8RU~;{t>rz-ib<&sH0P$^00yFF?f}Q|Tz_sntwoPz1t!b^C%YN2J zg^@cJQ$cc0lJnB?JwW-&7T~RrgtWnX^S4YQ0V`h%M~@@K-)^Uc*)?slA^(wkmf%R; z>&a{8$cKj+r?|DiS*DMFjsY2MPuVn+%v}uEX(`~AZ`D600xsBKMgBl?F>}O%sJk!rTb_n zrPVISw$6h9*)OQK4fOo!XIS#z+vmz&zN?FBR+&lR2$4;2@1W0EaK$fe&&7gVWKcJH zVuq+VXJQ! zw5vm3oDO(kwrV|&uBgFN&KkwsYgioejevxb*)`@>Dw7;YjY85sR>>!eSrwRDqsZak zqnb*0Yn4+N)gHdK?(@JFos!70J8epo0hyIQsFKSpEgJ5}sRfI~c+$o%a;U)2L;$EK zL%Af^NZ;?<-&*&zI`}tZFKoP+b2o9>_8OyG$d@OWTM@38R?t(6jSfV?l)lO+=2JL! z*TI<8y!NeLeP9rws|GoL63~~qU1klq_m>+09ikvKS$?spYYTPXvDLQm!es{(8i7H%lRK0q!?0v7wSuJDTXbbef=TcDleLMW&SN!gLN@1CLXYbC05*!;D zdU?Lof?qK0sgb&W?H0N7V$EgxX;rW>zyx-x!pIV_8d~}bImsC z`udCUVJnZ$cdkJI0L=pzgTjyTOI3_!An8cc8m*PD<-!orrDL~aD}w`=m)=Y{CzC!w z1MYxjLd({M$1THsaBDBP>tS*&d!c8jlM)rURutRjQM)_P?bz1a*tTmU8<4hmLs9uz z$=DuAmjmXmCRj7iXRuTL(wOME3RI%;PFsil+-(CkC)`Xf^b9)`x(vU zc0~L}K)x2nR{&Xq`aRMAr<8_XESFm;b~QIdRyeE(!W3oap7QO$l{Qqug9(&3$CIsQ zDs7fkI2m_p+OxvOfD%S?;I9p)=kD#vSw+--QQ$4-gW)ah`;lW!AJR$XW-Lt;To9To z#>)k670$7Q`lugzlL0&bWd8`v-)*)BWBVZS7t82-Vu8BoS4m{i3msz%>D2uR1!p^2 z@~j|x?GI#l@?=&H3Z>7!+AM4`Ek{iwC2u^xY@GxkVCk=!IG-0K zn2@@u-Ly03vh_m%=>59DK(DcEJLd)4hTsiwy_aZdUm0f}elMC|;nLaYs898A)QU`u zQ5wfCs0I-Zdf_R*qAbdPAuJlpM~D-za|mMzx|;=NLEjS%IM#5}@j;E<_Ez)VJJi5G zpCilm2JAW7Y4g?ND#OSmy5+Ns%pTXcD}R2)*Xlcu=0MqdNB)^wuf0Y;W;|k-0KJ{O z9~$X`T+1_>Y3*#(09)JWT>8zm{JTk}$^9Y-l3oQq^8HUNZvNoRq27phx2BNo+?s{K zxPb8aUm5CS*t6B$gYg0SfG!`s^*p1D$`@<{Ab-L#+1~a;KtUjQ6{^EaMLW6kCJ&|A!qmO$Q)D$}*HuxTyj|IT*a^k|%Y!QZD zmozcmcF-Vxx`LOb(@0tQzPBi;M+dg0MnX@sFp}`uQ=)`4jaRJ~F(K;XKbOk?bhd!O zlk2}8V?MURx^mR|wEmXN*2Nzb8F=L7n|QVoL*KBXMpN^RJ1q!?VG$0obyJa@Ick5SjLo1t7@Te5wv+G0MIA48tgiFK@_aO@+E+{zs z*z^pHv%SN&ua>ZdeRIeEIyS_pSY0&Qw`#Vm|7qh*tj_!U$vP*-5>of0MK1n=-O3N} z_wwL&1JWx#Ja;S7FGTGOLrZfafBUX=d|n#CSl`>1(hsh<&q8vrZ>eP%(>H zxJc4p;NBavv+yr9*w}2p=0AVImROYsEe+qy-W_!)n+4)D8%@$&iA_!YGK8v)LAYu2FE#J@BSk4b$uxVVf&fQWv5#Pi(74hCl z0WHYhANe=(CA>7`P{r!>3&*m!pd@ig09*Fg;WBhzyz-i*Ib8KGiJ|N}DR`1-bMW?p z7jdq;(0H?D0pwoJQ4P7U7;?R@tU<=fOf#H1X5IKmSXdzW{Kre>F5wK3-3G5&s&3k} zgR$rx%-&YH!@()?4(0utgYF#S#$XpO@Tlac#n4qbSKGGy>8=;}YY@rh1NPbZOo|2m znugiLWeU*TbODt#`BbY?V>s~N=YQw|E6wQKj-dq`*IcdxY!=ml()3XL&S>@6`4`-F zt5bb}eaX_{A?mF(vaamL+>IVApQ6etQ^}Rc1#;21*rBKY94% zCpV=R>V+P#OBy{lbHS10+|?RB^*8Q7Zb~rDZzXtspif+b@JysAz*qU;g z^Y<+`-J|cckG4vpu@++Xw!}=X|Adzc zPnr>y1Bk$&@qm5!ty1g?a;V(3l1LWZCOOqiIyW7;w7O{&@iL+m8H-IY z`@c2i)463VlZ@G_*_+7 zF7|}u2e}VfSk0*)2K(3Y4Y~*MqQ0v1>1%t*_TgnOznA8Bwsu<8@y2od4L~+{Z>*(c z-G{l|dYq-G-63%fZ5!Y21FW=SMpx{>j;q5WO1(lp#-d zz$$~z(YTrC`;GlDD?`bIga}9|Z+?mxwd~Z}WZH7t;`;=zbZ4F}f!7WH{l?whyXqEE zO5XjZ(N(iOt5SsI&AotTrGt3B3E>R@zt{BM<5w2Y8zs>@9YyZ4;Owx3_5eryF>QH- z%e(5FJ~Lk$f0#-&(f|>m4sKsVf@Hp{@K*XVkvX;)g zL#~`5TbJNivg7AQ6+cTKcGpi+PgiRIB#kt&`zqFIAsDP2A2b4%$J_}9L90B0(SBdk`L5SHLh>s=E;&5AAf)1o+Wb%3@sW`2m3 zh9X_liI9R{W*iuZ%zmj;Jw;P|47vvh5CX zQFbXh-dXT)f0y*gW4brQe{mBDp&z0f>G4l@rWfIMahXO+1yU4?*}F*010cv9CpH|$ zAgn#2M+CUGi2o!Z3L6E~Jbu=Gva24=2MKh(I%elu3|m*?vS!@ByEZr05I34&3@PS8 zo7JXG9lcm+jHX52!L0n?p^NJ$Mxz!7H4JN(CL$0hXkTh9j4>O(e`n!zppkG+Rg-XK0S$VaQ@a@evym$;D+u|kg0(OFCi14z4?1o>p1Yz+Ng%- zN-P#UGMdd!ePfa<*ryv*Vsg56SD@>Q?*(I0ejj}Id}OuzSbq)qy^#0V|6VYGq#trs zkO05l&&)FbTbPYaX=52}+jaC|z4uV`&0JnFRMy36S+!vG;?#$pW0}RI@16D$H!Zl| zqXK2i_sXo?>_V~UUl`2IPl4;?C=2XRl5E@B0~!Q|xElf!(oF(Mf@b_$U~BtJvW|*2 zd`BhlWf*PMSR=Vn`qeT|L|-ds z7Xs5Tzf}?{KV-3tx{<4Ii0chsA1e4$ca?+z%(63}tH0l+IS)lC zxNRGu=!by-D6=j6BTKw!IgY-Ad{H_UV6DY0(yD6v(~0%%NM2X^s1u`6xIFXOe+K}) zoMSdc3Qk(FgsRJgYw?B_;6ZY@mB&tz_&fQ5@OzO(934~I`w0A(_qo3F<|c%hATEAo ziR{g7Rm$bPN@oV_(t>pp9W z?RmM}<*p@p0k%jmbyxsuAJ@tb6X{8QWg_cutShS^juj413CAxPMkH&q#sdEJelF-& zs{sVKvXdIJ#LJ&cnCGWM9YD;zf&m}LrnaDz%wp8D&D7Yj$PAf)eCX;}V23WzOdamT zJSxb>+R%DnGGz!>sxS;pYW>LOJ!p%&7C;H6rzxWywL%Wnl_K;yPEwe^PbE9Ee2_Jd zoM%{;Ew4cfZrL241M1E`GLlD%qY@d}@BAS~VlQ#?rBl1ZM%wB-lcqrQh!~Mr&2-

=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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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 zcmc&+3w#vS)jttJc)vkJFk1w{A|wG31k9U2BrmfNf>mI$J4r@%XV%$SNFV}LXsfM{ zDvu&isGpWvtyR#XDUYC5KoAO6&{E}9gcK?Wq&&lXzyI7jyR*seW(nW#>;3)Up1C{c z+;h)4_ndPdlZis5Q&0t4R(i25U6KX0(q^OHbg~aoTx;@|u372D6GXu&s^cm&QT9rT z{0I@n5K$}?TYhLcRTR|hBXLC6EUmG*ima7qV{Bbw0wZXV zDO6^+Bcg$p#+DNlpPGFu5v5pKTjff!PZP86A)+bPcDv|MWM?)w*DUSrawV5b^0FVz zG5O0z{^md;iZcePsP=wD1dZ4;CnlgSaQVt4*=w7{q-z_Nm~`LJ#4B+`w}53LYGZeZ zvY0KGD)t^kG~T6@2rfKQOyCy`da_qkGX+h!^%gex4n))vVh{Q?k^c(i`UUI+(=Gg* zQ|nw&6F_TPxVL?lA7Up-noG0;{W(i>oZVOQnCQ@qAAW23p;eksD_2yby~fhsdZJi4 zTTz`}7aNw7ZSm-Cii;xZJsTa;NRLFM0Zfyed*s^Sq17DwRpH;4&UE6t_q5$xbFPNNqnK zP&&itZNaEY^LSj6!Z@sDBT{068Aw7h+)cZ=frXuF-%lbFjbj?Cc?i>AD=tP7>n6p86rGUQc^DXREXgHC80!Cr#lGB%nZy7#@-{@2BX~8t zFiUiXv58ZBL>U3e`%#)@6!*n35k0^N-e%>&N-%<`OsiPPNu@GUeZrB7&MXddYI%%W z&md~188p)_Fg2nm39VT)fJwH3y<|HJI}_2Mb%T#>y5}!D|FpIG?lBwT7 zUDPy5F7qDcodiWJqy()jI6)qMSQ};!cQdiKf*1FsRY+dLp-Zx6%;s!a6U#&vhK$he z=4eA0TH64cR3=A8UCU9CIm8qgvmi@VWG!;12=T``CiEFoU`NmnU^dxrd|NhBOTQV4 zqBQ0$ zmMm#@hu{*k1P5|0=-sLKN?f7==K0(uT-}+>($1!MITb$LVe0;I+&Hxz?TC;*Okg~< zN8ECkNTRG|C@w|i?$D*Eh2zuhFlW{bW;jQofL`s{NYfZqSV*Q=D)?MlhTs-eAxvoM zPDBN&;`BL0)m9*?UXSQN!ZDGP;|?3a711eIaf*f)6lI?~5Sfh3`ALq#=M%;Slmb=J z6o=vpg$C2!$kDiQLPd62@Z`%`st~?INTZ%(4i603SRr@>*{%tyW(aSh(FDhB0Km&B zoc68?;DHWIX2SxS2oO*_Vevb^UAuBB5k1B6E!LIc^Jdf?Sx}LehIC`PuIm_o=rRE+;H8b3RC-f8 z1{O=P8H!KV3~xB56I&7>#cFN7oU_LM$*pVV6EO>c4I7J)&KAl+uDH5mkO*KpcEzVU z#C*0kz#LTq%LIdLi%v{=W0(lKVHz2xnp6;JX1LcEc4HBo$683L z>CpZ1a+KPX#=!f$cOC#>g6S?%c7_YK80A%t!*e6J-$F$*=4zV5;dA?31{{gRnC?tQ zJo+K^H}axr@#k@T&H@~upY8NYkKpwhLz$eT$YuD)f&rNK&z~sEsh-Rvk$az$_XUM#zK39{J7d;Fg&`y);cmK{@IaFCv^ zROCvsF`Ob1N$K!RV>+-0Lu3&$zaBjqgAFMP70sns*ppcX_lZcn3slLC&EX=+E0svt zuL?CeFlV3P$b5!FXBmQ5^V;m?f>W984TtwJ$Ft;tg`z{0W{FKTe6W)q)1x-{X z!FAWIA)Vh792GXw*oxp1vq~!LKDAVEh&cil5Sj-|WL|>oY5lECxZL9#xlXy@l;ko@ z*5I8*;Isc=shsjjP5|4!F$G4v%k{Z5$#A@(qzfvedNCv6J8aO(IJe~WO0z`fx<j z=+x?cLoGKddIdQTL_U+AWnE=iOa)Oqo3f4b!(Aw(5VQyi$qsX&J4_d90b1S0*x@Tp z43CqGikr!|1~JwL~hZzgXsA+MbbnWlE_>yY{?qr;Ry`S_#t<~ z4I;da<3)_{SjBa-EWd(0a%NvPUP~hMkPiVt#!05EiK<62)-srh4K_Y7s8xNLLMAf9 zGgbjS7t1d#^@>_^XKo!Q{2(I?uVxZ1kY*{(rki^QW7t9$nV%$=-5kHKa=MU8OaTjR zjP6abd-x8fpP`H_CZ#d4S&GXR{o&%?{qc=-W5K&K)KiKB<3A@md{#CKp2oN%;|Bg#!<_fP$*ADg0lKO7rB1*+(m4HkLLSQRDS z$*5XOK4*GFw6IJr;b?Op4>(go(`#Pc%9UJDb9@VhiwxyOho!s^dYo7+J&q%8*JH?bafGi~!wjhs1Gk~0Ojr%;f~#Hb?d z)W|fV3a+an(}*gr9tlB1sJOAQ4ESMVd01#fH5Mr{ji|=*Hm7kvvsS2_>&33AqFknx zN2QvJO_GUfM9G7hRKjx;)u0xIAj5=vLxbAYB$=oN)$N|fg9_#*szDWVG9#I$km3}m z#)+mgO)hgqH-%O;K_sdH9c_Y0RKw{tgqZhhUiN zWDiS2BjU{;!gOFgcq&(Bhgv>ii}f%3b$){Y*9-H+G66R*3Pja`E34&#N3{Dq9!1r{ zQE4}nb;_yWB!}PqNYgY`D#2}K*(p{;z7a5%Q@NAr9Y=FnswmD0yS8>WM##y}OjjyG z@rfnSj-f1K@sNTOqa26JW(`)M+JG9geM}*?*Wwd`GGXinfwn(VHEM{8s+Uvcd#A|X zK-G*<4WAp)3>Bf)CopaC?IvVtmQ$4mk0B_wN@Vza>l9}a0el-zF9q2lM*7L1i~ky5 z2#B&&TTb!f*ewKa&vEQF2&})gc`}!i+NG|iaT^3oMwa?)E*gpJ8H|zTg z&T#$@zaP|YmQe3wE43m0)I0YaMf;it{2ZQrtd_qY#Q%(r%6DjQI9?mkS%Xdk`K0pq zYZ}P2hSOiuK>jw)57h_jEA`AL2LB6c9uL+dFIN46k5KwWdno-He2364+C%9V?V6lzt8RA@qy(Q2I6K zgwU@+Cxm{{ZkC4jW)^F^|NWyQnMWAXf~aeO;;JlDaF<#WD~vcMZRglF{xu3hDWcvf zUDpBQULpMAzjR#(jMs2$vsTx2z*u+VSmUIw>ws~jfK9yC8?88S0-|D&!7&}^s z&Bt|J2aG)_obUcq*LA=+Q|n7a$8=o>3`chd5gpZa9WdfVJP{qybsaE*kqt`JbsaEP zDe#$xbX^CGBn969eWU9-U_?cP<*#*J2aI$CUswD?*LA>HoZ+VCL0#7Y<8TSLN&c?u zI$+!s!*TIfx~>C8!FusaE-#b=Tvm`DV8{uw0}L7A88GC7XTXpRo&iHHcm@oa;2ALF zfoH&w1)c#z4tNF(8Q>W(#*b&f7(1Q;W88QKj4|UGFvg2#z!)o@0b`ta28{9H88F5T zTiLJcI%?PnVAQY?z^Gv#fKkIX0HcOo07ea)0E`;;02noF0WfOV0btay0l=uCf551r zd%&oncfhEjbHJ#fZ@{RbYrv?XXTYeTW5B2}PQbsspUtIJF+{QBoMoa{GCt%Yq8FfR zu1=^3VuvV!ieY0YU=Lj)ea4Xl_IPpK4Y?*hufd&YEWWX?iHray12ceXp)RNZqJ_91 z0t^eRBC*DS4zeJu)khOiGLDgvb`lw9Q^*rxc3=}`2GfB#K*dlU)BzDgY!C(ZD{(C! zw*YV}39CDN?SX?#WJ^dwv0GAzY@UdUiRec}G>wP^TmvVfGMoYvk(-D-M1(7xvxumY zh^mNaJ`p`hM2m>%XZV(lh@OQkiRdLFT1rIAiD)GettO&Zi0CyU`W+FyK}2s6(L1=? zNkkinsG5j2!QT?mM?|!as|(}pWTr~yCyrp?*|7nEbT#gLjB^=>)9g^+NVLybi1 zD}@wC2asDD_hX^|vAh*}@0lxjJd#|7ZwFO&`O~;uis_bK8RUyy91SzKm;cscLqr%u zrlg7v?E%jDrWm3)#;w;`%sW{O>9-p}R(d1We zzu)(1#^odH_x$_<`F5OHb@j!0JCCPrP=Ei*{AJ%w+4uU;%Li9nzxZnXhNMGN$Nu2! zPhWqgazxVfBQHJYd)odFec$wL1Haz>`kv`$hTglf;=H_M*Y3`a~s~tKT=zm zQd{&~{`pCpljF13R{wL#ug^XD`|;IFj)9Ar)7ID@J-Yq;s6AUgsIGdk@+JA(#ZUb9 zVyUg~mA&=bj%_{Bq4vt=9dDgUeo;PNT))4%DEV@>KDfU4YM-6T@nY|v&h1E9n(Wij zytdEw{40O!GkE?zTb7((QFZQO_QuUWD!ll@9a~Oc&+mWGV@s(m>UdeN6I#ZdDEaI} z@re=pH-B=p_{6aH^x}&neDX_Yx5Nx=apIu`T%nl9t+_&{I1!vNV=*f$VjMH*$DRH& zEmYTA>F472{kV)}lw77<7J z4{#97tP!Eyf+y+L2N=m4H0?Z&2JaQ#wBd`4gk+M!EZm;Ldizfd(JhmUoS5_wMXh1n zlNXPf*gviNqQS$ztVvzkVbzy4HM`$y@$9blvBE3YW2=|D-ucawl>>(j>es8y%Wd`@ z&6@bsx>MB&@yEKYE}t>&n`&QP?t%V)8M$HirrCKHZ*%0#opWZ+uI*(G-?r6XFY5H^ z<(g~1J+XV&sn_-&Sh}=+>X3V;4T{fuVz0gbsIu9w$?@OiEq*?I)0pH-`IQfxeC_%{ z@1pCo)aze*cVFIJzwhcNRp$qu`uaCV&V2gU6?^8!%~ASY*btvmfA$|+tNvVHw|4*B zt9_-uSJ%tmU;W)5&tCm1<)dw=M6jb?{J?w+dkyQ@z8b@-v4^~(>9+Oq7-_(RKlm#0@2W^Ep@Y|pPd zoE!2%)%UxqCJtS4eDLNie|&h`^*4tf`rGhBr{m8*dwJjUm-p9ynX+fy6JN)j|L3Nv zUUTEhM{C~2+=`LB#!W72oUy_d5xx)t5wgrI1ZRq9Gcyr{TeO+k2tpg$%!~xn4sB*u zg6o=SGcyx}YP6Zz3Bn@U%nSvg7j0&if*XlwGcy&0d9<0?3PLK{%!~zL8*OIRf(=8o znVAbx1hkpi3&Jtl%nSx0A8lq9gL7!KnVAgI3$!7dLHdDEZhXv+TR=_BD23&ye&G>~c)JTgEw^8E+1u$x)Uw~2LE)rnWxaI>GHT)=G)JWL?qsEaT zVAM#z0Ha3Q1Q<2aJiw^&RW4xENZA0RMydrEHBv*ssIjyIj2dYkVAM#*0Hek@0SA>A zGMIv)#O~IZU|!iZBEw-z)* zefalr%oABIu%NJe#cuXL0;W<*Kb0=@%RlFP3)` z@)G|VeCD-^qw{+RaK6SlmvO;8s@v#iQ_k`c@sl3pDCS2N(Oi!RW%}t4G9h8${PQC& z^X5`q+?XPlQxg=Ytc!hQ$G0h&jeva*GRa%66Xtsa2fjH%`WEqeWMB`uahaCGrU&xN zj=`U{=Fms)ut|c*;{P%@#{b!>F-IP<9C-y<^1_Kh93yFr;uBQ!GxV(-`-VP2_1yQ1 zkCA-0$YEXI_$R1l_nDl+4S#}avJk%fflC|?DIjqpT<83C=<0x5N*BDM(ac0{fQ?eY z;4&8}FE8yf6j{a}E>L9eW!@*!k(dlSJZgTvKYaT&Xv<~B5d50Sl;0l4hkVUE(e#s* zL%wEuUjJ)BWd`zhUSeT#-nVj@4~DOahSJ*qM}Oc} z@#^%%#H68`Ftc|aTQ$KWwK1#|e=@*t4Sua=S>Eg5-`xxSZj|N9#y48;-TF=0x_RF} 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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 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 Date: Sat, 2 Sep 2017 21:23:13 +1200 Subject: [PATCH 271/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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/385] 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 561c3e4653011bd4cf961877db05d22b13b87d73 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Sep 2017 17:24:07 +1200 Subject: [PATCH 286/385] 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 287/385] 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 288/385] 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 ce15d0ecd0ed8ea8b9a4d1b64af5ad8d12a3c01f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Sep 2017 15:18:42 +1200 Subject: [PATCH 289/385] 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 290/385] 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 291/385] 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 292/385] 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 293/385] 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 294/385] 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 295/385] 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 296/385] 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 297/385] 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 298/385] 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 299/385] 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 300/385] 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 301/385] 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 302/385] 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 303/385] 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 18ce5ba30f5c1e0a0487e00a4942acd866be374f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Sep 2017 08:53:20 +1200 Subject: [PATCH 304/385] 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 305/385] 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 306/385] 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 307/385] 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 308/385] 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 309/385] 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 310/385] 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 311/385] 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 312/385] 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 313/385] 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 314/385] 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 315/385] 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 316/385] 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 317/385] 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 318/385] 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 319/385] 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 320/385] 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 41647305ed8412e9b3aaa9ff90f6007296ef0590 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 20 Sep 2017 11:11:18 +1200 Subject: [PATCH 321/385] 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 322/385] 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 323/385] 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 324/385] 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 f58b2a3bed5ba07933f7f97f6d9e4e6088212da7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Sep 2017 21:59:19 +1200 Subject: [PATCH 325/385] 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 a757012c10702ca04ddb69141a0d8cbe056323fd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Sep 2017 08:17:35 +1200 Subject: [PATCH 326/385] 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 327/385] 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 328/385] 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 329/385] 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 330/385] 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 331/385] 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 332/385] 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 333/385] 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 334/385] 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 335/385] 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 336/385] 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 337/385] 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 d64e3aca550f27a04ccda0354d72fd66f2c29a14 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Sep 2017 11:10:05 +1300 Subject: [PATCH 338/385] 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 339/385] 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 340/385] 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 341/385] 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 342/385] 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 343/385] 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 344/385] 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 345/385] 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 346/385] 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 57cec5058395501cd64725f65bd597ed08572797 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Sep 2017 12:18:09 +1300 Subject: [PATCH 347/385] 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 348/385] 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 349/385] 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 350/385] 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 fd917917c40004ecfc8125981502bccaad969180 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 29 Sep 2017 14:48:01 -0400 Subject: [PATCH 351/385] 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 0daa5012ca2dce766c1c6a5c7cb344bd49448917 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 30 Sep 2017 15:00:15 +1300 Subject: [PATCH 352/385] 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 353/385] 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 354/385] 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 355/385] 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 356/385] 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 357/385] += 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 358/385] 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 ca11d19b3ea611e11345953d5a528a2e0978bbd8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 2 Oct 2017 16:06:44 -0700 Subject: [PATCH 359/385] 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 360/385] 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 361/385] 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 362/385] 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 363/385] 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 364/385] 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 365/385] 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 215193ad90425a980ac6cad229edbe9331b9c04c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 12:05:13 -0700 Subject: [PATCH 366/385] 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 3a6e84e681a070e2bf1f809c9ca151ebd1c15f8a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 4 Oct 2017 17:22:21 -0700 Subject: [PATCH 367/385] 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 4855e0f528a55dbd58ee66609dbd16c22feb8a69 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 5 Oct 2017 14:36:25 +1300 Subject: [PATCH 368/385] 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 369/385] 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 53a49272dc0e5f99ac8ea5dd8a67bb4b17ba4c63 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 5 Oct 2017 11:43:19 -0700 Subject: [PATCH 370/385] 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 371/385] 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 21d286b4220111c0ff07f4932acfa1bf9d1c1b4b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 10:04:14 +1300 Subject: [PATCH 372/385] 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 1673c0835349d64b6657517140144493a45c445a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 10:42:23 +1300 Subject: [PATCH 373/385] 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 5587a2fdf842f0ab29fe42b78ad02f6fd27df7c5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 11:13:53 +1300 Subject: [PATCH 374/385] 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 375/385] 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 105457b388b2c065733aa21a52b9fe62e440c69a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 6 Oct 2017 13:27:08 +1300 Subject: [PATCH 376/385] 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 377/385] 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 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 378/385] 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 379/385] 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 3fbaf250a4bd8030f1441e36ddcab5408700350e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 7 Oct 2017 11:50:31 +1300 Subject: [PATCH 380/385] 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 596ce8e9c12c8a75db2a1d7092cf3f9c38ab3969 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 7 Oct 2017 21:46:56 +1300 Subject: [PATCH 381/385] 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 382/385] 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 383/385] 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 384/385] 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 cd97ab0fdfd4f23ee5d90d37c979590a377c2dc7 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Mon, 9 Oct 2017 11:39:57 -0700 Subject: [PATCH 385/385] 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();