3501 lines · 149.6 KB · JavaScript
RawDownload
////  entitySelectionTool.js////  Created by Brad hefta-Gaub on October 1st, 2014.//    Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on April 7th, 2017//    Modified by David Back on January 9th, 2018//  Copyright 2014 High Fidelity, Inc.//  Copyright 2020 Vircadia contributors//  Copyright 2022-2025 Overte e.V.////  This script implements a class useful for building tools for editing entities.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// /* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections,   getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ const SPACE_LOCAL = "local";const SPACE_WORLD = "world";const HIGHLIGHT_LIST_NAME = "editHandleHighlightList";const MIN_DISTANCE_TO_REZ_FROM_AVATAR = 3; var controllerStandard = Controller.Standard; Script.include([    "../../libraries/controllers.js",    "../../libraries/controllerDispatcherUtils.js",    "../../libraries/utils.js"]); function deepCopy(v) {    return JSON.parse(JSON.stringify(v));} function getDuplicateAppendedName(name) {    var appendiceChar = "(0123456789)";    var currentChar = "";    var rippedName = name;    var existingSequence = 0;    var sequenceReader = "";    for (var i = name.length - 1; i >= 0; i--) {        currentChar = name.charAt(i);        if (i === (name.length - 1) && currentChar !== ")") {            rippedName = name;            break;        } else {            if (appendiceChar.indexOf(currentChar) === -1) {                rippedName = name;                break;            } else {                if (currentChar == "(" && i === name.length - 2) {                    rippedName = name;                    break;                } else {                    if (currentChar == "(") {                        rippedName = name.substr(0, i-1);                        existingSequence = parseInt(sequenceReader);                        break;                    } else {                        sequenceReader = currentChar + sequenceReader;                    }                }            }        }    }    if (existingSequence === 0) {        rippedName = rippedName.trim() + " (2)";    } else {        existingSequence++;        rippedName = rippedName.trim() + " (" + existingSequence + ")";    }    return rippedName.trim();} SelectionManager = (function() {    var that = {};     // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES    function subscribeToUpdateMessages() {        Messages.subscribe("entityToolUpdates");        Messages.messageReceived.connect(handleEntitySelectionToolUpdates);    }     // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES    function handleEntitySelectionToolUpdates(channel, message, sender) {        //print("Channel: " + channel + " Sender: " + sender + " Message: " + JSON.stringify(message));        if (channel !== 'entityToolUpdates') {            return;        }        if (sender !== MyAvatar.sessionUUID) {            return;        }         var wantDebug = false;        var messageParsed;        try {            messageParsed = JSON.parse(message);        } catch (err) {            print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message");            return;        }         if (messageParsed.method === "selectEntity") {            if (!that.editEnabled) {                return;            }            if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {                if (wantDebug) {                    print("setting selection to " + messageParsed.entityID);                }                if (that.createApp.expectingRotateAsClickedSurface) {                    if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) {                        audioFeedback.rejection();                        Window.notifyEditError("You have nothing selected, or the selection is locked.");                        that.createApp.expectingRotateAsClickedSurface = false;                    } else {                        //Rotate Selection according the Surface Normal                        var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(messageParsed.surfaceNormal, -1));                        selectionDisplay.rotateSelection(normalRotation);                        //Translate Selection according the clicked Surface                        var distanceFromSurface;                        if (selectionDisplay.getSpaceMode() === SPACE_WORLD){                            distanceFromSurface = SelectionManager.worldDimensions.z / 2;                        } else {                            distanceFromSurface = SelectionManager.localDimensions.z / 2;                        }                        selectionDisplay.moveSelection(Vec3.sum(messageParsed.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface})));                        that._update(false, this);                        that.createApp.pushCommandForSelections();                        that.createApp.expectingRotateAsClickedSurface = false;                        audioFeedback.action();                    }                } else {                    var pickRay = SelectionDisplay.controllerComputePickRay();                    var intersectObj = SelectionDisplay.testRayIntersect(pickRay, SelectionDisplay.allToolEntities);                    if (intersectObj.intersects) {                        return;                    }                    if (that.createApp.hmdMultiSelectMode) {                        that.addEntity(messageParsed.entityID, true, that);                    } else {                        that.setSelections([messageParsed.entityID], that);                    }                }            }        } else if (messageParsed.method === "clearSelection") {            if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {                that.clearSelections();            }        } else if (messageParsed.method === "pointingAt") {            if (messageParsed.hand === controllerStandard.RightHand) {                that.pointingAtDesktopWindowRight = messageParsed.desktopWindow;                that.pointingAtTabletRight = messageParsed.tablet;            } else {                that.pointingAtDesktopWindowLeft = messageParsed.desktopWindow;                that.pointingAtTabletLeft = messageParsed.tablet;            }        }    }     subscribeToUpdateMessages();     // disabling this for now as it is causing rendering issues with the other handle toolEntities    /*    var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 };    var editHandleOutlineStyle = {        outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT,        outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT,        fillUnoccludedColor: COLOR_ORANGE_HIGHLIGHT,        fillOccludedColor: COLOR_ORANGE_HIGHLIGHT,        outlineUnoccludedAlpha: 1,        outlineOccludedAlpha: 0,        fillUnoccludedAlpha: 0,        fillOccludedAlpha: 0,        outlineWidth: 3,        isOutlineSmooth: true    };    Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle);    */     that.savedProperties = {};    that.selections = [];    var listeners = [];     that.localRotation = Quat.IDENTITY;    that.localPosition = Vec3.ZERO;    that.localDimensions = Vec3.ZERO;    that.localRegistrationPoint = Vec3.HALF;     that.worldRotation = Quat.IDENTITY;    that.worldPosition = Vec3.ZERO;    that.worldDimensions = Vec3.ZERO;    that.worldRegistrationPoint = Vec3.HALF;    that.centerPosition = Vec3.ZERO;        that.pointingAtDesktopWindowLeft = false;    that.pointingAtDesktopWindowRight = false;    that.pointingAtTabletLeft = false;    that.pointingAtTabletRight = false;    that.editEnabled = true;        that.updateEditSettings = function(data) {                if (data.createAppMode) {            if (data.createAppMode === "object"){                that.editEnabled = true;            } else {                that.editEnabled = false;                if(that.hasSelection()){                    that.clearSelections();                }            }        }    }         that.saveProperties = function() {        that.savedProperties = {};        for (var i = 0; i < that.selections.length; i++) {            var entityID = that.selections[i];            that.savedProperties[entityID] = Entities.getEntityProperties(entityID);        }    };     that.addEventListener = function(func, thisContext) {        listeners.push({            callback: func,            thisContext: thisContext        });    };     that.hasSelection = function() {        return that.selections.length > 0;    };     that.setSelections = function(entityIDs, caller) {        print("setSelections: " + JSON.stringify(entityIDs));        Script.logBacktrace("setSelections");                // Clear highlight handle list        const selection =  Selection.getSelectedItemsList(HIGHLIGHT_LIST_NAME);                if ( selection && selection.entities && selection.entities.length > 0 ) {            for (let i = 0; i < that.selections.length; i++) {                Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", that.selections[i]);            }        }                // Clear class-level selections list        that.selections = [];                // Add entities to the highlight handle list and Class selections list        for (let i = 0; i < entityIDs.length; i++) {            var entityID = entityIDs[i];            that.selections.push(entityID);            Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);        }         that._update(true, caller);    };     that.addEntity = function(entityID, toggleSelection, caller) {        if (entityID) {            var idx = -1;            for (var i = 0; i < that.selections.length; i++) {                if (entityID === that.selections[i]) {                    idx = i;                    break;                }            }            if (idx === -1) {                that.selections.push(entityID);                Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);            } else if (toggleSelection) {                that.selections.splice(idx, 1);                Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);            }        }         that._update(true, caller);    };     function removeEntityByID(entityID) {        var idx = that.selections.indexOf(entityID);        if (idx >= 0) {            that.selections.splice(idx, 1);            Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);        }    }     that.removeEntity = function (entityID, caller) {        removeEntityByID(entityID);        that._update(true, caller);    };     that.removeEntities = function(entityIDs, caller) {        for (var i = 0, length = entityIDs.length; i < length; i++) {            removeEntityByID(entityIDs[i]);        }        that._update(true, caller);    };     that.clearSelections = function(caller) {                const selection =  Selection.getSelectedItemsList(HIGHLIGHT_LIST_NAME);                // Clear highlight handle list        if ( selection && selection.entities && selection.entities.length > 0 ) {            for (let i = 0; i < that.selections.length; i++) {                Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", that.selections[i]);            }        }                // Clear class-level selections list        that.selections = [];        that._update(true, caller);    };        that.addChildrenEntities = function(parentEntityID, entityList, entityHostType) {        var wantDebug = false;        var children = Entities.getChildrenIDs(parentEntityID);        var entityHostTypes = Entities.getMultipleEntityProperties(children, 'entityHostType');        for (var i = 0; i < children.length; i++) {            var childID = children[i];             if (entityHostTypes[i].entityHostType !== entityHostType) {                if (wantDebug) {                    console.log("Skipping addition of entity " + childID + " with conflicting entityHostType: " +                        entityHostTypes[i].entityHostType + ", expected: " + entityHostType);                }                continue;            }             if (entityList.indexOf(childID) < 0) {                entityList.push(childID);            }            that.addChildrenEntities(childID, entityList, entityHostType);        }    };     // Determine if an entity is being grabbed.    // This is mostly a heuristic - there is no perfect way to know if an entity is being    // grabbed.    //    // @return {boolean} true if the given entity with `properties` is being grabbed by an avatar    function nonDynamicEntityIsBeingGrabbedByAvatar(properties) {        if (properties.dynamic || Uuid.isNull(properties.parentID)) {            return false;        }         var avatar = AvatarList.getAvatar(properties.parentID);        if (Uuid.isNull(avatar.sessionUUID)) {            return false;        }         var grabJointNames = [            'RightHand', 'LeftHand',            '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND',            '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND',            '_FARGRAB_RIGHTHAND', '_FARGRAB_LEFTHAND', '_FARGRAB_MOUSE'        ];         for (var i = 0; i < grabJointNames.length; ++i) {            if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) {                return true;            }        }         return false;    }     var entityClipboard = {        entities: {}, // Map of id -> properties for copied entities        position: { x: 0, y: 0, z: 0 },        dimensions: { x: 0, y: 0, z: 0 },    };     that.duplicateSelection = function() {        var entitiesToDuplicate = [];        var duplicatedEntityIDs = [];        var duplicatedChildrenWithOldParents = [];        var originalEntityToNewEntityID = [];         SelectionManager.saveProperties();                // build list of entities to duplicate by including any unselected children of selected parent entities        var originalEntityIDs = Object.keys(that.savedProperties);        var entityHostTypes = Entities.getMultipleEntityProperties(originalEntityIDs, 'entityHostType');        for (var i = 0; i < originalEntityIDs.length; i++) {            var originalEntityID = originalEntityIDs[i];            if (entitiesToDuplicate.indexOf(originalEntityID) === -1) {                entitiesToDuplicate.push(originalEntityID);            }            that.addChildrenEntities(originalEntityID, entitiesToDuplicate, entityHostTypes[i].entityHostType);        }                var duplicateInterrupted = false;        for (var j = 0; j < entitiesToDuplicate.length; j++) {            var lockedEntities = Entities.getEntityProperties(entitiesToDuplicate[j], 'locked');            if (lockedEntities.locked) {                duplicateInterrupted = true;                break;            }        }        if (duplicateInterrupted) {            audioFeedback.rejection();            Window.notifyEditError("At least one of the selected entities or one of their children are locked.");            return [];        }         // duplicate entities from above and store their original to new entity mappings and children needing re-parenting        for (var i = 0; i < entitiesToDuplicate.length; i++) {            var originalEntityID = entitiesToDuplicate[i];            var properties = that.savedProperties[originalEntityID];            if (properties === undefined) {                properties = Entities.getEntityProperties(originalEntityID);            }            if (!properties.locked && (!properties.avatarEntity || properties.owningAvatarID === MyAvatar.sessionUUID)) {                if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) {                    properties.parentID = null;                    properties.parentJointIndex = null;                    properties.localPosition = properties.position;                    properties.localRotation = properties.rotation;                }                 properties.name = getDuplicateAppendedName(properties.name);                 properties.localVelocity = Vec3.ZERO;                properties.localAngularVelocity = Vec3.ZERO;                 delete properties.actionData;                var newEntityID = Entities.addEntity(properties);                 // Re-apply actions from the original entity                var actionIDs = Entities.getActionIDs(properties.id);                for (var j = 0; j < actionIDs.length; ++j) {                    var actionID = actionIDs[j];                    var actionArguments = Entities.getActionArguments(properties.id, actionID);                    if (actionArguments) {                        var type = actionArguments.type;                        if (type === 'hold' || type === 'far-grab') {                            continue;                        }                        delete actionArguments.ttl;                        Entities.addAction(type, newEntityID, actionArguments);                    }                }                 duplicatedEntityIDs.push({                    entityID: newEntityID,                    properties: properties                });                if (properties.parentID !== Uuid.NONE) {                    duplicatedChildrenWithOldParents[newEntityID] = properties.parentID;                }                originalEntityToNewEntityID[originalEntityID] = newEntityID;            }        }                // re-parent duplicated children to the duplicate entities of their original parents (if they were duplicated)        Object.keys(duplicatedChildrenWithOldParents).forEach(function(childIDNeedingNewParent) {            var originalParentID = duplicatedChildrenWithOldParents[childIDNeedingNewParent];            var newParentID = originalEntityToNewEntityID[originalParentID];            if (newParentID) {                Entities.editEntity(childIDNeedingNewParent, { parentID: newParentID });                for (var i = 0; i < duplicatedEntityIDs.length; i++) {                    var duplicatedEntity = duplicatedEntityIDs[i];                    if (duplicatedEntity.entityID === childIDNeedingNewParent) {                        duplicatedEntity.properties.parentID = newParentID;                    }                }            }        });                audioFeedback.confirmation();        return duplicatedEntityIDs;    };     // Create the entities in entityProperties, maintaining parent-child relationships.    // @param entityProperties {array} - Array of entity property objects    that.createEntities = function(entityProperties) {        var entitiesToCreate = [];        var createdEntityIDs = [];        var createdChildrenWithOldParents = [];        var originalEntityToNewEntityID = [];         that.saveProperties();         for (var i = 0; i < entityProperties.length; ++i) {            var properties = entityProperties[i];            if (properties.parentID in originalEntityToNewEntityID) {                properties.parentID = originalEntityToNewEntityID[properties.parentID];            } else {                delete properties.parentID;            }             delete properties.actionData;            var newEntityID = Entities.addEntity(properties);

Lines 1–500 of 3501.