From 686d84526ca163062c7c1b228ddd8a34f89bf647 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sat, 25 Jun 2022 17:02:38 +0200 Subject: [PATCH] Redesigned voxel edit UI for VR and added VR controller support --- scripts/system/create/editModes/editModes.js | 2 + scripts/system/create/editModes/editVoxels.js | 153 +++++++++++++++++- .../entitySelectionTool.js | 3 + scripts/system/html/css/edit-style.css | 18 +++ scripts/system/html/gridControls.html | 67 +++++--- scripts/system/html/js/gridControls.js | 128 +++++++++++---- 6 files changed, 319 insertions(+), 52 deletions(-) diff --git a/scripts/system/create/editModes/editModes.js b/scripts/system/create/editModes/editModes.js index 835029f878..4f293bbae6 100644 --- a/scripts/system/create/editModes/editModes.js +++ b/scripts/system/create/editModes/editModes.js @@ -111,6 +111,8 @@ EditTools = function(options) { if (data.type !== "update-edit-tools") { return; } + + print(JSON.stringify(data)); var needsUpdate = false; diff --git a/scripts/system/create/editModes/editVoxels.js b/scripts/system/create/editModes/editVoxels.js index 0d27246848..c3cdd89cb5 100644 --- a/scripts/system/create/editModes/editVoxels.js +++ b/scripts/system/create/editModes/editVoxels.js @@ -8,6 +8,13 @@ // Created by Seth Alves on 2015-08-25 // Copyright 2015 High Fidelity, Inc. // +// Based on entitySelectionTool.js +// Created by Brad hefta-Gaub on 10/1/14. +// Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017 +// Modified by David Back on 1/9/2018 +// Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors +// // This script implements voxel edit mode // // Distributed under the Apache License, Version 2.0. @@ -22,6 +29,8 @@ EditVoxels = function() { var self = this; var that = {}; + const NO_HAND = -1; + var controlHeld = false; var shiftHeld = false; @@ -38,6 +47,11 @@ EditVoxels = function() { var editSphereRadius = 0.15; var brushLength = 0.5; + + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click-voxels'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press-voxels'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; that.setActive = function(active) { isActive = (active === true); @@ -114,6 +128,10 @@ EditVoxels = function() { } function attemptVoxelChangeForEntity(entityID, pickRayDir, intersectionLocation) { + var wantDebug = false; + if (wantDebug) { + print("=============== eV::attemptVoxelChangeForEntity BEG ======================="); + } var properties = Entities.getEntityProperties(entityID); if (properties.type != "PolyVox") { @@ -133,6 +151,12 @@ EditVoxels = function() { var pickRayDirInVoxelSpace = Vec3.subtract(voxelPosition, voxelOrigin); pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace); + if (wantDebug) { + print("voxelOrigin: " + JSON.stringify(voxelOrigin)); + print("voxelPosition: " + JSON.stringify(voxelPosition)); + print("pickRayDirInVoxelSpace: " + JSON.stringify(pickRayDirInVoxelSpace)); + } + var doAdd = addingVoxels; var doDelete = deletingVoxels; var doAddSphere = addingSpheres; @@ -156,23 +180,49 @@ EditVoxels = function() { if (doDelete) { var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); + if (wantDebug) { + print("Calling setVoxel to delete"); + print("entityID: " + JSON.stringify(entityID)); + print("floorVector(toErasePosition): " + JSON.stringify(floorVector(toErasePosition))); + } return Entities.setVoxel(entityID, floorVector(toErasePosition), 0); } if (doAdd) { var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); + if (wantDebug) { + print("Calling setVoxel to add"); + print("entityID: " + JSON.stringify(entityID)); + print("floorVector(toDrawPosition): " + JSON.stringify(floorVector(toDrawPosition))); + } return Entities.setVoxel(entityID, floorVector(toDrawPosition), 255); } if (doDeleteSphere) { var toErasePosition = intersectionLocation; + if (wantDebug) { + print("Calling setVoxelSphere to delete"); + print("entityID: " + JSON.stringify(entityID)); + print("editSphereRadius: " + JSON.stringify(editSphereRadius)); + print("floorVector(toErasePosition): " + JSON.stringify(floorVector(toErasePosition))); + } return Entities.setVoxelSphere(entityID, floorVector(toErasePosition), editSphereRadius, 0); } if (doAddSphere) { var toDrawPosition = intersectionLocation; + if (wantDebug) { + print("Calling setVoxelSphere to add"); + print("entityID: " + JSON.stringify(entityID)); + print("editSphereRadius: " + JSON.stringify(editSphereRadius)); + print("floorVector(toDrawPosition): " + JSON.stringify(floorVector(toDrawPosition))); + } return Entities.setVoxelSphere(entityID, floorVector(toDrawPosition), editSphereRadius, 255); } } function attemptVoxelChange(pickRayDir, intersection) { + var wantDebug = false; + if (wantDebug) { + print("=============== eV::attemptVoxelChange BEG ======================="); + } var ids; @@ -181,6 +231,10 @@ EditVoxels = function() { ids.push(intersection.entityID); } + if (wantDebug) { + print("Entities: " + JSON.stringify(ids)); + } + var success = false; for (var i = 0; i < ids.length; i++) { var entityID = ids[i]; @@ -189,14 +243,43 @@ EditVoxels = function() { return success; } + function controllerComputePickRay() { + var hand = triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + var controllerPosition = controllerPose.translation; + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(controllerPose.rotation); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } + function mousePressEvent(event) { - if (!event.isLeftButton) { + var wantDebug = false; + if (!editEnabled || !isActive) { + return false; + } + + if (wantDebug) { + print("=============== eV::mousePressEvent BEG ======================="); + } + + if (!event.isLeftButton && !triggered()) { return; } - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking + if (wantDebug) { + print("Pick ray: " + JSON.stringify(pickRay)); + print("Intersection: " + JSON.stringify(intersection)); + } + if (intersection.intersects) { if (attemptVoxelChange(pickRay.direction, intersection)) { return; @@ -211,6 +294,10 @@ EditVoxels = function() { } } + function mouseReleaseEvent(event) { + return; + } + function keyPressEvent(event) { if (event.text == "CONTROL") { controlHeld = true; @@ -229,16 +316,78 @@ EditVoxels = function() { } } + function triggered() { + return that.triggeredHand !== NO_HAND; + }; + + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; + } + + function makeClickHandler(hand) { + return function (clicked) { + if (!editEnabled) { + return; + } + // Don't allow both hands to trigger at the same time + if (triggered() && hand !== that.triggeredHand) { + return; + } + if (!triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { + that.triggeredHand = hand; + mousePressEvent({}); + } else if (triggered() && !clicked) { + that.triggeredHand = NO_HAND; + mouseReleaseEvent({}); + } + }; + } + + function makePressHandler(hand) { + return function (value) { + if (!editEnabled) { + return; + } + if (value >= TRIGGER_ON_VALUE && !triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + } else { + that.pressedHand = NO_HAND; + } + } + } + function cleanup() { Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); Controller.keyPressEvent.disconnect(self.keyPressEvent); Controller.keyReleaseEvent.disconnect(self.keyReleaseEvent); } Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + }; + that.enableTriggerMapping(); Script.scriptEnding.connect(cleanup); + Script.scriptEnding.connect(that.disableTriggerMapping); return that; } diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index 4eb6f65663..7ce8edd70f 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -99,6 +99,9 @@ SelectionManager = (function() { } if (messageParsed.method === "selectEntity") { + if (!that.editEnabled) { + return; + } if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { if (wantDebug) { print("setting selection to " + messageParsed.entityID); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 62c58a5676..77ac0fb0c5 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -4,6 +4,7 @@ // Created by Ryan Huffman on 13 Nov 2014 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -2062,6 +2063,23 @@ div.entity-list-menu { cursor: pointer; } +div.tools-select-menu { + position: relative; + display: none; + width: 370px; + height: 0px; + top: 0px; + left: 8px; + right: 0; + bottom: 0; + border-style: solid; + border-color: #505050; + border-width: 1px; + background-color: #c0c0c0; + z-index: 2; + cursor: pointer; +} + div.menu-separator{ width: 100%; height: 2px; diff --git a/scripts/system/html/gridControls.html b/scripts/system/html/gridControls.html index 97213aca39..d5a0978c01 100644 --- a/scripts/system/html/gridControls.html +++ b/scripts/system/html/gridControls.html @@ -24,49 +24,73 @@
- +
- +
+ + +
+

Voxel edit settings

- +
+
+ + + +
- -
-
- - - -
- -
-

Grid settings

@@ -116,5 +140,6 @@
+ diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index 67b1d6bdae..e3d6d937e6 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -7,15 +7,25 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +var createAppModeValue = ""; +var voxelEditModeValue = ""; + function loaded() { openEventBridge(function() { - elCreateAppMode = document.getElementById("create-app-mode"); - elVoxelEditMode = document.getElementById("voxel-edit-mode"); + elCreateAppModeMenu = document.getElementById("create-app-mode"); + elEditModeObject = document.getElementById("edit-mode-object"); + elEditModeVoxel = document.getElementById("edit-mode-voxel"); + elVoxelEditModeMenu = document.getElementById("voxel-edit-mode"); + elVoxelEditModeSingle = document.getElementById("voxel-edit-mode-single"); + elVoxelEditModeSphere = document.getElementById("voxel-edit-mode-sphere"); + elVoxelEditModeCube = document.getElementById("voxel-edit-mode-cube"); + elMenuBackgroundOverlay = document.getElementById("menuBackgroundOverlay"); + elVoxelSphereSize = document.getElementById("voxel-sphere-size"); - elVoxelEditDynamics = document.getElementById("voxel-edit-dynamics"); + //elVoxelEditDynamics = document.getElementById("voxel-edit-dynamics"); elVoxelRemove = document.getElementById("voxel-remove"); - elVoxelPointerMode = document.getElementById("voxel-pointer-mode"); - elVoxelBrushLength = document.getElementById("voxel-brush-length"); + //elVoxelPointerMode = document.getElementById("voxel-pointer-mode"); + //elVoxelBrushLength = document.getElementById("voxel-brush-length"); elPosY = document.getElementById("horiz-y"); elMinorSpacing = document.getElementById("minor-spacing"); @@ -25,38 +35,93 @@ function loaded() { elMoveToSelection = document.getElementById("move-to-selection"); elMoveToAvatar = document.getElementById("move-to-avatar"); + elCreateAppModeMenu.onclick = function() { + document.getElementById("menuBackgroundOverlay").style.display = "block"; + document.getElementById("edit-mode-menu").style.display = "block"; + }; + + elVoxelEditModeMenu.onclick = function() { + document.getElementById("menuBackgroundOverlay").style.display = "block"; + document.getElementById("voxel-edit-mode-menu").style.display = "block"; + }; + + elMenuBackgroundOverlay.onclick = function() { + closeAllEntityListMenu(); + }; + + elEditModeObject.onclick = function() { + createAppModeValue = "object"; + elCreateAppModeMenu.value = "Object mode\u25BE"; + emitUpdateEditTools(); + closeAllEntityListMenu(); + }; + + elEditModeVoxel.onclick = function() { + createAppModeValue = "voxel"; + elCreateAppModeMenu.value = "Voxel edit mode\u25BE"; + emitUpdateEditTools(); + closeAllEntityListMenu(); + }; + + elVoxelEditModeSingle.onclick = function() { + voxelEditModeValue = "single"; + elVoxelEditModeMenu.value = "Single voxels\u25BE"; + emitUpdateEditTools(); + closeAllEntityListMenu(); + }; + + elVoxelEditModeSphere.onclick = function() { + voxelEditModeValue = "sphere"; + elVoxelEditModeMenu.value = "Spheres\u25BE"; + emitUpdateEditTools(); + closeAllEntityListMenu(); + }; + + elVoxelEditModeCube.onclick = function() { + voxelEditModeValue = "cube"; + elVoxelEditModeMenu.value = "Cubes\u25BE"; + emitUpdateEditTools(); + closeAllEntityListMenu(); + }; + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.createAppMode !== undefined) { - elCreateAppMode.value = data.createAppMode; + if (data.createAppMode === "object") { + createAppModeValue = data.createAppMode; + elCreateAppModeMenu.value = "Object mode\u25BE"; + } + if (data.createAppMode === "voxel") { + createAppModeValue = data.createAppMode; + elCreateAppModeMenu.value = "Voxel edit mode\u25BE"; + } } if (data.voxelEditMode !== undefined) { - elVoxelEditMode.value = data.voxelEditMode; + if (data.voxelEditMode === "single") { + voxelEditModeValue = data.voxelEditMode; + elVoxelEditModeMenu.value = "Single voxels\u25BE"; + } + if (data.voxelEditMode === "sphere") { + voxelEditModeValue = data.voxelEditMode; + elVoxelEditModeMenu.value = "Spheres\u25BE"; + } + if (data.voxelEditMode === "cube") { + voxelEditModeValue = data.voxelEditMode; + elVoxelEditModeMenu.value = "Cubes\u25BE"; + } } if (data.voxelSphereSize !== undefined) { elVoxelSphereSize.value = data.voxelSphereSize; } - if (data.voxelEditDynamics !== undefined) { - elVoxelEditDynamics.value = data.voxelEditDynamics; - } - if (data.voxelRemove !== undefined) { elVoxelRemove.checked = data.voxelRemove == true; } - if (data.voxelPointerMode !== undefined) { - elVoxelPointerMode.value = data.voxelPointerMode; - } - - if (data.voxelBrushLength !== undefined) { - elVoxelBrushLength.value = data.voxelBrushLength; - } - if (data.origin) { var origin = data.origin; elPosY.value = origin.y; @@ -100,24 +165,22 @@ function loaded() { function emitUpdateEditTools() { EventBridge.emitWebEvent(JSON.stringify({ type: "update-edit-tools", - createAppMode: elCreateAppMode.value, - voxelEditMode: elVoxelEditMode.value, + createAppMode: createAppModeValue, + voxelEditMode: voxelEditModeValue, voxelSphereSize: elVoxelSphereSize.value, - voxelEditDynamics: elVoxelEditDynamics.value, + //voxelEditDynamics: elVoxelEditDynamics.value, voxelRemove: elVoxelRemove.checked, - voxelPointerMode: elVoxelPointerMode.value, - voxelBrushLength: elVoxelBrushLength.value, + //voxelPointerMode: elVoxelPointerMode.value, + //voxelBrushLength: elVoxelBrushLength.value, })); } } - elCreateAppMode.addEventListener("change", emitUpdateEditTools); - elVoxelEditMode.addEventListener("change", emitUpdateEditTools); elVoxelSphereSize.addEventListener("change", emitUpdateEditTools); - elVoxelEditDynamics.addEventListener("change", emitUpdateEditTools); + //elVoxelEditDynamics.addEventListener("change", emitUpdateEditTools); elVoxelRemove.addEventListener("change", emitUpdateEditTools); - elVoxelPointerMode.addEventListener("change", emitUpdateEditTools); - elVoxelBrushLength.addEventListener("change", emitUpdateEditTools); + //elVoxelPointerMode.addEventListener("change", emitUpdateEditTools); + //elVoxelBrushLength.addEventListener("change", emitUpdateEditTools); elPosY.addEventListener("change", emitUpdate); elMinorSpacing.addEventListener("change", emitUpdate); @@ -211,8 +274,15 @@ function loaded() { })); }, false); + function closeAllEntityListMenu() { + document.getElementById("menuBackgroundOverlay").style.display = "none"; + document.getElementById("edit-mode-menu").style.display = "none"; + document.getElementById("voxel-edit-mode-menu").style.display = "none"; + } + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { event.preventDefault(); }, false); + }