diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index dabdfe34f9a..5d3e924ccfd 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -4,7 +4,7 @@ // Persist toolbar by HRS 6/11/15. // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022-2023 Overte e.V. +// Copyright 2022-2024 Overte e.V. // // This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing // @@ -38,6 +38,7 @@ "entitySelectionTool/entitySelectionTool.js", "audioFeedback/audioFeedback.js", "modules/brokenURLReport.js", + "modules/renderWithZonesManager.js", "editModes/editModes.js", "editModes/editVoxels.js" ]); diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index 257f967852d..749c030450a 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -4,7 +4,7 @@ // // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2023 Overte e.V. +// Copyright 2023-2024 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 @@ -422,6 +422,8 @@ var EntityListTool = function(shouldUseEditTabletApp, selectionManager) { that.createApp.alignGridToAvatar(); } else if (data.type === 'brokenURLReport') { brokenURLReport(that.selectionManager.selections); + } else if (data.type === 'renderWithZonesManager') { + renderWithZonesManager(that.selectionManager.selections); } else if (data.type === 'toggleGridVisibility') { that.createApp.toggleGridVisibility(); } else if (data.type === 'toggleSnapToGrid') { diff --git a/scripts/system/create/entityList/html/entityList.html b/scripts/system/create/entityList/html/entityList.html index 75b172e2015..38ea66832bd 100644 --- a/scripts/system/create/entityList/html/entityList.html +++ b/scripts/system/create/entityList/html/entityList.html @@ -4,6 +4,7 @@ // Created by Ryan Huffman on 19 Nov 2014 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2024 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 @@ -316,6 +317,12 @@ + diff --git a/scripts/system/create/entityList/html/js/entityList.js b/scripts/system/create/entityList/html/js/entityList.js index a64d95a64c4..99e2851d960 100644 --- a/scripts/system/create/entityList/html/js/entityList.js +++ b/scripts/system/create/entityList/html/js/entityList.js @@ -3,6 +3,7 @@ // Created by Ryan Huffman on 19 Nov 2014 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2024 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 @@ -273,6 +274,7 @@ let elEntityTable, elAlignGridToSelection, elAlignGridToAvatar, elBrokenURLReport, + elRenderWithZonesManager, elFilterTypeMultiselectBox, elFilterTypeText, elFilterTypeOptions, @@ -361,6 +363,7 @@ function loaded() { elAlignGridToSelection = document.getElementById("alignGridToSelection"); elAlignGridToAvatar = document.getElementById("alignGridToAvatar"); elBrokenURLReport = document.getElementById("brokenURLReport"); + elRenderWithZonesManager = document.getElementById("renderWithZonesManager"); elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box"); elFilterTypeText = document.getElementById("filter-type-text"); elFilterTypeOptions = document.getElementById("filter-type-options"); @@ -603,6 +606,10 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: "brokenURLReport" })); closeAllEntityListMenu(); }; + elRenderWithZonesManager.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ type: "renderWithZonesManager" })); + closeAllEntityListMenu(); + }; elToggleSpaceMode.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: "toggleSpaceMode" })); }; diff --git a/scripts/system/create/modules/renderWithZonesManager.html b/scripts/system/create/modules/renderWithZonesManager.html new file mode 100644 index 00000000000..03dd4601599 --- /dev/null +++ b/scripts/system/create/modules/renderWithZonesManager.html @@ -0,0 +1,410 @@ + + + + + + RenderWithZones Manager + + + +
+
+

+
+
+ Analysis in progress... +

+
+ +
+
+ + + diff --git a/scripts/system/create/modules/renderWithZonesManager.js b/scripts/system/create/modules/renderWithZonesManager.js new file mode 100644 index 00000000000..49dec2bdf34 --- /dev/null +++ b/scripts/system/create/modules/renderWithZonesManager.js @@ -0,0 +1,453 @@ +// +// renderWithZonesManager.js +// +// Created by Alezia Kurdis on January 28th, 2024. +// Copyright 2024 Overte e.V. +// +// This script is to manage the zone in the property renderWithZones more efficiently in the Create Application. +// It allows a global view over a specific selection with possibility to +// REPLACE, REMOVE or ADD zones on those properties more efficiently. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +let rwzmSelectedId = []; +let rwzmAllZonesList = []; +let rwzmUsedZonesList = []; +let rwzmSelectedEntitiesData = []; +let rwzmUndo = []; + +let enforceLocked = false; + +const RWZ_ZONE_SCAN_RADIUS = 27713; //maximal radius to cover the entire domain. + +let rwzmOverlayWebWindow = null; + +function renderWithZonesManager(entityIDs, highlightedID = "") { + if (rwzmGetCheckSum(entityIDs) !== rwzmGetCheckSum(rwzmSelectedId)) { + rwzmUndo = []; + } + rwzmSelectedId = entityIDs; + if (entityIDs.length === 0) { + audioFeedback.rejection(); + Window.alert("You have nothing selected."); + return; + } else { + rwzmAllZonesList = []; + rwzmUsedZonesList = []; + rwzmSelectedEntitiesData = []; + rwzmAllZonesList = rwzmGetExistingZoneList(); + + let properties; + let i = 0; + let j = 0; + let rwzmData = {}; + for (i = 0; i < entityIDs.length; i++ ){ + properties = Entities.getEntityProperties(entityIDs[i], ["renderWithZones", "locked", "name", "type"]); + //Identify the unique zone used in renderWithZones properties of the entities and make the list of this in rwzmUsedZonesList + if (properties.renderWithZones.length > 0) { + for (j = 0; j < properties.renderWithZones.length; j++ ){ + if (rwzmUsedZonesList.indexOf(properties.renderWithZones[j]) === -1) { + rwzmUsedZonesList.push(properties.renderWithZones[j]); + } + } + } + //Make the list of entities, with their id, renderWithZones, locked, in rwzmSelectedEntitiesData + rwzmData = { + "id": entityIDs[i], + "name": properties.name, + "type": properties.type, + "renderWithZones": properties.renderWithZones, + "locked": properties.locked + }; + rwzmSelectedEntitiesData.push(rwzmData); + } + } + + if (rwzmOverlayWebWindow === null) { + rwzmOverlayWebWindow = new OverlayWebWindow({ + title: "RenderWithZones Manager", + source: Script.resolvePath("renderWithZonesManager.html"), + width: 1100, + height: 600 + }); + + rwzmOverlayWebWindow.closed.connect(uiHasClosed); + rwzmOverlayWebWindow.webEventReceived.connect(webEventReceiver); + } + + rwzmGenerateUI(highlightedID); +} + +function rwzmGetCheckSum(array) { + let i = 0; + let sum = 0; + let strForm = JSON.stringify(array); + for (i = 0; i < strForm.length; i++) { + sum = sum + strForm.charCodeAt(i); + } + return sum; +} +function uiHasClosed() { + rwzmOverlayWebWindow.closed.disconnect(uiHasClosed); + rwzmOverlayWebWindow.webEventReceived.disconnect(webEventReceiver); + rwzmOverlayWebWindow = null; +} + +function rwzmGenerateUI(highlightedID) { + let canUnlock = Entities.canAdjustLocks(); + let uiContent = ""; + let i = 0; + let k = 0; + let zones = ""; + let name = ""; + let elementClass = ""; + let firstClass = ""; + let isLocked = ""; + let selectionBox = ""; + let setCheck = ""; + let addAction = ""; + let toHighlight = ""; + let viewBtnCaption = ""; + let warning = false; + uiContent = uiContent + '

RenderWithZones Manager


\n'; + if (canUnlock) { + if (enforceLocked) { + setCheck = " checked"; + } else { + setCheck = ""; + } + } + uiContent = uiContent + ' \n'; + uiContent = uiContent + ' \n'; + uiContent = uiContent + ' \n'; + uiContent = uiContent + ' \n'; + uiContent = uiContent + ' \n'; + uiContent = uiContent + ' \n'; + uiContent = uiContent + '
\n'; + if (rwzmUndo.length === 0) { + undoBtnCode = ""; + } else { + undoBtnCode = ''; + } + uiContent = uiContent + '

Visibility Zones:

' + undoBtnCode + '
\n'; + uiContent = uiContent + '
\n'; + uiContent = uiContent + ' \n'; + for (i = 0; i < rwzmUsedZonesList.length; i++ ) { + name = rwzmGetZoneName(rwzmUsedZonesList[i]); + elementClass = "line"; + firstClass = "cells"; + if (name === "") { + name = rwzmGenerateUnidentifiedZoneName(rwzmUsedZonesList[i]); + elementClass = "errorline"; + warning = true; + } + toHighlight = rwzmUsedZonesList[i]; + viewBtnCaption = "View"; + if ( rwzmUsedZonesList[i] === highlightedID) { + toHighlight = ""; + viewBtnCaption = "Hide"; + firstClass = "highlightedCells"; + if (elementClass === "line") { + elementClass = "lineInverted"; + } + } + uiContent = uiContent + ' \n'; + } + uiContent = uiContent + '
'; + uiContent = uiContent + 'ZONESACTIONS (On listed entities)
' + rwzmGetTruncatedString(name, 30) + '
'; + uiContent = uiContent + ''; + uiContent = uiContent + ''; + uiContent = uiContent + '
\n'; + uiContent = uiContent + '
 \n'; + uiContent = uiContent + ' '; + uiContent = uiContent + '

Entities:

Modify locked entities for me.
\n'; + uiContent = uiContent + '
\n'; + uiContent = uiContent + ' \n'; + for (i = 0; i < rwzmSelectedEntitiesData.length; i++ ) { + elementClass = "line"; + firstClass = "cells"; + if (rwzmSelectedEntitiesData[i].renderWithZones.indexOf(highlightedID) !== -1) { + firstClass = "highlightedCells"; + elementClass = "lineInverted"; + } + zones = " "; + if (rwzmSelectedEntitiesData[i].renderWithZones.length > 0) { + for (k = 0; k < rwzmSelectedEntitiesData[i].renderWithZones.length; k++ ) { + name = rwzmGetTruncatedString(rwzmGetZoneName(rwzmSelectedEntitiesData[i].renderWithZones[k]),30); + if (name === "") { + name = rwzmGetTruncatedString(rwzmGenerateUnidentifiedZoneName(rwzmSelectedEntitiesData[i].renderWithZones[k]),30); + } + if ((canUnlock && enforceLocked && rwzmSelectedEntitiesData[i].locked) || !rwzmSelectedEntitiesData[i].locked) { + name = name + " "; + } + + if (k === 0) { + zones = zones + name; + } else { + zones = zones + "
" + name; + } + } + } + isLocked = " "; + selectionBox = " "; + addAction = " "; + if ((canUnlock && enforceLocked && rwzmSelectedEntitiesData[i].locked) || !rwzmSelectedEntitiesData[i].locked) { + addAction = ""; + selectionBox = ''; + } + if (rwzmSelectedEntitiesData[i].locked) { + if (canUnlock) { + isLocked = ""; //Locked + } else { + isLocked = "🛇"; //Forbidden + } + } + uiContent = uiContent + ' \n'; + } + uiContent = uiContent + '
'; + uiContent = uiContent + ''; + uiContent = uiContent + 'ENTITIES'; + uiContent = uiContent + 'RENDER WITH ZONES 
' + selectionBox; + uiContent = uiContent + '' + isLocked + '' + rwzmSelectedEntitiesData[i].type + ' - ' + rwzmGetTruncatedString(rwzmSelectedEntitiesData[i].name, 30) + '' + zones + '' + addAction + '
\n'; + uiContent = uiContent + '
\n'; + uiContent = uiContent + ' \n'; + if (warning) { + uiContent = uiContent + '
WARNING: The "ZONE NOT FOUND" visibility zones might simply not be loaded if too far and small. Please, verify before.
\n'; + } + //Zone selector Add + uiContent = uiContent + '
\n'; + uiContent = uiContent + '

Select the zone to add:

\n'; + for (i = 0; i < rwzmAllZonesList.length; i++ ) { + uiContent = uiContent + "
\n"; + } + uiContent = uiContent + '
\n'; + uiContent = uiContent + '
\n'; + //Zone selector Replace + uiContent = uiContent + '
\n'; + uiContent = uiContent + '

Select the replacement zone:

\n'; + for (i = 0; i < rwzmAllZonesList.length; i++ ) { + uiContent = uiContent + "
\n"; + } + uiContent = uiContent + '
\n'; + uiContent = uiContent + '
\n'; + + Script.setTimeout(function () { + rwzmOverlayWebWindow.emitScriptEvent(uiContent); + }, 300); +} + +function rwzmGetZoneName(id) { + let k = 0; + let name = ""; + for (k = 0; k < rwzmAllZonesList.length; k++) { + if (rwzmAllZonesList[k].id === id) { + name = rwzmAllZonesList[k].name; + break; + } + } + return name; +} + +function rwzmGenerateUnidentifiedZoneName(id) { + let partialID = id.substr(1,8); + return "ZONE NOT FOUND (" + partialID +")"; +} + +function rwzmGetExistingZoneList() { + var center = { "x": 0, "y": 0, "z": 0 }; + var existingZoneIDs = Entities.findEntitiesByType("Zone", center, RWZ_ZONE_SCAN_RADIUS); + var listExistingZones = []; + var thisZone = {}; + var properties; + for (var k = 0; k < existingZoneIDs.length; k++) { + properties = Entities.getEntityProperties(existingZoneIDs[k], ["name"]); + thisZone = { + "id": existingZoneIDs[k], + "name": properties.name + }; + listExistingZones.push(thisZone); + } + listExistingZones.sort(rwzmZoneSortOrder); + return listExistingZones; +} + +function rwzmZoneSortOrder(a, b) { + var nameA = a.name.toUpperCase(); + var nameB = b.name.toUpperCase(); + if (nameA > nameB) { + return 1; + } else if (nameA < nameB) { + return -1; + } + if (a.name > b.name) { + return 1; + } else if (a.name < b.name) { + return -1; + } + return 0; +} + +function rwzmRemoveZoneFromEntity(id, zoneID, forceLocked, highlightedID) { + rwzmUndo = []; + let properties = Entities.getEntityProperties(id, ["renderWithZones", "locked"]); + + let newRenderWithZones = []; + let i = 0; + for (i = 0; i < properties.renderWithZones.length; i++) { + if (properties.renderWithZones[i] !== zoneID) { + newRenderWithZones.push(properties.renderWithZones[i]); + } + } + + if (forceLocked && properties.locked) { + Entities.editEntity(id, {"locked": false}); + Entities.editEntity(id, {"renderWithZones": newRenderWithZones, "locked": properties.locked}); + } else { + Entities.editEntity(id, {"renderWithZones": newRenderWithZones}); + } + rwzmUndo.push({"id": id, "renderWithZones": properties.renderWithZones}); + renderWithZonesManager(rwzmSelectedId, highlightedID); +} + +function rwzmAddZonesToEntities(ids, zoneID, forceLocked, highlightedID) { + rwzmUndo = []; + let k = 0; + let j = 0; + let properties; + let newRenderWithZones = []; + for (k = 0; k < ids.length; k++) { + properties = Entities.getEntityProperties(ids[k], ["renderWithZones", "locked"]); + newRenderWithZones = []; + + for (j = 0; j < properties.renderWithZones.length; j++) { + if (properties.renderWithZones[j] !== zoneID) { + newRenderWithZones.push(properties.renderWithZones[j]); + } + } + newRenderWithZones.push(zoneID); + if (forceLocked && properties.locked) { + Entities.editEntity(ids[k], {"locked": false}); + Entities.editEntity(ids[k], {"renderWithZones": newRenderWithZones, "locked": properties.locked}); + } else { + Entities.editEntity(ids[k], {"renderWithZones": newRenderWithZones}); + } + rwzmUndo.push({"id": ids[k], "renderWithZones": properties.renderWithZones}); + } + renderWithZonesManager(rwzmSelectedId, highlightedID); +} + +function rwzmRemoveZoneFromAllEntities(zoneID, forceLocked, highlightedID) { + rwzmUndo = []; + let k = 0; + let j = 0; + let properties; + let newRenderWithZones = []; + for (k = 0; k < rwzmSelectedId.length; k++) { + properties = Entities.getEntityProperties(rwzmSelectedId[k], ["renderWithZones", "locked"]); + newRenderWithZones = []; + + for (j = 0; j < properties.renderWithZones.length; j++) { + if (properties.renderWithZones[j] !== zoneID) { + newRenderWithZones.push(properties.renderWithZones[j]); + } + } + if (forceLocked && properties.locked) { + Entities.editEntity(rwzmSelectedId[k], {"locked": false}); + Entities.editEntity(rwzmSelectedId[k], {"renderWithZones": newRenderWithZones, "locked": properties.locked}); + } else { + Entities.editEntity(rwzmSelectedId[k], {"renderWithZones": newRenderWithZones}); + } + rwzmUndo.push({"id": rwzmSelectedId[k], "renderWithZones": properties.renderWithZones}); + } + renderWithZonesManager(rwzmSelectedId, highlightedID); +} + +function rwzmReplaceZoneOnAllEntities(targetZoneID, replacementZoneID, forceLocked, highlightedID) { + rwzmUndo = []; + let k = 0; + let j = 0; + let properties; + let newRenderWithZones = []; + for (k = 0; k < rwzmSelectedId.length; k++) { + properties = Entities.getEntityProperties(rwzmSelectedId[k], ["renderWithZones", "locked"]); + newRenderWithZones = []; + + for (j = 0; j < properties.renderWithZones.length; j++) { + if (properties.renderWithZones[j] !== targetZoneID) { + newRenderWithZones.push(properties.renderWithZones[j]); + } else { + newRenderWithZones.push(replacementZoneID); + } + } + if (forceLocked && properties.locked) { + Entities.editEntity(rwzmSelectedId[k], {"locked": false}); + Entities.editEntity(rwzmSelectedId[k], {"renderWithZones": newRenderWithZones, "locked": properties.locked}); + } else { + Entities.editEntity(rwzmSelectedId[k], {"renderWithZones": newRenderWithZones}); + } + rwzmUndo.push({"id": rwzmSelectedId[k], "renderWithZones": properties.renderWithZones}); + } + renderWithZonesManager(rwzmSelectedId, highlightedID); +} + +function rwzmGetTruncatedString(str, max) { + if (str.length > max) { + return str.substr(0, max-1) + "…"; + } else { + return str; + } +} + +function rwzmUndoLastAction(highlightedID) { + let k = 0; + let properties; + let locked; + for (k = 0; k < rwzmUndo.length; k++) { + locked = Entities.getEntityProperties(rwzmUndo[k].id, ["locked"]).locked; + if (locked) { + Entities.editEntity(rwzmUndo[k].id, {"locked": false}); + Entities.editEntity(rwzmUndo[k].id, {"renderWithZones": rwzmUndo[k].renderWithZones, "locked": locked}); + } else { + Entities.editEntity(rwzmUndo[k].id, {"renderWithZones": rwzmUndo[k].renderWithZones}); + } + } + rwzmUndo = []; + renderWithZonesManager(rwzmSelectedId, highlightedID); +} + +function webEventReceiver (message) { + try { + var data = JSON.parse(message); + } catch(e) { + print("renderWithZonesManager.js: Error parsing JSON"); + return; + } + if (data.action === "highlight") { + enforceLocked = data.enforceLocked; + renderWithZonesManager(rwzmSelectedId, data.id); + } else if (data.action === "removeZoneFromEntity") { + enforceLocked = data.enforceLocked; + rwzmRemoveZoneFromEntity(data.id, data.zoneID, data.enforceLocked, data.highlightedID); + } else if (data.action === "addZoneToEntities") { + enforceLocked = data.enforceLocked; + rwzmAddZonesToEntities(data.ids, data.zoneID, data.enforceLocked, data.highlightedID); + } else if (data.action === "refresh") { + enforceLocked = data.enforceLocked; + renderWithZonesManager(rwzmSelectedId, data.highlightedID); + } else if (data.action === "removeZoneOnAllEntities") { + enforceLocked = data.enforceLocked; + rwzmRemoveZoneFromAllEntities(data.zoneID, data.enforceLocked, data.highlightedID); + } else if (data.action === "replaceZoneOnAllEntities") { + enforceLocked = data.enforceLocked; + rwzmReplaceZoneOnAllEntities(data.targetZoneID, data.replacementZoneID, data.enforceLocked, data.highlightedID); + } else if (data.action === "undo") { + enforceLocked = data.enforceLocked; + rwzmUndoLastAction(data.highlightedID); + } +} +