From 580957657744985faf36319e284a2a2346565a25 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sun, 12 Feb 2023 15:29:47 +0100 Subject: [PATCH] Fixed Create App --- .../src/scripting/DesktopScriptingInterface.h | 1 + libraries/script-engine/src/ScriptEngine.h | 1 + libraries/script-engine/src/ScriptManager.cpp | 4 + libraries/script-engine/src/ScriptManager.h | 7 + .../script-engine/src/v8/ScriptEngineV8.cpp | 8 + .../script-engine/src/v8/ScriptEngineV8.h | 1 + .../src/v8/ScriptEngineV8_cast.cpp | 1 + .../src/v8/ScriptObjectV8Proxy.cpp | 18 +- .../src/v8/ScriptValueV8Wrapper.cpp | 1 + scripts/system/create/edit.js | 6084 +++++++++-------- .../system/create/entityList/entityList.js | 2 +- .../entitySelectionTool.js | 59 +- scripts/system/libraries/gridTool.js | 4 +- 13 files changed, 3124 insertions(+), 3067 deletions(-) diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 68eb27c7d0..175ce22ad2 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -45,6 +45,7 @@ * {@link InteractiveWindow}: none, top left, top right, bottom right, or bottom left of the Interface window. * Read-only. */ + class DesktopScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index bee7d21134..81f4d340a7 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -129,6 +129,7 @@ public: virtual void compileTest() = 0; virtual QString scriptValueDebugDetails(const ScriptValue &value) = 0; virtual QString scriptValueDebugListMembers(const ScriptValue &value) = 0; + virtual void logBacktrace(const QString &title) = 0; public: // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways bool IS_THREADSAFE_INVOCATION(const QString& method); diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 18f41f32c5..b359dc5e3a 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -2508,3 +2508,7 @@ ScriptValue ScriptManager::evaluate(const QString& program, const QString& fileN void ScriptManager::requestGarbageCollection() { _engine->requestCollectGarbage(); } + +void ScriptManager::logBacktrace(const QString &title) { + _engine->logBacktrace(title); +} diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h index 3d149c5e8d..3a21882e04 100644 --- a/libraries/script-engine/src/ScriptManager.h +++ b/libraries/script-engine/src/ScriptManager.h @@ -564,6 +564,13 @@ public: */ Q_INVOKABLE void requestGarbageCollection(); + /**jsdoc + * Prints out current backtrace to the log. + * @function Script.logBacktrace + * @param {string} title - Title added to the printed out backtrace. + */ + Q_INVOKABLE void logBacktrace(const QString &title); + /*@jsdoc * @function Script.loadEntityScript * @param {Uuid} entityID - Entity ID. diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 186bba2e5b..de4e8fa264 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -1714,6 +1714,14 @@ QString ScriptEngineV8::scriptValueDebugDetailsV8(const V8ScriptValue &v8Value) } }*/ +void ScriptEngineV8::logBacktrace(const QString &title) { + QStringList backtrace = currentContext()->backtrace(); + qDebug(scriptengine) << title; + for (int n = 0; n < backtrace.length(); n++) { + qDebug(scriptengine) << backtrace[n]; + } +} + QStringList ScriptEngineV8::getCurrentScriptURLs() const { auto isolate = _v8Isolate; v8::Locker locker(isolate); diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h index 324ebb1292..e6f89d0b7d 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -136,6 +136,7 @@ public: // ScriptEngine implementation QString scriptValueDebugDetailsV8(const V8ScriptValue &value); virtual QString scriptValueDebugListMembers(const ScriptValue &value) override; QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value); + virtual void logBacktrace(const QString &title) override; // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index 5b550eef0f..b614a063fe 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -49,6 +49,7 @@ void ScriptEngineV8::registerCustomType(int type, } Q_DECLARE_METATYPE(ScriptValue); +Q_DECLARE_METATYPE(QVariantMap); /*static V8ScriptValue ScriptValueToV8ScriptValue(ScriptEngineV8* engine, const ScriptValue& src) { return ScriptValueV8Wrapper::fullUnwrap(static_cast(engine), src); diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index 4f9a4dbb43..c4b3a0a030 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -1144,10 +1144,20 @@ int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** argu for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; { - //auto functionContext = callback->CreationContext(); - auto functionContext = _v8Context.Get(_engine->getIsolate()); - _engine->pushContext(functionContext); + /*if (!conn.callback.get()->IsFunction()) { + auto stringV8 = conn.callback.get()->ToDetailString(context).ToLocalChecked(); + QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); + qDebug() << error; + Q_ASSERT(false); + } + v8::Local callback = v8::Local::Cast(conn.callback.get()); + auto functionContext = callback->CreationContext(); v8::Context::Scope functionContextScope(functionContext); + _engine->pushContext(functionContext);*/ + /*auto functionContext = _v8Context.Get(_engine->getIsolate()); + _engine->pushContext(functionContext); + v8::Context::Scope functionContextScope(functionContext);*/ + auto functionContext = context; Q_ASSERT(!conn.callback.get().IsEmpty()); Q_ASSERT(!conn.callback.get()->IsUndefined()); @@ -1178,7 +1188,7 @@ int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** argu << _engine->formatErrorMessageFromTryCatch(tryCatch) << "\nThis provided: " << conn.thisValue.get()->IsObject(); } - _engine->popContext(); + //_engine->popContext(); } } }); diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp index f76798b45b..135b44bbdd 100644 --- a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp @@ -91,6 +91,7 @@ ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const Scri //qDebug() << "V8 This: " << _engine->scriptValueDebugDetailsV8(v8This); }else{ recv = _engine->getContext()->Global(); + //recv = v8::Null(isolate); //qDebug() << "global"; } //qDebug() << "V8 Call: " << *v8::String::Utf8Value(isolate, v8This.get()->TypeOf(isolate)); diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index ad40505855..aa8430a2ce 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -18,3252 +18,3268 @@ progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, keyUpEventFromUIWindow:true */ -//(function() { // BEGIN LOCAL_SCOPE +(function() { // BEGIN LOCAL_SCOPE + //var CreateApp = function() { // BEGIN LOCAL_SCOPE + var createApp = {}; -var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; -var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; -Script.include([ - "../libraries/stringHelpers.js", - "../libraries/dataViewHelpers.js", - "../libraries/progressDialog.js", - "../libraries/ToolTip.js", - "../libraries/entityCameraTool.js", - "../libraries/utils.js", - "../libraries/entityIconOverlayManager.js", - "../libraries/gridTool.js", - "entityList/entityList.js", - "entitySelectionTool/entitySelectionTool.js", - "audioFeedback/audioFeedback.js", - "modules/brokenURLReport.js", - "editModes/editModes.js", - "editModes/editVoxels.js" -]); + Script.include([ + "../libraries/stringHelpers.js", + "../libraries/dataViewHelpers.js", + "../libraries/progressDialog.js", + "../libraries/ToolTip.js", + "../libraries/entityCameraTool.js", + "../libraries/utils.js", + "../libraries/entityIconOverlayManager.js", + "../libraries/gridTool.js", + "entityList/entityList.js", + "entitySelectionTool/entitySelectionTool.js", + "audioFeedback/audioFeedback.js", + "modules/brokenURLReport.js", + "editModes/editModes.js", + "editModes/editVoxels.js" + ]); -var CreateWindow = Script.require('./modules/createWindow.js'); + var CreateWindow = Script.require('./modules/createWindow.js'); -var TITLE_OFFSET = 60; -var CREATE_TOOLS_WIDTH = 750; -var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; -var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; + var TITLE_OFFSET = 60; + var CREATE_TOOLS_WIDTH = 750; + var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; -var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); -var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); + var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); + var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); -var createToolsWindow = new CreateWindow( - Script.resolvePath("qml/EditTools.qml"), - 'Create Tools', - 'com.highfidelity.create.createToolsWindow', - function () { - var windowHeight = Window.innerHeight - TITLE_OFFSET; - if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { - windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; - } - return { - size: { - x: CREATE_TOOLS_WIDTH, - y: windowHeight - }, - position: { - x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, - y: Window.y + TITLE_OFFSET + var createToolsWindow = new CreateWindow( + Script.resolvePath("qml/EditTools.qml"), + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; } - } - }, - false -); + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false + ); -/** - * @description Returns true in case we should use the tablet version of the CreateApp - * @returns boolean - */ -var shouldUseEditTabletApp = function() { - return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); -}; + /** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ + var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); + }; -var selectionDisplay = SelectionDisplay; -var selectionManager = SelectionManager; + var selectionDisplay = SelectionDisplay; + selectionDisplay.createApp = createApp; + var selectionManager = SelectionManager; -var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); -var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); -var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); -var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); -var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); + var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); + var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); + var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); + var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); + var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); -var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { - var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); - if (properties.type === "Light") { - return { - imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL - }; - } else if (properties.type === "Zone") { - return { imageURL: ZONE_URL }; - } else if (properties.type === "Material") { - if (properties.parentID !== Uuid.NULL && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) { - return { imageURL: MATERIAL_URL }; + var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); + if (properties.type === "Light") { + return { + imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL + }; + } else if (properties.type === "Zone") { + return { imageURL: ZONE_URL }; + } else if (properties.type === "Material") { + if (properties.parentID !== Uuid.NULL && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) { + return { imageURL: MATERIAL_URL }; + } else { + return { imageURL: "" }; + } } else { - return { imageURL: "" }; + return { imageURL: PARTICLE_SYSTEM_URL }; } - } else { - return { imageURL: PARTICLE_SYSTEM_URL }; - } -}); - -var hmdMultiSelectMode = false; -var expectingRotateAsClickedSurface = false; -var keepSelectedOnNextClick = false; - -var copiedPosition; -var copiedRotation; - -var cameraManager = new CameraManager(); - -var grid = new Grid(); -var gridTool = new GridTool({ - horizontalGrid: grid, - createToolsWindow: createToolsWindow, - shouldUseEditTabletApp: shouldUseEditTabletApp -}); -gridTool.setVisible(false); - -var editTools = new EditTools({ - createToolsWindow: createToolsWindow, -}); - -var editVoxels = new EditVoxels(); -editVoxels.editTools = editTools; - -editTools.addListener(editVoxels.updateEditSettings); -editTools.addListener(selectionManager.updateEditSettings); - -var entityShapeVisualizerSessionName = "SHAPE_VISUALIZER_" + Uuid.generate(); - -var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); -var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"], entityShapeVisualizerSessionName); - -var entityListTool = new EntityListTool(shouldUseEditTabletApp); - -selectionManager.addEventListener(function () { - selectionDisplay.updateHandles(); - entityIconOverlayManager.updatePositions(); - entityShapeVisualizer.setEntities(selectionManager.selections); -}); - -var DEGREES_TO_RADIANS = Math.PI / 180.0; -var RADIANS_TO_DEGREES = 180.0 / Math.PI; - -var MIN_ANGULAR_SIZE = 2; -var MAX_ANGULAR_SIZE = 45; -var allowLargeModels = true; -var allowSmallModels = true; - -var DEFAULT_DIMENSION = 0.20; - -var DEFAULT_DIMENSIONS = { - x: DEFAULT_DIMENSION, - y: DEFAULT_DIMENSION, - z: DEFAULT_DIMENSION -}; - -var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); - -var MENU_IMPORT_FROM_FILE = "Import Entities (.json) From a File"; -var MENU_IMPORT_FROM_URL = "Import Entities (.json) From a URL"; -var MENU_CREATE_SEPARATOR = "Create Application"; -var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences"; -var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; -var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; -var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode"; -var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; -var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; -var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; -var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; -var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius"; - -var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; -var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; -var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; -var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; -var SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup"; -var SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius"; - -var SETTING_EDIT_PREFIX = "Edit/"; - - -var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; -var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; - -// marketplace info, etc. not quite ready yet. -var SHOULD_SHOW_PROPERTY_MENU = false; -var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; -var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; - -var isActive = false; -var createButton = null; - -var IMPORTING_SVO_OVERLAY_WIDTH = 144; -var IMPORTING_SVO_OVERLAY_HEIGHT = 30; -var IMPORTING_SVO_OVERLAY_MARGIN = 5; -var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; -var importingSVOImageOverlay = Overlays.addOverlay("image", { - imageURL: Script.resolvePath("assets/images/hourglass.svg"), - width: 20, - height: 20, - alpha: 1.0, - x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, - y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, - visible: false -}); -var importingSVOTextOverlay = Overlays.addOverlay("text", { - font: { - size: 14 - }, - text: "Importing SVO...", - leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, - x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, - y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, - width: IMPORTING_SVO_OVERLAY_WIDTH, - height: IMPORTING_SVO_OVERLAY_HEIGHT, - backgroundColor: { - red: 80, - green: 80, - blue: 80 - }, - backgroundAlpha: 0.7, - visible: false -}); - -var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; -var marketplaceWindow = new OverlayWebWindow({ - title: 'Marketplace', - source: "about:blank", - width: 900, - height: 700, - visible: false -}); - -function showMarketplace(marketplaceID) { - var url = MARKETPLACE_URL; - if (marketplaceID) { - url = url + "/items/" + marketplaceID; - } - marketplaceWindow.setURL(url); - marketplaceWindow.setVisible(true); - marketplaceWindow.raise(); - - UserActivityLogger.logAction("opened_marketplace"); -} - -function hideMarketplace() { - marketplaceWindow.setVisible(false); - marketplaceWindow.setURL("about:blank"); -} - -// function toggleMarketplace() { -// if (marketplaceWindow.visible) { -// hideMarketplace(); -// } else { -// showMarketplace(); -// } -// } - -function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original - // position in the given direction. - var CORNERS = [ - { 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 }, - ]; - - // Go through all corners and find least (most negative) distance in front of position. - var distance = 0; - for (var i = 0, length = CORNERS.length; i < length; i++) { - var cornerVector = - Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); - var cornerDistance = Vec3.dot(cornerVector, direction); - distance = Math.min(cornerDistance, distance); - } - position = Vec3.sum(Vec3.multiply(distance, direction), position); - return position; -} - -// Handles any edit mode updates required when domains have switched -function checkEditPermissionsAndUpdate() { - if ((createButton === null) || (createButton === undefined)) { - //--EARLY EXIT--( nothing to safely update ) - return; - } - - var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); - createButton.editProperties({ - icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), - captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), }); - if (!hasRezPermissions && isActive) { - that.setActive(false); - tablet.gotoHomeScreen(); - } -} + var hmdMultiSelectMode = false; + var expectingRotateAsClickedSurface = false; + var keepSelectedOnNextClick = false; -// Copies the properties in `b` into `a`. `a` will be modified. -function copyProperties(a, b) { - for (var key in b) { - a[key] = b[key]; - } - return a; -} + var copiedPosition; + var copiedRotation; -const DEFAULT_DYNAMIC_PROPERTIES = { - dynamic: true, - damping: 0.39347, - angularDamping: 0.39347, - gravity: { x: 0, y: -9.8, z: 0 }, -}; + var cameraManager = new CameraManager(); -const DEFAULT_NON_DYNAMIC_PROPERTIES = { - dynamic: false, - damping: 0, - angularDamping: 0, - gravity: { x: 0, y: 0, z: 0 }, -}; + var grid = new Grid(); + selectionDisplay.grid = grid; + var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp + }); + gridTool.selectionDisplay = selectionDisplay; + gridTool.setVisible(false); -const DEFAULT_ENTITY_PROPERTIES = { - All: { - description: "", - rotation: { x: 0, y: 0, z: 0, w: 1 }, - collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", - collisionSoundURL: "", - cloneable: false, - ignoreIK: true, - canCastShadow: true, - href: "", - script: "", - serverScripts:"", - velocity: { - x: 0, - y: 0, - z: 0 + var editTools = new EditTools({ + createToolsWindow: createToolsWindow, + }); + + var editVoxels = new EditVoxels(); + editVoxels.editTools = editTools; + + editTools.addListener(editVoxels.updateEditSettings); + editTools.addListener(selectionManager.updateEditSettings); + + var entityShapeVisualizerSessionName = "SHAPE_VISUALIZER_" + Uuid.generate(); + + var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); + var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"], entityShapeVisualizerSessionName); + + var entityListTool = new EntityListTool(shouldUseEditTabletApp); + entityListTool.createApp = createApp; + + selectionManager.addEventListener(function () { + selectionDisplay.updateHandles(); + entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); + }); + + var DEGREES_TO_RADIANS = Math.PI / 180.0; + var RADIANS_TO_DEGREES = 180.0 / Math.PI; + + var MIN_ANGULAR_SIZE = 2; + var MAX_ANGULAR_SIZE = 45; + var allowLargeModels = true; + var allowSmallModels = true; + + var DEFAULT_DIMENSION = 0.20; + + var DEFAULT_DIMENSIONS = { + x: DEFAULT_DIMENSION, + y: DEFAULT_DIMENSION, + z: DEFAULT_DIMENSION + }; + + var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + + var MENU_IMPORT_FROM_FILE = "Import Entities (.json) From a File"; + var MENU_IMPORT_FROM_URL = "Import Entities (.json) From a URL"; + var MENU_CREATE_SEPARATOR = "Create Application"; + var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences"; + var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; + var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; + var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode"; + var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; + var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; + var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; + var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius"; + + var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; + var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; + var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; + var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + var SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup"; + var SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius"; + + var SETTING_EDIT_PREFIX = "Edit/"; + + + var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; + var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; + + // marketplace info, etc. not quite ready yet. + var SHOULD_SHOW_PROPERTY_MENU = false; + var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; + var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; + + var isActive = false; + var createButton = null; + + var IMPORTING_SVO_OVERLAY_WIDTH = 144; + var IMPORTING_SVO_OVERLAY_HEIGHT = 30; + var IMPORTING_SVO_OVERLAY_MARGIN = 5; + var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; + var importingSVOImageOverlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("assets/images/hourglass.svg"), + width: 20, + height: 20, + alpha: 1.0, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, + visible: false + }); + var importingSVOTextOverlay = Overlays.addOverlay("text", { + font: { + size: 14 }, - angularVelocity: { - x: 0, - y: 0, - z: 0 + text: "Importing SVO...", + leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { + red: 80, + green: 80, + blue: 80 }, - restitution: 0.5, - friction: 0.5, - density: 1000, + backgroundAlpha: 0.7, + visible: false + }); + + var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; + var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false + }); + + function showMarketplace(marketplaceID) { + var url = MARKETPLACE_URL; + if (marketplaceID) { + url = url + "/items/" + marketplaceID; + } + marketplaceWindow.setURL(url); + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); + } + + function hideMarketplace() { + marketplaceWindow.setVisible(false); + marketplaceWindow.setURL("about:blank"); + } + + // function toggleMarketplace() { + // if (marketplaceWindow.visible) { + // hideMarketplace(); + // } else { + // showMarketplace(); + // } + // } + + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { 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 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; + } + + // Handles any edit mode updates required when domains have switched + function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } + } + + // Copies the properties in `b` into `a`. `a` will be modified. + function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; + } + + const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, + }; + + const DEFAULT_NON_DYNAMIC_PROPERTIES = { dynamic: false, - }, - Shape: { - shape: "Box", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 0, green: 180, blue: 239 }, - }, - Text: { - text: "Text", - dimensions: { - x: 0.65, - y: 0.3, - z: 0.01 - }, - textColor: { red: 255, green: 255, blue: 255 }, - backgroundColor: { red: 0, green: 0, blue: 0 }, - lineHeight: 0.06, - faceCamera: false, - }, - Zone: { - dimensions: { - x: 10, - y: 10, - z: 10 - }, - flyingAllowed: true, - ghostingAllowed: true, - filter: "", - keyLightMode: "inherit", - keyLightColor: { red: 255, green: 255, blue: 255 }, - keyLight: { - intensity: 1.0, - direction: { - x: 0.0, - y: -0.707106769084930, // 45 degrees - z: 0.7071067690849304 + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, + }; + + const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 }, - castShadows: true - }, - ambientLightMode: "inherit", - ambientLight: { - ambientIntensity: 0.5, - ambientURL: "" - }, - hazeMode: "inherit", - haze: { - hazeRange: 1000, - hazeAltitudeEffect: false, - hazeBaseRef: 0, - hazeColor: { - red: 128, - green: 154, - blue: 179 + angularVelocity: { + x: 0, + y: 0, + z: 0 }, - hazeBackgroundBlend: 0, - hazeEnableGlare: false, - hazeGlareColor: { + restitution: 0.5, + friction: 0.5, + density: 1000, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + shapeType: "box", + bloomMode: "inherit", + avatarPriority: "inherit", + screenshare: "inherit", + }, + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE + }, + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://overte.org/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: DEFAULT_PARTICLE, + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusSpread: 0, + particleColor: { red: 255, - green: 229, - blue: 179 + green: 255, + blue: 255 }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: Math.PI, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI }, - shapeType: "box", - bloomMode: "inherit", - avatarPriority: "inherit", - screenshare: "inherit", - }, - Model: { - collisionShape: "none", - compoundShapeURL: "", - animation: { - url: "", - running: false, - allowTranslation: false, - loop: true, - hold: false, - currentFrame: 0, - firstFrame: 0, - lastFrame: 100000, - fps: 30.0, - } - }, - Image: { - dimensions: { - x: 0.5385, - y: 0.2819, - z: 0.0092 + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, }, - shapeType: "box", - collisionless: true, - keepAspectRatio: false, - imageURL: DEFAULT_IMAGE - }, - Web: { - dimensions: { - x: 1.6, - y: 0.9, - z: 0.01 - }, - sourceUrl: "https://overte.org/", - dpi: 30, - }, - ParticleEffect: { - lifespan: 1.5, - maxParticles: 10, - textures: DEFAULT_PARTICLE, - emitRate: 5.5, - emitSpeed: 0, - speedSpread: 0, - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, - emitterShouldTrail: true, - particleRadius: 0.25, - radiusStart: 0, - radiusSpread: 0, - particleColor: { - red: 255, - green: 255, - blue: 255 - }, - colorSpread: { - red: 0, - green: 0, - blue: 0 - }, - alpha: 0, - alphaStart: 1, - alphaSpread: 0, - emitAcceleration: { - x: 0, - y: 2.5, - z: 0 - }, - accelerationSpread: { - x: 0, - y: 0, - z: 0 - }, - particleSpin: 0, - spinSpread: 0, - rotateWithEntity: false, - polarStart: 0, - polarFinish: Math.PI, - azimuthStart: -Math.PI, - azimuthFinish: Math.PI - }, - Light: { - color: { red: 255, green: 255, blue: 255 }, - intensity: 5.0, - dimensions: DEFAULT_LIGHT_DIMENSIONS, - falloffRadius: 1.0, - isSpotlight: false, - exponent: 1.0, - cutoff: 75.0, - }, -}; + }; -var toolBar = (function () { - var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts - var that = {}, - toolBar, - activeButton = null, - systemToolbar = null, - dialogWindow = null, - tablet = null; + var toolBar = (function () { + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts + var that = {}, + toolBar, + activeButton = null, + systemToolbar = null, + dialogWindow = null, + tablet = null; - function createNewEntity(requestedProperties) { - var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; - var position = getPositionToCreateEntity(); - var entityID = null; + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = getPositionToCreateEntity(); + var entityID = null; - var properties = {}; + var properties = {}; - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); - var type = requestedProperties.type; - if (type === "Box" || type === "Sphere") { - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); - } else { - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); - } - - // We apply the requested properties first so that they take priority over any default properties. - copyProperties(properties, requestedProperties); - - if (properties.dynamic) { - copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); - } else { - copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); - } - - - if (position !== null && position !== undefined) { - var direction; - if (Camera.mode === "entity" || Camera.mode === "independent") { - direction = Camera.orientation; + var type = requestedProperties.type; + if (type === "Box" || type === "Sphere") { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); } else { - direction = MyAvatar.orientation; + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); } - direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; - if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // We apply the requested properties first so that they take priority over any default properties. + copyProperties(properties, requestedProperties); - // Adjust position of entity per bounding box prior to creating it. - var registration = properties.registration; - if (registration === undefined) { - var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; - registration = DEFAULT_REGISTRATION; - } + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } - var orientation = properties.orientation; - if (orientation === undefined) { - properties.orientation = MyAvatar.orientation; - var DEFAULT_ORIENTATION = properties.orientation; - orientation = DEFAULT_ORIENTATION; + + if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; } else { - // If the orientation is already defined, we perform the corresponding rotation assuming that - // our start referential is the avatar referential. - properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); - var DEFAULT_ORIENTATION = properties.orientation; - orientation = DEFAULT_ORIENTATION; + direction = MyAvatar.orientation; + } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); + + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } + + var orientation = properties.orientation; + if (orientation === undefined) { + properties.orientation = MyAvatar.orientation; + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } else { + // If the orientation is already defined, we perform the corresponding rotation assuming that + // our start referential is the avatar referential. + properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); } - position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); - } + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); + properties.position = position; - position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); - properties.position = position; - - if (!properties.grab) { - properties.grab = {}; - if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && - !(properties.type === "Zone" || properties.type === "Light" - || properties.type === "ParticleEffect" || properties.type === "Web")) { - properties.grab.grabbable = true; - } else { - properties.grab.grabbable = false; + if (!properties.grab) { + properties.grab = {}; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" + || properties.type === "ParticleEffect" || properties.type === "Web")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } } - } - if (type === "Model") { - properties.visible = false; - } + if (type === "Model") { + properties.visible = false; + } - entityID = Entities.addEntity(properties); + entityID = Entities.addEntity(properties); + + var dimensionsCheckCallback = function(){ + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + // Make sure the model entity is loaded before we try to figure out + // its dimensions. We need to give ample time to load the entity. + var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds. + var LOADED_CHECK_INTERVAL = 100; + var isLoadedCheckCount = 0; + var entityIsLoadedCheck = function() { + isLoadedCheckCount++; + if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) { + var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions; + + if (isLoadedCheckCount === MAX_LOADED_CHECKS) { + console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions)) + } - var dimensionsCheckCallback = function(){ - // Adjust position of entity per bounding box after it has been created and auto-resized. - var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; - var DIMENSIONS_CHECK_INTERVAL = 200; - var MAX_DIMENSIONS_CHECKS = 10; - var dimensionsCheckCount = 0; - var dimensionsCheckFunction = function () { - dimensionsCheckCount++; - var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); - if (!Vec3.equal(properties.dimensions, initialDimensions)) { - position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, - properties.dimensions, properties.rotation); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), - properties.dimensions); Entities.editEntity(entityID, { - position: position - }); - selectionManager._update(false, this); - } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + visible: true, + dimensions: naturalDimensions + }) + dimensionsCheckCallback(); + // We want to update the selection manager again since the script has moved on without us. + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); + return; } - }; - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); - } - // Make sure the model entity is loaded before we try to figure out - // its dimensions. We need to give ample time to load the entity. - var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds. - var LOADED_CHECK_INTERVAL = 100; - var isLoadedCheckCount = 0; - var entityIsLoadedCheck = function() { - isLoadedCheckCount++; - if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) { - var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions; - - if (isLoadedCheckCount === MAX_LOADED_CHECKS) { - console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions)) - } - - Entities.editEntity(entityID, { - visible: true, - dimensions: naturalDimensions - }) - dimensionsCheckCallback(); - // We want to update the selection manager again since the script has moved on without us. - selectionManager.clearSelections(this); - entityListTool.sendUpdate(); - selectionManager.setSelections([entityID], this); - return; + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); } - Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); - } - - var POST_ADJUST_ENTITY_TYPES = ["Model"]; - if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { - Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); + } + + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + createApp.pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); + + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); } - SelectionManager.addEntity(entityID, false, this); - SelectionManager.saveProperties(); - pushCommandForSelections([{ - entityID: entityID, - properties: properties - }], [], true); + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); - } else { - Window.notifyEditError("Can't create " + properties.type + ": " + - properties.type + " would be out of bounds."); + Window.setFocus(); + + return entityID; } - selectionManager.clearSelections(this); - entityListTool.sendUpdate(); - selectionManager.setSelections([entityID], this); - - Window.setFocus(); - - return entityID; - } - - function closeExistingDialogWindow() { - if (dialogWindow) { - dialogWindow.close(); - dialogWindow = null; - } - } - - function cleanup() { - that.setActive(false); - if (tablet) { - tablet.removeButton(activeButton); - } - if (systemToolbar) { - systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); - } - } - - var buttonHandlers = {}; // only used to tablet mode - - function addButton(name, handler) { - buttonHandlers[name] = handler; - } - - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; - var DYNAMIC_DEFAULT = false; - - var MATERIAL_MODE_UV = 0; - var MATERIAL_MODE_PROJECTED = 1; - - function handleNewModelDialogResult(result) { - if (result) { - var url = result.url; - var shapeType; - switch (result.collisionShapeIndex) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; } + } - var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - createNewEntity({ - type: "Model", - modelURL: url, - shapeType: shapeType, + function cleanup() { + that.setActive(false); + if (tablet) { + tablet.removeButton(activeButton); + } + if (systemToolbar) { + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + } + + var buttonHandlers = {}; // only used to tablet mode + + function addButton(name, handler) { + buttonHandlers[name] = handler; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var DYNAMIC_DEFAULT = false; + + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + + function handleNewModelDialogResult(result) { + if (result) { + var url = result.url; + var shapeType; + switch (result.collisionShapeIndex) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; + } + + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + createNewEntity({ + type: "Model", + modelURL: url, + shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, + dynamic: dynamic, + useOriginalPivot: result.useOriginalPivot + }); + } + } + } + + function handleNewPolyVoxDialogResult(result) { + if (result) { + var initialShape = result.initialShapeIndex; + var volumeSizeX = parseInt(result.volumeSizeX); + var volumeSizeY = parseInt(result.volumeSizeY); + var volumeSizeZ = parseInt(result.volumeSizeZ); + var voxelSurfaceStyle = parseInt(result.surfaceStyleIndex); + var voxelPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: volumeSizeZ * -1.6 })); + + var polyVoxID = createNewEntity({ + type: "PolyVox", + name: "terrain", + dimensions: { + x: volumeSizeX, + y: volumeSizeY, + z: volumeSizeZ + }, + voxelVolumeSize: { + x: volumeSizeX, + y: volumeSizeY, + z: volumeSizeZ + }, + xTextureURL: result.xTextureURL, + yTextureURL: result.yTextureURL, + zTextureURL: result.zTextureURL, + voxelSurfaceStyle: voxelSurfaceStyle, + collisionless: !(result.collisions), grab: { grabbable: result.grabbable }, - dynamic: dynamic, - useOriginalPivot: result.useOriginalPivot }); - } - } - } - - function handleNewPolyVoxDialogResult(result) { - if (result) { - var initialShape = result.initialShapeIndex; - var volumeSizeX = parseInt(result.volumeSizeX); - var volumeSizeY = parseInt(result.volumeSizeY); - var volumeSizeZ = parseInt(result.volumeSizeZ); - var voxelSurfaceStyle = parseInt(result.surfaceStyleIndex); - var voxelPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: volumeSizeZ * -1.6 })); - - var polyVoxID = createNewEntity({ - type: "PolyVox", - name: "terrain", - dimensions: { - x: volumeSizeX, - y: volumeSizeY, - z: volumeSizeZ - }, - voxelVolumeSize: { - x: volumeSizeX, - y: volumeSizeY, - z: volumeSizeZ - }, - xTextureURL: result.xTextureURL, - yTextureURL: result.yTextureURL, - zTextureURL: result.zTextureURL, - voxelSurfaceStyle: voxelSurfaceStyle, - collisionless: !(result.collisions), - grab: { - grabbable: result.grabbable - }, - }); - - Entities.editEntity(polyVoxID, { - position: voxelPosition - }); - - if (polyVoxID){ - switch (initialShape) { - case 0: - Entities.setVoxelsInCuboid(polyVoxID, { - x: Math.round(volumeSizeX / 4), - y: Math.round(volumeSizeY / 4), - z: Math.round(volumeSizeZ / 4) - }, { - x: Math.round(volumeSizeX / 2.0), - y: Math.round(volumeSizeY / 2.0), - z: Math.round(volumeSizeZ / 2.0) - }, 255); - break; - // Plane 1/4 - case 1: - Entities.setVoxelsInCuboid(polyVoxID, { - x: 0, - y: 0, - z: 0 - }, { - x: volumeSizeX, - y: Math.round(volumeSizeY / 4), - z: volumeSizeZ - }, 255); - break; - // Plane 3/4 - case 2: - Entities.setVoxelsInCuboid(polyVoxID, { - x: 0, - y: 0, - z: 0 - }, { - x: volumeSizeX, - y: Math.round(3 * volumeSizeY / 4), - z: volumeSizeZ - }, 255); - break; - // Single voxel at center - case 3: - Entities.setVoxel(polyVoxID, { - x: Math.round(volumeSizeX / 2), - y: Math.round(volumeSizeY / 2), - z: Math.round(volumeSizeZ / 2) - }, 255); - break; + + Entities.editEntity(polyVoxID, { + position: voxelPosition + }); + + if (polyVoxID){ + switch (initialShape) { + case 0: + Entities.setVoxelsInCuboid(polyVoxID, { + x: Math.round(volumeSizeX / 4), + y: Math.round(volumeSizeY / 4), + z: Math.round(volumeSizeZ / 4) + }, { + x: Math.round(volumeSizeX / 2.0), + y: Math.round(volumeSizeY / 2.0), + z: Math.round(volumeSizeZ / 2.0) + }, 255); + break; + // Plane 1/4 + case 1: + Entities.setVoxelsInCuboid(polyVoxID, { + x: 0, + y: 0, + z: 0 + }, { + x: volumeSizeX, + y: Math.round(volumeSizeY / 4), + z: volumeSizeZ + }, 255); + break; + // Plane 3/4 + case 2: + Entities.setVoxelsInCuboid(polyVoxID, { + x: 0, + y: 0, + z: 0 + }, { + x: volumeSizeX, + y: Math.round(3 * volumeSizeY / 4), + z: volumeSizeZ + }, 255); + break; + // Single voxel at center + case 3: + Entities.setVoxel(polyVoxID, { + x: Math.round(volumeSizeX / 2), + y: Math.round(volumeSizeY / 2), + z: Math.round(volumeSizeZ / 2) + }, 255); + break; + } } } } - } - function handleNewMaterialDialogResult(result) { - if (result) { - var materialURL = result.textInput; - if (materialURL === "") { - materialURL = "materialData"; + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + if (materialURL === "") { + materialURL = "materialData"; + } + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }); + } + + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData + }); + } } - //var materialMappingMode; - //switch (result.comboBox) { - // case MATERIAL_MODE_PROJECTED: - // materialMappingMode = "projected"; - // break; - // default: - // shapeType = "uv"; - //} - var materialData = ""; - if (materialURL.startsWith("materialData")) { - materialData = JSON.stringify({ - "materials": {} - }); + } + + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.popFromStack(); + switch (message.method) { + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; + case "newPolyVoxDialogAdd": + handleNewPolyVoxDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newPolyVoxDialogCancel": + closeExistingDialogWindow(); + break; + } + } + + var entitiesToDelete = []; + var deletedEntityTimer = null; + var DELETE_ENTITY_TIMER_TIMEOUT = 100; + + function checkDeletedEntityAndUpdate(entityID) { + // Allow for multiple entity deletes before updating the entities selected. + entitiesToDelete.push(entityID); + if (deletedEntityTimer !== null) { + Script.clearTimeout(deletedEntityTimer); + } + deletedEntityTimer = Script.setTimeout(function () { + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete, this); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); + entitiesToDelete = []; + deletedEntityTimer = null; + }, DELETE_ENTITY_TIMER_TIMEOUT); + } + + function initialize() { + Script.scriptEnding.connect(cleanup); + Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + that.clearEntityList(); + checkEditPermissionsAndUpdate(); + }); + + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); + + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); + } + checkEditPermissionsAndUpdate(); + }); + + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + + Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + + var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + activeButton = tablet.addButton({ + captionColor: hasRezPermissions ? "#ffffff" : "#888888", + icon: createButtonIconRsrc, + activeIcon: "icons/tablet-icons/edit-a.svg", + text: "CREATE", + sortOrder: 10 + }); + createButton = activeButton; + tablet.screenChanged.connect(function (type, url) { + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== Script.resolvePath("qml/Edit.qml")) && !isGoingToHomescreenOnDesktop) { + that.setActive(false); + } + }); + tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); + + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + + that.toggle(); + }); + + addButton("importEntitiesButton", function() { + importEntitiesFromFile(); + }); + + addButton("importEntitiesFromUrlButton", function() { + importEntitiesFromUrl(); + }); + + addButton("openAssetBrowserButton", function() { + Window.showAssetServer(); + }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml")); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml"); + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + if( entityType === "PolyVox" ){ + DIALOG_WINDOW_SIZE.x = 600; + DIALOG_WINDOW_SIZE.y = 500; + } + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; } - var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; - if (materialURL) { + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + + addButton("newShapeButton", function () { createNewEntity({ - type: "Material", - materialURL: materialURL, - //materialMappingMode: materialMappingMode, - priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, - materialData: materialData + type: "Shape", + shape: "Cube", }); - } + }); + + addButton("newLightButton", function () { + createNewEntity({ + type: "Light", + }); + }); + + addButton("newTextButton", function () { + createNewEntity({ + type: "Text", + }); + }); + + addButton("newImageButton", function () { + createNewEntity({ + type: "Image", + }); + }); + + addButton("newWebButton", function () { + createNewEntity({ + type: "Web", + }); + }); + + addButton("newZoneButton", function () { + createNewEntity({ + type: "Zone", + }); + }); + + addButton("newParticleButton", function () { + createNewEntity({ + type: "ParticleEffect", + }); + }); + + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + + addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); + + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + + that.setActive(false); } - } - function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.popFromStack(); - switch (message.method) { - case "newModelDialogAdd": - handleNewModelDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newModelDialogCancel": - closeExistingDialogWindow(); - break; - case "newEntityButtonClicked": - buttonHandlers[message.params.buttonName](); - break; - case "newMaterialDialogAdd": - handleNewMaterialDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newMaterialDialogCancel": - closeExistingDialogWindow(); - break; - case "newPolyVoxDialogAdd": - handleNewPolyVoxDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newPolyVoxDialogCancel": - closeExistingDialogWindow(); - break; - } - } + that.clearEntityList = function () { + entityListTool.clearEntityList(); + }; - var entitiesToDelete = []; - var deletedEntityTimer = null; - var DELETE_ENTITY_TIMER_TIMEOUT = 100; - - function checkDeletedEntityAndUpdate(entityID) { - // Allow for multiple entity deletes before updating the entities selected. - entitiesToDelete.push(entityID); - if (deletedEntityTimer !== null) { - Script.clearTimeout(deletedEntityTimer); - } - deletedEntityTimer = Script.setTimeout(function () { - if (entitiesToDelete.length > 0) { - selectionManager.removeEntities(entitiesToDelete, this); - } - entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); - entitiesToDelete = []; - deletedEntityTimer = null; - }, DELETE_ENTITY_TIMER_TIMEOUT); - } - - function initialize() { - Script.scriptEnding.connect(cleanup); - Window.domainChanged.connect(function () { - if (isActive) { + that.toggle = function () { + that.setActive(!isActive); + if (!isActive) { tablet.gotoHomeScreen(); } - that.setActive(false); - that.clearEntityList(); - checkEditPermissionsAndUpdate(); - }); + }; - HMD.displayModeChanged.connect(function() { - if (isActive) { - tablet.gotoHomeScreen(); + that.setActive = function (active) { + ContextOverlay.enabled = !active; + Settings.setValue(EDIT_SETTING, active); + if (active) { + Controller.captureEntityClickEvents(); + } else { + Controller.releaseEntityClickEvents(); + + closeExistingDialogWindow(); } - that.setActive(false); - }); - - Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { - if (isActive && !canAdjustLocks) { - that.setActive(false); + if (active === isActive) { + return; } - checkEditPermissionsAndUpdate(); - }); - - Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); - var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); - - Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); - - var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - activeButton = tablet.addButton({ - captionColor: hasRezPermissions ? "#ffffff" : "#888888", - icon: createButtonIconRsrc, - activeIcon: "icons/tablet-icons/edit-a.svg", - text: "CREATE", - sortOrder: 10 - }); - createButton = activeButton; - tablet.screenChanged.connect(function (type, url) { - var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && - (url === 'hifi/tablet/TabletHome.qml' || url === '')); - if (isActive && (type !== "QML" || url !== Script.resolvePath("qml/Edit.qml")) && !isGoingToHomescreenOnDesktop) { - that.setActive(false); - } - }); - tablet.fromQml.connect(fromQml); - createToolsWindow.fromQml.addListener(fromQml); - - createButton.clicked.connect(function() { - if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); return; } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + print("Setting isActive: " + active); + isActive = active; + activeButton.editProperties({isActive: isActive}); + undoHistory.setEnabled(isActive); - that.toggle(); - }); + editVoxels.setActive(active); - addButton("importEntitiesButton", function() { - importEntitiesFromFile(); - }); + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - addButton("importEntitiesFromUrlButton", function() { - importEntitiesFromUrl(); - }); - - addButton("openAssetBrowserButton", function() { - Window.showAssetServer(); - }); - function createNewEntityDialogButtonCallback(entityType) { - return function() { - if (shouldUseEditTabletApp()) { - // tablet version of new-model dialog - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml")); - } else { - closeExistingDialogWindow(); - var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml"); - var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; - if( entityType === "PolyVox" ){ - DIALOG_WINDOW_SIZE.x = 600; - DIALOG_WINDOW_SIZE.y = 500; - } - dialogWindow = Desktop.createWindow(qmlPath, { - title: "New " + entityType + " Entity", - additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, - presentationMode: Desktop.PresentationMode.NATIVE, - size: DIALOG_WINDOW_SIZE, - visible: true - }); - dialogWindow.fromQml.connect(fromQml); - } - }; - } - - addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); - - addButton("newShapeButton", function () { - createNewEntity({ - type: "Shape", - shape: "Cube", - }); - }); - - addButton("newLightButton", function () { - createNewEntity({ - type: "Light", - }); - }); - - addButton("newTextButton", function () { - createNewEntity({ - type: "Text", - }); - }); - - addButton("newImageButton", function () { - createNewEntity({ - type: "Image", - }); - }); - - addButton("newWebButton", function () { - createNewEntity({ - type: "Web", - }); - }); - - addButton("newZoneButton", function () { - createNewEntity({ - type: "Zone", - }); - }); - - addButton("newParticleButton", function () { - createNewEntity({ - type: "ParticleEffect", - }); - }); - - addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); - - addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); - - var deactivateCreateIfDesktopWindowsHidden = function() { - if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { - that.setActive(false); - } - }; - entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); - createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); - - that.setActive(false); - } - - that.clearEntityList = function () { - entityListTool.clearEntityList(); - }; - - that.toggle = function () { - that.setActive(!isActive); - if (!isActive) { - tablet.gotoHomeScreen(); - } - }; - - that.setActive = function (active) { - ContextOverlay.enabled = !active; - Settings.setValue(EDIT_SETTING, active); - if (active) { - Controller.captureEntityClickEvents(); - } else { - Controller.releaseEntityClickEvents(); - - closeExistingDialogWindow(); - } - if (active === isActive) { - return; - } - if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { - Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); - return; - } - Messages.sendLocalMessage("edit-events", JSON.stringify({ - enabled: active - })); - isActive = active; - activeButton.editProperties({isActive: isActive}); - undoHistory.setEnabled(isActive); - - editVoxels.setActive(active); - - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - - if (!isActive) { - entityListTool.setVisible(false); - gridTool.setVisible(false); - grid.setEnabled(false); - propertiesTool.setVisible(false); - selectionManager.clearSelections(this); - cameraManager.disable(); - selectionDisplay.disableTriggerMapping(); - tablet.landscape = false; - Controller.disableMapping(CONTROLLER_MAPPING_NAME); - } else { - if (shouldUseEditTabletApp()) { - tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(this); + cameraManager.disable(); + selectionDisplay.disableTriggerMapping(); + tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); } else { - // make other apps inactive while in desktop mode - tablet.gotoHomeScreen(); + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } + UserActivityLogger.enabledEdit(); + entityListTool.setVisible(true); + entityListTool.sendUpdate(); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.enableTriggerMapping(); + print("starting tablet in landscape mode"); + tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); } - UserActivityLogger.enabledEdit(); - entityListTool.setVisible(true); - entityListTool.sendUpdate(); - gridTool.setVisible(true); - grid.setEnabled(true); - propertiesTool.setVisible(true); - selectionDisplay.enableTriggerMapping(); - print("starting tablet in landscape mode"); - tablet.landscape = true; - Controller.enableMapping(CONTROLLER_MAPPING_NAME); - // Not sure what the following was meant to accomplish, but it currently causes - // everybody else to think that Interface has lost focus overall. fogbugzid:558 - // Window.setFocus(); - } - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - }; + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + }; - initialize(); - return that; -})(); + initialize(); + return that; + })(); -var selectedEntityID; -var orientation; -var intersection; + var selectedEntityID; + var orientation; + var intersection; -function rayPlaneIntersection(pickRay, point, normal) { // - // - // This version of the test returns the intersection of a line with a plane - // - var collides = Vec3.dot(pickRay.direction, normal); + createApp.rayPlaneIntersection = function(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); - var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; - return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); -} - -function rayPlaneIntersection2(pickRay, point, normal) { - // - // This version of the test returns false if the ray is directed away from the plane - // - var collides = Vec3.dot(pickRay.direction, normal); - var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; - if (t < 0.0) { - return false; - } else { return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); } -} -function findClickedEntity(event) { - var pickZones = event.isControl; - - if (pickZones) { - Entities.setZonesArePickable(true); - } - - var pickRay = Camera.computePickRay(event.x, event.y); - var tabletIDs = getMainTabletIDs(); - if (tabletIDs.length > 0) { - var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); - if (overlayResult.intersects) { - return null; - } - } - - var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking - var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); - iconResult.accurate = true; - - if (pickZones) { - Entities.setZonesArePickable(false); - } - - var result; - if (expectingRotateAsClickedSurface) { - if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected, or the selection is locked."); - expectingRotateAsClickedSurface = false; - } else { - //Rotate Selection according the Surface Normal - var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1)); - selectionDisplay.rotateSelection(normalRotation); - //Translate Selection according the clicked Surface - var distanceFromSurface; - if (selectionDisplay.getSpaceMode() === "world"){ - distanceFromSurface = SelectionManager.worldDimensions.z / 2; - } else { - distanceFromSurface = SelectionManager.localDimensions.z / 2; - } - selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); - selectionManager._update(false, this); - pushCommandForSelections(); - expectingRotateAsClickedSurface = false; - audioFeedback.action(); - } - keepSelectedOnNextClick = true; - return null; - } else { - if (iconResult.intersects) { - result = iconResult; - } else if (entityResult.intersects) { - result = entityResult; - } else { - return null; - } - - if (!result.accurate) { - return null; - } - - var foundEntity = result.entityID; - return { - pickRay: pickRay, - entityID: foundEntity, - intersection: result.intersection - }; - } -} - -// Handles selections on overlays while in edit mode by querying entities from -// entityIconOverlayManager. -function handleOverlaySelectionToolUpdates(channel, message, sender) { - var wantDebug = false; - if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') - return; - - var data = JSON.parse(message); - - if (data.method === "selectOverlay") { - if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { - if (wantDebug) { - print("setting selection to overlay " + data.overlayID); - } - var entity = entityIconOverlayManager.findEntity(data.overlayID); - - if (entity !== null) { - if (hmdMultiSelectMode) { - selectionManager.addEntity(entity, true, this); - } else { - selectionManager.setSelections([entity], this); - } - } - } - } -} - -function handleMessagesReceived(channel, message, sender) { - switch( channel ){ - case 'entityToolUpdates': { - handleOverlaySelectionToolUpdates( channel, message, sender ); - break; - } - default: { - return; - } - } -} - -Messages.subscribe("entityToolUpdates"); -Messages.messageReceived.connect(handleMessagesReceived); - -var mouseHasMovedSincePress = false; -var mousePressStartTime = 0; -var mousePressStartPosition = { - x: 0, - y: 0 -}; -var mouseDown = false; - -function mousePressEvent(event) { - mouseDown = true; - mousePressStartPosition = { - x: event.x, - y: event.y - }; - mousePressStartTime = Date.now(); - mouseHasMovedSincePress = false; - mouseCapturedByTool = false; - - if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { - mouseCapturedByTool = true; - return; - } - if (isActive) { - if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { - // Event handled; do nothing. - return; - } - } -} - -var mouseCapturedByTool = false; -var lastMousePosition = null; -var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms -var CLICK_MOVE_DISTANCE_THRESHOLD = 20; -var IDLE_MOUSE_TIMEOUT = 200; - -var lastMouseMoveEvent = null; - -function mouseMoveEventBuffered(event) { - lastMouseMoveEvent = event; -} - -function mouseMove(event) { - if (mouseDown && !mouseHasMovedSincePress) { - var timeSincePressMicro = Date.now() - mousePressStartTime; - - var dX = mousePressStartPosition.x - event.x; - var dY = mousePressStartPosition.y - event.y; - var sqDist = (dX * dX) + (dY * dY); - - // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved - // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move - // yet. The goal is to provide mouse clicks that are more lenient to small movements. - if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { - return; - } - mouseHasMovedSincePress = true; - } - - if (!isActive) { - return; - } - - // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing - if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { - return; - } - - lastMousePosition = { - x: event.x, - y: event.y - }; -} - -function mouseReleaseEvent(event) { - mouseDown = false; - - if (lastMouseMoveEvent) { - mouseMove(lastMouseMoveEvent); - lastMouseMoveEvent = null; - } - if (propertyMenu.mouseReleaseEvent(event)) { - return true; - } - if (isActive && selectionManager.hasSelection()) { - tooltip.show(false); - } - if (mouseCapturedByTool) { - - return; - } - - cameraManager.mouseReleaseEvent(event); - - if (!mouseHasMovedSincePress) { - mouseClickEvent(event); - } -} - -function wasTabletOrEditHandleClicked(event) { - var rayPick = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(rayPick, true); - if (result.intersects) { - var overlayID = result.overlayID; - var tabletIDs = getMainTabletIDs(); - if (tabletIDs.indexOf(overlayID) >= 0) { - return true; - } else if (selectionDisplay.isEditHandle(overlayID)) { - return true; - } - } - return false; -} - -function mouseClickEvent(event) { - var wantDebug = false; - var result, properties, tabletClicked; - if (isActive && event.isLeftButton) { - result = findClickedEntity(event); - var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); - if (tabletOrEditHandleClicked) { - return; - } - - if (result === null || result === undefined) { - if (!event.isShifted) { - if (!keepSelectedOnNextClick) { - selectionManager.clearSelections(this); - } - keepSelectedOnNextClick = false; - } - return; - } - toolBar.setActive(true); - var pickRay = result.pickRay; - var foundEntity = result.entityID; - if (HMD.tabletID && foundEntity === HMD.tabletID) { - return; - } - properties = Entities.getEntityProperties(foundEntity); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - if (wantDebug) { - print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); - } - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A + createApp.rayPlaneIntersection2 = function(pickRay, point, normal) { // - // |X-A| = (P-A).B - // X === A + ((P-A).B)B - // d = |P-X| + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); + } + } - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; + function findClickedEntity(event) { + var pickZones = event.isControl; - var x = Vec3.dot(Vec3.subtract(P, A), B); + if (pickZones) { + Entities.setZonesArePickable(true); + } - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * - 180 / Math.PI; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && - (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK && selectionManager.editEnabled) { - selectedEntityID = foundEntity; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); - - if (!event.isShifted) { - selectionManager.setSelections([foundEntity], this); - } else { - selectionManager.addEntity(foundEntity, true, this); - } - selectionManager.saveProperties(); - - if (wantDebug) { - print("Model selected: " + foundEntity); - } - selectionDisplay.select(selectedEntityID, event); - - if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; } } - } else if (event.isRightButton) { - result = findClickedEntity(event); - if (result) { - if (SHOULD_SHOW_PROPERTY_MENU !== true) { + + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); + iconResult.accurate = true; + + if (pickZones) { + Entities.setZonesArePickable(false); + } + + var result; + if (expectingRotateAsClickedSurface) { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + expectingRotateAsClickedSurface = false; + } else { + //Rotate Selection according the Surface Normal + var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1)); + selectionDisplay.rotateSelection(normalRotation); + //Translate Selection according the clicked Surface + var distanceFromSurface; + if (selectionDisplay.getSpaceMode() === "world"){ + distanceFromSurface = SelectionManager.worldDimensions.z / 2; + } else { + distanceFromSurface = SelectionManager.localDimensions.z / 2; + } + selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); + selectionManager._update(false, this); + createApp.pushCommandForSelections(); + expectingRotateAsClickedSurface = false; + audioFeedback.action(); + } + keepSelectedOnNextClick = true; + return null; + } else { + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } + + if (!result.accurate) { + return null; + } + + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; + } + } + + // Handles selections on overlays while in edit mode by querying entities from + // entityIconOverlayManager. + function handleOverlaySelectionToolUpdates(channel, message, sender) { + var wantDebug = false; + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; + + var data = JSON.parse(message); + + if (data.method === "selectOverlay") { + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); + + if (entity !== null) { + if (hmdMultiSelectMode) { + selectionManager.addEntity(entity, true, this); + } else { + selectionManager.setSelections([entity], this); + } + } + } + } + } + + function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + default: { return; } - properties = Entities.getEntityProperties(result.entityID); - if (properties.marketplaceID) { - propertyMenu.marketplaceID = properties.marketplaceID; - propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); - } else { - propertyMenu.marketplaceID = null; - propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); - } - propertyMenu.setPosition(event.x, event.y); - propertyMenu.show(); - } else { - propertyMenu.hide(); } } -} -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + Messages.subscribe("entityToolUpdates"); + Messages.messageReceived.connect(handleMessagesReceived); + var mouseHasMovedSincePress = false; + var mousePressStartTime = 0; + var mousePressStartPosition = { + x: 0, + y: 0 + }; + var mouseDown = false; -// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already -// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that -// added it. -var originalLightsArePickable = Entities.getLightsArePickable(); + function mousePressEvent(event) { + mouseDown = true; + mousePressStartPosition = { + x: event.x, + y: event.y + }; + mousePressStartTime = Date.now(); + mouseHasMovedSincePress = false; + mouseCapturedByTool = false; -function setupModelMenus() { - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "Undo", - shortcutKey: 'Ctrl+Z', - position: 0, - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "Redo", - shortcutKey: 'Ctrl+Y', - position: 1, - }); - - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_CREATE_SEPARATOR, - isSeparator: true - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_IMPORT_FROM_FILE, - afterItem: MENU_CREATE_SEPARATOR - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_IMPORT_FROM_URL, - afterItem: MENU_IMPORT_FROM_FILE - }); - - Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); - - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, - position: 0, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_LARGE, - afterItem: MENU_CREATE_ENTITIES_GRABBABLE, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_SMALL, - afterItem: MENU_ALLOW_SELECTION_LARGE, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_LIGHTS, - afterItem: MENU_ALLOW_SELECTION_SMALL, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_AUTO_FOCUS_ON_SELECT, - afterItem: MENU_ALLOW_SELECTION_LIGHTS, - isCheckable: true, - isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_EASE_ON_FOCUS, - afterItem: MENU_AUTO_FOCUS_ON_SELECT, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE, - afterItem: MENU_EASE_ON_FOCUS, - isCheckable: true, - isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS, - afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE - }); - - Entities.setLightsArePickable(false); -} - -setupModelMenus(); // do this when first running our script. - -function cleanupModelMenus() { - Menu.removeMenuItem("Edit", "Undo"); - Menu.removeMenuItem("Edit", "Redo"); - - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS); - Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); - Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_URL); - Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_FILE); - Menu.removeSeparator("Edit", MENU_CREATE_SEPARATOR); -} - -Script.scriptEnding.connect(function () { - toolBar.setActive(false); - Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); - Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); - - - progressDialog.cleanup(); - cleanupModelMenus(); - tooltip.cleanup(); - selectionDisplay.cleanup(); - entityShapeVisualizer.cleanup(); - Entities.setLightsArePickable(originalLightsArePickable); - - Overlays.deleteOverlay(importingSVOImageOverlay); - Overlays.deleteOverlay(importingSVOTextOverlay); - - Controller.keyReleaseEvent.disconnect(keyReleaseEvent); - Controller.keyPressEvent.disconnect(keyPressEvent); - - Controller.mousePressEvent.disconnect(mousePressEvent); - Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); - Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); - - Messages.messageReceived.disconnect(handleMessagesReceived); - Messages.unsubscribe("entityToolUpdates"); - createButton = null; -}); - -var lastOrientation = Camera.orientation; -var lastPosition = Camera.position; - -// Do some stuff regularly, like check for placement of various overlays -Script.update.connect(function (deltaTime) { - progressDialog.move(); - selectionDisplay.checkControllerMove(); - var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); - var dPosition = Vec3.distance(Camera.position, lastPosition); - if (dOrientation > 0.001 || dPosition > 0.001) { - propertyMenu.hide(); - lastOrientation = Camera.orientation; - lastPosition = Camera.position; - } - if (lastMouseMoveEvent) { - mouseMove(lastMouseMoveEvent); - lastMouseMoveEvent = null; - } -}); - -function insideBox(center, dimensions, point) { - return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && - (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && - (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); -} - -function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { - if (selectionManager.hasSelection()) { - // Get all entities touching the bounding box of the current selection - var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, - Vec3.multiply(selectionManager.worldDimensions, 0.5)); - var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); - - if (!keepIfTouching) { - var isValid; - if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { - isValid = function (position) { - return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); - }; - } else { - isValid = function (position) { - var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), - Vec3.subtract(position, - selectionManager.localPosition)); - return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); - }; - } - for (var i = 0; i < entities.length; ++i) { - var properties = Entities.getEntityProperties(entities[i]); - if (!isValid(properties.position)) { - entities.splice(i, 1); - --i; - } - } - } - selectionManager.setSelections(entities, this); - } -} - -function sortSelectedEntities(selected) { - var sortedEntities = selected.slice(); - var begin = 0; - while (begin < sortedEntities.length) { - var elementRemoved = false; - var next = begin + 1; - while (next < sortedEntities.length) { - var beginID = sortedEntities[begin]; - var nextID = sortedEntities[next]; - - if (Entities.isChildOfParent(beginID, nextID)) { - sortedEntities[begin] = nextID; - sortedEntities[next] = beginID; - sortedEntities.splice(next, 1); - elementRemoved = true; - break; - } else if (Entities.isChildOfParent(nextID, beginID)) { - sortedEntities.splice(next, 1); - elementRemoved = true; - break; - } - next++; - } - if (!elementRemoved) { - begin++; - } - } - return sortedEntities; -} - -function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { - var wantDebug = false; - var entitiesLength = entities.length; - var initialPropertySets = Entities.getMultipleEntityProperties(entities); - var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); - for (var i = 0; i < entitiesLength; ++i) { - var entityID = entities[i]; - - if (entityHostTypes[i].entityHostType !== entityHostType) { - if (wantDebug) { - console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + - entityHostTypes[i].entityHostType + ", expected: " + entityHostType); - } - continue; - } - - var children = Entities.getChildrenIDs(entityID); - var grandchildrenList = []; - recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); - childrenList.push({ - entityID: entityID, - properties: initialPropertySets[i], - children: grandchildrenList - }); - deletedIDs.push(entityID); - Entities.deleteEntity(entityID); - } -} - -function unparentSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var selectedEntities = selectionManager.selections; - var parentCheck = false; - - if (selectedEntities.length < 1) { - audioFeedback.rejection(); - Window.notifyEditError("You must have an entity selected in order to unparent it."); + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + mouseCapturedByTool = true; return; } - selectedEntities.forEach(function (id, index) { - var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; - if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { - parentCheck = true; + if (isActive) { + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. + return; } - Entities.editEntity(id, {parentID: null}); + } + } + + var mouseCapturedByTool = false; + var lastMousePosition = null; + var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms + var CLICK_MOVE_DISTANCE_THRESHOLD = 20; + var IDLE_MOUSE_TIMEOUT = 200; + + var lastMouseMoveEvent = null; + + function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; + } + + function mouseMove(event) { + if (mouseDown && !mouseHasMovedSincePress) { + var timeSincePressMicro = Date.now() - mousePressStartTime; + + var dX = mousePressStartPosition.x - event.x; + var dY = mousePressStartPosition.y - event.y; + var sqDist = (dX * dX) + (dY * dY); + + // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved + // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move + // yet. The goal is to provide mouse clicks that are more lenient to small movements. + if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + return; + } + mouseHasMovedSincePress = true; + } + + if (!isActive) { + return; + } + + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; + } + + lastMousePosition = { + x: event.x, + y: event.y + }; + } + + function mouseReleaseEvent(event) { + print("mouseReleaseEvent"); + mouseDown = false; + + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + if (propertyMenu.mouseReleaseEvent(event)) { + print("propertyMenu.mouseReleaseEvent(event)"); return true; - }); - if (parentCheck) { - audioFeedback.confirmation(); - if (selectedEntities.length > 1) { - Window.notify("Entities unparented"); - } else { - Window.notify("Entity unparented"); - } - //Refresh - entityListTool.sendUpdate(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - if (selectedEntities.length > 1) { - Window.notify("Selected Entities have no parents"); - } else { - Window.notify("Selected Entity does not have a parent"); - } } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); - } -} -function parentSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var selectedEntities = selectionManager.selections; - if (selectedEntities.length <= 1) { - audioFeedback.rejection(); - Window.notifyEditError("You must have multiple entities selected in order to parent them"); + if (isActive && selectionManager.hasSelection()) { + tooltip.show(false); + } + if (mouseCapturedByTool) { + print("mouseCapturedByTool"); return; } - var parentCheck = false; - var lastEntityId = selectedEntities[selectedEntities.length - 1]; - selectedEntities.forEach(function (id, index) { - if (lastEntityId !== id) { - var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; - if (parentId !== lastEntityId) { - parentCheck = true; - } - Entities.editEntity(id, {parentID: lastEntityId}); + + cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + print("!mouseHasMovedSincePress: " + JSON.stringify(event)); + mouseClickEvent(event); + } else { + print("mouseHasMovedSincePress"); + } + } + + function wasTabletOrEditHandleClicked(event) { + var rayPick = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; } + } + return false; + } + + function mouseClickEvent(event) { + print("mouseClickEvent isActive: " + isActive); + var wantDebug = false; + var result, properties, tabletClicked; + if (isActive && event.isLeftButton) { + print("mouseClickEvent isActive && event.isLeftButton"); + result = findClickedEntity(event); + var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { + return; + } + + if (result === null || result === undefined) { + if (!event.isShifted) { + if (!keepSelectedOnNextClick) { + selectionManager.clearSelections(this); + } + keepSelectedOnNextClick = false; + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + if (HMD.tabletID && foundEntity === HMD.tabletID) { + return; + } + properties = Entities.getEntityProperties(foundEntity); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK && selectionManager.editEnabled) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = createApp.rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity], this); + } else { + selectionManager.addEntity(foundEntity, true, this); + } + selectionManager.saveProperties(); + + if (wantDebug) { + print("Model selected: " + foundEntity); + } + selectionDisplay.select(selectedEntityID, event); + + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } + } else if (event.isRightButton) { + result = findClickedEntity(event); + if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } + properties = Entities.getEntityProperties(result.entityID); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); + } else { + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); + } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); + } + } + } + + Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + + + // In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already + // exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that + // added it. + var originalLightsArePickable = Entities.getLightsArePickable(); + + function setupModelMenus() { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Undo", + shortcutKey: 'Ctrl+Z', + position: 0, + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Redo", + shortcutKey: 'Ctrl+Y', + position: 1, }); - if (parentCheck) { - audioFeedback.confirmation(); - Window.notify("Entities parented"); - //Refresh - entityListTool.sendUpdate(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - Window.notify("Entities are already parented to last"); - } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); - } -} -function deleteSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var deletedIDs = []; + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_CREATE_SEPARATOR, + isSeparator: true + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_IMPORT_FROM_FILE, + afterItem: MENU_CREATE_SEPARATOR + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_IMPORT_FROM_URL, + afterItem: MENU_IMPORT_FROM_FILE + }); - SelectionManager.saveProperties(); - var savedProperties = []; - var newSortedSelection = sortSelectedEntities(selectionManager.selections); - var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); - for (var i = 0; i < newSortedSelection.length; ++i) { - var entityID = newSortedSelection[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - if (initialProperties.locked || - (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); + + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + position: 0, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + afterItem: MENU_ALLOW_SELECTION_LIGHTS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS, + afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE + }); + + Entities.setLightsArePickable(false); + } + + setupModelMenus(); // do this when first running our script. + + function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS); + Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); + Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_URL); + Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_FILE); + Menu.removeSeparator("Edit", MENU_CREATE_SEPARATOR); + } + + Script.scriptEnding.connect(function () { + toolBar.setActive(false); + Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + + + progressDialog.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); + Entities.setLightsArePickable(originalLightsArePickable); + + Overlays.deleteOverlay(importingSVOImageOverlay); + Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); + + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleMessagesReceived); + Messages.unsubscribe("entityToolUpdates"); + createButton = null; + }); + + var lastOrientation = Camera.orientation; + var lastPosition = Camera.position; + + // Do some stuff regularly, like check for placement of various overlays + Script.update.connect(function (deltaTime) { + progressDialog.move(); + selectionDisplay.checkControllerMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + }); + + function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && + (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && + (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); + } + + function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); + + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { + isValid = function (position) { + return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); + }; + } else { + isValid = function (position) { + var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), + Vec3.subtract(position, + selectionManager.localPosition)); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); + }; + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities, this); + } + } + + function sortSelectedEntities(selected) { + var sortedEntities = selected.slice(); + var begin = 0; + while (begin < sortedEntities.length) { + var elementRemoved = false; + var next = begin + 1; + while (next < sortedEntities.length) { + var beginID = sortedEntities[begin]; + var nextID = sortedEntities[next]; + + if (Entities.isChildOfParent(beginID, nextID)) { + sortedEntities[begin] = nextID; + sortedEntities[next] = beginID; + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } else if (Entities.isChildOfParent(nextID, beginID)) { + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } + next++; + } + if (!elementRemoved) { + begin++; + } + } + return sortedEntities; + } + + function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { + var wantDebug = false; + var entitiesLength = entities.length; + var initialPropertySets = Entities.getMultipleEntityProperties(entities); + var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); + for (var i = 0; i < entitiesLength; ++i) { + var entityID = entities[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } continue; } + var children = Entities.getChildrenIDs(entityID); - var childList = []; - recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); - savedProperties.push({ + var grandchildrenList = []; + recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); + childrenList.push({ entityID: entityID, - properties: initialProperties, - children: childList + properties: initialPropertySets[i], + children: grandchildrenList }); deletedIDs.push(entityID); Entities.deleteEntity(entityID); } - - if (savedProperties.length > 0) { - SelectionManager.clearSelections(this); - pushCommandForSelections([], savedProperties); - entityListTool.deleteEntities(deletedIDs); - } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); - } -} - -function toggleSelectedEntitiesLocked() { - if (SelectionManager.hasSelection()) { - var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - Entities.editEntity(entityID, { - locked: locked - }); - } - entityListTool.sendUpdate(); - selectionManager._update(false, this); - } -} - -function toggleSelectedEntitiesVisible() { - if (SelectionManager.hasSelection()) { - var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - Entities.editEntity(entityID, { - visible: visible - }); - } - entityListTool.sendUpdate(); - selectionManager._update(false, this); - } -} - -function onFileSaveChanged(filename) { - Window.saveFileChanged.disconnect(onFileSaveChanged); - if (filename !== "") { - var success = Clipboard.exportEntities(filename, selectionManager.selections); - if (!success) { - Window.notifyEditError("Export failed."); - } - } -} - -function onFileOpenChanged(filename) { - // disconnect the event, otherwise the requests will stack up - try { - // Not all calls to onFileOpenChanged() connect an event. - Window.browseChanged.disconnect(onFileOpenChanged); - } catch (e) { - // Ignore. } - var importURL = null; - if (filename !== "") { - importURL = filename; - if (!/^(http|https):\/\//.test(filename)) { - importURL = "file:///" + importURL; - } - } - if (importURL) { - if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { - toolBar.toggle(); - } - importSVO(importURL); - } -} + function unparentSelectedEntities() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; -function onPromptTextChanged(prompt) { - Window.promptTextChanged.disconnect(onPromptTextChanged); - if (prompt !== "") { - if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { - toolBar.toggle(); - } - importSVO(prompt); - } -} - -function onPromptTextChangedDefaultRadiusUserPref(prompt) { - Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref); - if (prompt !== "") { - var radius = parseInt(prompt); - if (radius < 0 || isNaN(radius)){ - radius = 100; - } - Settings.setValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius); - } -} - -function handleMenuEvent(menuItem) { - if (menuItem === MENU_ALLOW_SELECTION_SMALL) { - allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL); - } else if (menuItem === MENU_ALLOW_SELECTION_LARGE) { - allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE); - } else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) { - Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); - } else if (menuItem === "Delete") { - deleteSelectedEntities(); - } else if (menuItem === "Undo") { - undoHistory.undo(); - } else if (menuItem === "Redo") { - undoHistory.redo(); - } else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) { - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { - Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); - } else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) { - Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref); - Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100)); - } else if (menuItem === MENU_IMPORT_FROM_FILE) { - importEntitiesFromFile(); - } else if (menuItem === MENU_IMPORT_FROM_URL) { - importEntitiesFromUrl(); - } - tooltip.show(false); -} - -var HALF_TREE_SCALE = 16384; - -function getPositionToCreateEntity(extra) { - var CREATE_DISTANCE = 2; - var position; - var delta = extra !== undefined ? extra : 0; - if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); - } else { - position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - } - - if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { - return null; - } - return position; -} - -function importSVO(importURL) { - if (!Entities.canRez() && !Entities.canRezTmp() && - !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { - Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); - return; - } - - Overlays.editOverlay(importingSVOTextOverlay, { - visible: true - }); - Overlays.editOverlay(importingSVOImageOverlay, { - visible: true - }); - - var success = Clipboard.importEntities(importURL); - - if (success) { - var VERY_LARGE = 10000; - var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; - var position = Vec3.ZERO; - if (!isLargeImport) { - position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); - } - if (position !== null && position !== undefined) { - var pastedEntityIDs = Clipboard.pasteEntities(position); - if (!isLargeImport) { - // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move - // entities after they're imported so that they're all the correct distance in front of and with geometric mean - // centered on the avatar/camera direction. - var deltaPosition = Vec3.ZERO; - var entityPositions = []; - var entityParentIDs = []; - - var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; - var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; - if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { - var targetDirection; - if (Camera.mode === "entity" || Camera.mode === "independent") { - targetDirection = Camera.orientation; - } else { - targetDirection = MyAvatar.orientation; - } - targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); - - var targetPosition = getPositionToCreateEntity(); - var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. - var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. - for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", - "registrationPoint", "rotation", "parentID"]); - var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, - curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); - var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); - var distance = Vec3.dot(delta, targetDirection); - deltaParallel = Math.min(distance, deltaParallel); - deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), - deltaPerpendicular); - entityPositions[i] = curLoopEntityProps.position; - entityParentIDs[i] = curLoopEntityProps.parentID; - } - deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); - deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + if (selectedEntities.length < 1) { + audioFeedback.rejection(); + Window.notifyEditError("You must have an entity selected in order to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { + parentCheck = true; } - - if (grid.getSnapToGrid()) { - var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", - "registrationPoint"]); - var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); - position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, - firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); - deltaPosition = Vec3.subtract(position, firstEntityProps.position); + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + audioFeedback.confirmation(); + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); } - - if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { - if (Uuid.isNull(entityParentIDs[editEntityIndex])) { - Entities.editEntity(pastedEntityIDs[editEntityIndex], { - position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) - }); - } - } + //Refresh + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); } } - - if (isActive) { - selectionManager.setSelections(pastedEntityIDs, this); + } else { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected or the selection has locked entities."); + } + } + function parentSelectedEntities() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + audioFeedback.rejection(); + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; } - } else { - Window.notifyEditError("Can't import entities: entities would be out of bounds."); - } - } else { - Window.notifyEditError("There was an error importing the entity file."); - } - - Overlays.editOverlay(importingSVOTextOverlay, { - visible: false - }); - Overlays.editOverlay(importingSVOImageOverlay, { - visible: false - }); -} -Window.svoImportRequested.connect(importSVO); - -Menu.menuItemEvent.connect(handleMenuEvent); - -var keyPressEvent = function (event) { - if (isActive) { - cameraManager.keyPressEvent(event); - } -}; -var keyReleaseEvent = function (event) { - if (isActive) { - cameraManager.keyReleaseEvent(event); - } -}; -Controller.keyReleaseEvent.connect(keyReleaseEvent); -Controller.keyPressEvent.connect(keyPressEvent); - -function deleteKey(value) { - if (value === 0) { // on release - deleteSelectedEntities(); - } -} -function deselectKey(value) { - if (value === 0) { // on release - selectionManager.clearSelections(this); - } -} -function toggleKey(value) { - if (value === 0) { // on release - selectionDisplay.toggleSpaceMode(); - } -} -function focusKey(value) { - if (value === 0) { // on release - setCameraFocusToSelection(); - } -} -function gridKey(value) { - if (value === 0) { // on release - alignGridToSelection(); - } -} -function viewGridKey(value) { - if (value === 0) { // on release - toggleGridVisibility(); - } -} -function snapKey(value) { - if (value === 0) { // on release - entityListTool.toggleSnapToGrid(); - } -} -function gridToAvatarKey(value) { - if (value === 0) { // on release - alignGridToAvatar(); - } -} -function rotateAsNextClickedSurfaceKey(value) { - if (value === 0) { // on release - rotateAsNextClickedSurface(); - } -} -function quickRotate90xKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("X"); - } -} -function quickRotate90yKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("Y"); - } -} -function quickRotate90zKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("Z"); - } -} -function recursiveAdd(newParentID, parentData) { - if (parentData.children !== undefined) { - var children = parentData.children; - for (var i = 0; i < children.length; i++) { - var childProperties = children[i].properties; - childProperties.parentID = newParentID; - var newChildID = Entities.addEntity(childProperties); - recursiveAdd(newChildID, children[i]); - } - } -} - -var UndoHistory = function(onUpdate) { - this.history = []; - // The current position is the index of the last executed action in the history array. - // - // -1 0 1 2 3 <- position - // A B C D <- actions in history - // - // If our lastExecutedIndex is 1, the last executed action is B. - // If we undo, we undo B (index 1). If we redo, we redo C (index 2). - this.lastExecutedIndex = -1; - this.enabled = true; - this.onUpdate = onUpdate; -}; - -UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { - if (!this.enabled) { - return; - } - // Delete any history following the last executed action. - this.history.splice(this.lastExecutedIndex + 1); - this.history.push({ - undoFn: undoFn, - undoArgs: undoArgs, - redoFn: redoFn, - redoArgs: redoArgs - }); - this.lastExecutedIndex++; - - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.setEnabled = function(enabled) { - this.enabled = enabled; - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.canUndo = function() { - return this.enabled && this.lastExecutedIndex >= 0; -}; -UndoHistory.prototype.canRedo = function() { - return this.enabled && this.lastExecutedIndex < this.history.length - 1; -}; -UndoHistory.prototype.undo = function() { - if (!this.canUndo()) { - console.warn("Cannot undo action"); - return; - } - - var command = this.history[this.lastExecutedIndex]; - command.undoFn(command.undoArgs); - this.lastExecutedIndex--; - - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.redo = function() { - if (!this.canRedo()) { - console.warn("Cannot redo action"); - return; - } - - var command = this.history[this.lastExecutedIndex + 1]; - command.redoFn(command.redoArgs); - this.lastExecutedIndex++; - - if (this.onUpdate) { - this.onUpdate(); - } -}; - -function updateUndoRedoMenuItems() { - Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); - Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); -} -var undoHistory = new UndoHistory(updateUndoRedoMenuItems); -updateUndoRedoMenuItems(); - -// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently -// possible to create an entity with a specific id, earlier undo commands to the deleted entity -// will fail if there isn't a way to find the new entity id. -var DELETED_ENTITY_MAP = {}; - -function applyEntityProperties(data) { - var editEntities = data.editEntities; - var createEntities = data.createEntities; - var deleteEntities = data.deleteEntities; - var selectedEntityIDs = []; - var selectEdits = createEntities.length === 0 || !data.selectCreated; - var i, entityID, entityProperties; - for (i = 0; i < createEntities.length; i++) { - entityID = createEntities[i].entityID; - entityProperties = createEntities[i].properties; - var newEntityID = Entities.addEntity(entityProperties); - recursiveAdd(newEntityID, createEntities[i]); - DELETED_ENTITY_MAP[entityID] = newEntityID; - if (data.selectCreated) { - selectedEntityIDs.push(newEntityID); - } - } - for (i = 0; i < deleteEntities.length; i++) { - entityID = deleteEntities[i].entityID; - if (DELETED_ENTITY_MAP[entityID] !== undefined) { - entityID = DELETED_ENTITY_MAP[entityID]; - } - Entities.deleteEntity(entityID); - var index = selectedEntityIDs.indexOf(entityID); - if (index >= 0) { - selectedEntityIDs.splice(index, 1); - } - } - for (i = 0; i < editEntities.length; i++) { - entityID = editEntities[i].entityID; - if (DELETED_ENTITY_MAP[entityID] !== undefined) { - entityID = DELETED_ENTITY_MAP[entityID]; - } - entityProperties = editEntities[i].properties; - if (entityProperties !== null) { - Entities.editEntity(entityID, entityProperties); - } - if (selectEdits) { - selectedEntityIDs.push(entityID); - } - } - - // We might be getting an undo while edit.js is disabled. If that is the case, don't set - // our selections, causing the edit widgets to display. - if (isActive) { - selectionManager.setSelections(selectedEntityIDs, this); - selectionManager.saveProperties(); - } -} - -// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the -// redo command, and the saved properties for the undo command. Also, include create and delete entity data. -function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { - doNotSaveEditProperties = false; - var undoData = { - editEntities: [], - createEntities: deletedEntityData || [], - deleteEntities: createdEntityData || [], - selectCreated: true - }; - var redoData = { - editEntities: [], - createEntities: createdEntityData || [], - deleteEntities: deletedEntityData || [], - selectCreated: true - }; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var currentProperties = null; - if (!initialProperties) { - continue; - } - - if (doNotSaveEditProperties) { - initialProperties = null; - } else { - currentProperties = Entities.getEntityProperties(entityID); - } - - undoData.editEntities.push({ - entityID: entityID, - properties: initialProperties - }); - redoData.editEntities.push({ - entityID: entityID, - properties: currentProperties - }); - } - undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); -} - -var ServerScriptStatusMonitor = function(entityID, statusCallback) { - var self = this; - - self.entityID = entityID; - self.active = true; - self.sendRequestTimerID = null; - - var onStatusReceived = function(success, isRunning, status, errorInfo) { - if (self.active) { - statusCallback({ - statusRetrieved: success, - isRunning: isRunning, - status: status, - errorInfo: errorInfo - }); - self.sendRequestTimerID = Script.setTimeout(function() { - if (self.active) { - Entities.getServerScriptStatus(entityID, onStatusReceived); + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}); } - }, 1000); - } - }; - self.stop = function() { - self.active = false; - }; + }); - Entities.getServerScriptStatus(entityID, onStatusReceived); -}; - -var PropertiesTool = function (opts) { - var that = {}; - - var webView = null; - webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - webView.setVisible = function(value) {}; - - var visible = false; - - // This keeps track of the last entity ID that was selected. If multiple entities - // are selected or if no entity is selected this will be `null`. - var currentSelectedEntityID = null; - var statusMonitor = null; - var blockPropertyUpdates = false; - - that.setVisible = function (newVisible) { - visible = newVisible; - webView.setVisible(shouldUseEditTabletApp() && visible); - createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); - }; - - that.setVisible(false); - - function emitScriptEvent(data) { - var dataString = JSON.stringify(data); - webView.emitScriptEvent(dataString); - createToolsWindow.emitScriptEvent(dataString); - } - - function updateScriptStatus(info) { - info.type = "server_script_status"; - emitScriptEvent(info); - } - - function resetScriptStatus() { - updateScriptStatus({ - statusRetrieved: undefined, - isRunning: undefined, - status: "", - errorInfo: "" - }); - } - - that.setSpaceMode = function(spaceMode) { - emitScriptEvent({ - type: 'setSpaceMode', - spaceMode: spaceMode - }) - }; - - function updateSelections(selectionUpdated, caller) { - if (HMD.active && visible) { - webView.setLandscape(true); + if (parentCheck) { + audioFeedback.confirmation(); + Window.notify("Entities parented"); + //Refresh + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); + Window.notify("Entities are already parented to last"); + } } else { - if (!visible) { - hmdMultiSelectMode = false; - webView.setLandscape(false); + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected or the selection has locked entities."); + } + } + function deleteSelectedEntities() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var deletedIDs = []; + + SelectionManager.saveProperties(); + var savedProperties = []; + var newSortedSelection = sortSelectedEntities(selectionManager.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); + for (var i = 0; i < newSortedSelection.length; ++i) { + var entityID = newSortedSelection[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + if (initialProperties.locked || + (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + continue; + } + var children = Entities.getChildrenIDs(entityID); + var childList = []; + recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); + savedProperties.push({ + entityID: entityID, + properties: initialProperties, + children: childList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } + + if (savedProperties.length > 0) { + SelectionManager.clearSelections(this); + createApp.pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } + } else { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected or the selection has locked entities."); + } + } + + function toggleSelectedEntitiesLocked() { + if (SelectionManager.hasSelection()) { + var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + locked: locked + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } + } + + function toggleSelectedEntitiesVisible() { + if (SelectionManager.hasSelection()) { + var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + visible: visible + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } + } + + function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); } } - - if (blockPropertyUpdates) { + } + + function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { + // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + // Ignore. + } + + var importURL = null; + if (filename !== "") { + importURL = filename; + if (!/^(http|https):\/\//.test(filename)) { + importURL = "file:///" + importURL; + } + } + if (importURL) { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(importURL); + } + } + + function onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } + } + + function onPromptTextChangedDefaultRadiusUserPref(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref); + if (prompt !== "") { + var radius = parseInt(prompt); + if (radius < 0 || isNaN(radius)){ + radius = 100; + } + Settings.setValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius); + } + } + + function handleMenuEvent(menuItem) { + if (menuItem === MENU_ALLOW_SELECTION_SMALL) { + allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL); + } else if (menuItem === MENU_ALLOW_SELECTION_LARGE) { + allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE); + } else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) { + Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + } else if (menuItem === "Delete") { + deleteSelectedEntities(); + } else if (menuItem === "Undo") { + undoHistory.undo(); + } else if (menuItem === "Redo") { + undoHistory.redo(); + } else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) { + Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref); + Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100)); + } else if (menuItem === MENU_IMPORT_FROM_FILE) { + importEntitiesFromFile(); + } else if (menuItem === MENU_IMPORT_FROM_URL) { + importEntitiesFromUrl(); + } + tooltip.show(false); + } + + var HALF_TREE_SCALE = 16384; + + function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; + } + + function importSVO(importURL) { + if (!Entities.canRez() && !Entities.canRezTmp() && + !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); return; } - var data = { - type: 'update', - spaceMode: selectionDisplay.getSpaceMode(), - isPropertiesToolUpdate: caller === this, + Overlays.editOverlay(importingSVOTextOverlay, { + visible: true + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: true + }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isActive) { + selectionManager.setSelections(pastedEntityIDs, this); + } + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); + } + } else { + Window.notifyEditError("There was an error importing the entity file."); + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: false + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: false + }); + } + Window.svoImportRequested.connect(importSVO); + + Menu.menuItemEvent.connect(handleMenuEvent); + + var keyPressEvent = function (event) { + if (isActive) { + cameraManager.keyPressEvent(event); + } + }; + var keyReleaseEvent = function (event) { + if (isActive) { + cameraManager.keyReleaseEvent(event); + } + }; + Controller.keyReleaseEvent.connect(keyReleaseEvent); + Controller.keyPressEvent.connect(keyPressEvent); + + function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } + } + function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(this); + } + } + function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } + } + function focusKey(value) { + if (value === 0) { // on release + setCameraFocusToSelection(); + } + } + function gridKey(value) { + if (value === 0) { // on release + alignGridToSelection(); + } + } + function viewGridKey(value) { + if (value === 0) { // on release + toggleGridVisibility(); + } + } + function snapKey(value) { + if (value === 0) { // on release + entityListTool.toggleSnapToGrid(); + } + } + function gridToAvatarKey(value) { + if (value === 0) { // on release + alignGridToAvatar(); + } + } + function rotateAsNextClickedSurfaceKey(value) { + if (value === 0) { // on release + rotateAsNextClickedSurface(); + } + } + function quickRotate90xKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("X"); + } + } + function quickRotate90yKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Y"); + } + } + function quickRotate90zKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Z"); + } + } + function recursiveAdd(newParentID, parentData) { + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } + } + } + + var UndoHistory = function(onUpdate) { + this.history = []; + // The current position is the index of the last executed action in the history array. + // + // -1 0 1 2 3 <- position + // A B C D <- actions in history + // + // If our lastExecutedIndex is 1, the last executed action is B. + // If we undo, we undo B (index 1). If we redo, we redo C (index 2). + this.lastExecutedIndex = -1; + this.enabled = true; + this.onUpdate = onUpdate; + }; + + UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { + if (!this.enabled) { + return; + } + // Delete any history following the last executed action. + this.history.splice(this.lastExecutedIndex + 1); + this.history.push({ + undoFn: undoFn, + undoArgs: undoArgs, + redoFn: redoFn, + redoArgs: redoArgs + }); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } + }; + UndoHistory.prototype.setEnabled = function(enabled) { + this.enabled = enabled; + if (this.onUpdate) { + this.onUpdate(); + } + }; + UndoHistory.prototype.canUndo = function() { + return this.enabled && this.lastExecutedIndex >= 0; + }; + UndoHistory.prototype.canRedo = function() { + return this.enabled && this.lastExecutedIndex < this.history.length - 1; + }; + UndoHistory.prototype.undo = function() { + if (!this.canUndo()) { + console.warn("Cannot undo action"); + return; + } + + var command = this.history[this.lastExecutedIndex]; + command.undoFn(command.undoArgs); + this.lastExecutedIndex--; + + if (this.onUpdate) { + this.onUpdate(); + } + }; + UndoHistory.prototype.redo = function() { + if (!this.canRedo()) { + console.warn("Cannot redo action"); + return; + } + + var command = this.history[this.lastExecutedIndex + 1]; + command.redoFn(command.redoArgs); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } + }; + + function updateUndoRedoMenuItems() { + Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); + Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); + } + var undoHistory = new UndoHistory(updateUndoRedoMenuItems); + updateUndoRedoMenuItems(); + + // When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently + // possible to create an entity with a specific id, earlier undo commands to the deleted entity + // will fail if there isn't a way to find the new entity id. + var DELETED_ENTITY_MAP = {}; + + function applyEntityProperties(data) { + var editEntities = data.editEntities; + var createEntities = data.createEntities; + var deleteEntities = data.deleteEntities; + var selectedEntityIDs = []; + var selectEdits = createEntities.length === 0 || !data.selectCreated; + var i, entityID, entityProperties; + for (i = 0; i < createEntities.length; i++) { + entityID = createEntities[i].entityID; + entityProperties = createEntities[i].properties; + var newEntityID = Entities.addEntity(entityProperties); + recursiveAdd(newEntityID, createEntities[i]); + DELETED_ENTITY_MAP[entityID] = newEntityID; + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (i = 0; i < deleteEntities.length; i++) { + entityID = deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + Entities.deleteEntity(entityID); + var index = selectedEntityIDs.indexOf(entityID); + if (index >= 0) { + selectedEntityIDs.splice(index, 1); + } + } + for (i = 0; i < editEntities.length; i++) { + entityID = editEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } + } + + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs, this); + selectionManager.saveProperties(); + } + } + + // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the + // redo command, and the saved properties for the undo command. Also, include create and delete entity data. + createApp.pushCommandForSelections = function (createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; + var undoData = { + editEntities: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true + }; + var redoData = { + editEntities: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: true + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + var currentProperties = null; + if (!initialProperties) { + continue; + } + + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ + entityID: entityID, + properties: initialProperties + }); + redoData.editEntities.push({ + entityID: entityID, + properties: currentProperties + }); + } + undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); + } + + var ServerScriptStatusMonitor = function(entityID, statusCallback) { + var self = this; + + self.entityID = entityID; + self.active = true; + self.sendRequestTimerID = null; + + var onStatusReceived = function(success, isRunning, status, errorInfo) { + if (self.active) { + statusCallback({ + statusRetrieved: success, + isRunning: isRunning, + status: status, + errorInfo: errorInfo + }); + self.sendRequestTimerID = Script.setTimeout(function() { + if (self.active) { + Entities.getServerScriptStatus(entityID, onStatusReceived); + } + }, 1000); + } + }; + self.stop = function() { + self.active = false; }; - if (selectionUpdated) { - resetScriptStatus(); + Entities.getServerScriptStatus(entityID, onStatusReceived); + }; - if (selectionManager.selections.length !== 1) { - if (statusMonitor !== null) { - statusMonitor.stop(); - statusMonitor = null; - } - currentSelectedEntityID = null; - } else if (currentSelectedEntityID !== selectionManager.selections[0]) { - if (statusMonitor !== null) { - statusMonitor.stop(); - } - var entityID = selectionManager.selections[0]; - currentSelectedEntityID = entityID; - statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); - } + var PropertiesTool = function (opts) { + var that = {}; + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value) {}; + + var visible = false; + + // This keeps track of the last entity ID that was selected. If multiple entities + // are selected or if no entity is selected this will be `null`. + var currentSelectedEntityID = null; + var statusMonitor = null; + var blockPropertyUpdates = false; + + that.setVisible = function (newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); } - var selections = []; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entity = {}; - entity.id = selectionManager.selections[i]; - entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); - if (entity.properties.rotation !== undefined) { - entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); - } - if (entity.properties.localRotation !== undefined) { - entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); - } - if (entity.properties.emitOrientation !== undefined) { - entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); - } - if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); - entity.properties.keyLight.direction.z = 0.0; - } - selections.push(entity); + function updateScriptStatus(info) { + info.type = "server_script_status"; + emitScriptEvent(info); } - data.selections = selections; - emitScriptEvent(data); - } - selectionManager.addEventListener(updateSelections, this); - - - var onWebEventReceived = function(data) { - try { - data = JSON.parse(data); - } catch(e) { - return; - } - var i, properties, dY, diff, newPosition; - if (data.type === "update") { - - if (data.properties || data.propertiesMap) { - var propertiesMap = data.propertiesMap; - if (propertiesMap === undefined) { - propertiesMap = [{ - entityIDs: data.ids, - properties: data.properties, - }]; - } - - var sendListUpdate = false; - propertiesMap.forEach(function(propertiesObject) { - var properties = propertiesObject.properties; - var updateEntityIDs = propertiesObject.entityIDs; - if (properties.dynamic === false) { - // this object is leaving dynamic, so we zero its velocities - properties.localVelocity = Vec3.ZERO; - properties.localAngularVelocity = Vec3.ZERO; - } - if (properties.rotation !== undefined) { - properties.rotation = Quat.fromVec3Degrees(properties.rotation); - } - if (properties.localRotation !== undefined) { - properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); - } - if (properties.emitOrientation !== undefined) { - properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); - } - if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { - var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); - if (properties.keyLight.direction.x === undefined) { - properties.keyLight.direction.x = currentKeyLightDirection.x; - } - if (properties.keyLight.direction.y === undefined) { - properties.keyLight.direction.y = currentKeyLightDirection.y; - } - properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); - } - - updateEntityIDs.forEach(function (entityID) { - Entities.editEntity(entityID, properties); - }); - - if (properties.name !== undefined || properties.modelURL !== undefined || properties.imageURL !== undefined || - properties.materialURL !== undefined || properties.visible !== undefined || properties.locked !== undefined) { - - sendListUpdate = true; - } - - }); - if (sendListUpdate) { - entityListTool.sendUpdate(); - } - } - - if (data.onlyUpdateEntities) { - blockPropertyUpdates = true; - } else { - pushCommandForSelections(); - SelectionManager.saveProperties(); - } - selectionManager._update(false, this); - blockPropertyUpdates = false; - - if (data.snapToGrid !== undefined) { - entityListTool.setListMenuSnapToGrid(data.snapToGrid); - } - } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { - data.ids.forEach(function(entityID) { - Entities.editEntity(entityID, data.properties); + function resetScriptStatus() { + updateScriptStatus({ + statusRetrieved: undefined, + isRunning: undefined, + status: "", + errorInfo: "" }); - } else if (data.type === "showMarketplace") { - showMarketplace(); - } else if (data.type === "action") { - if (data.action === "moveSelectionToGrid") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); - diff = { - x: 0, - y: dY, - z: 0 - }; - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - newPosition = Vec3.sum(properties.position, diff); - Entities.editEntity(selectionManager.selections[i], { - position: newPosition - }); - } - pushCommandForSelections(); - selectionManager._update(false, this); + } + + that.setSpaceMode = function(spaceMode) { + emitScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }) + }; + + function updateSelections(selectionUpdated, caller) { + if (HMD.active && visible) { + webView.setLandscape(true); + } else { + if (!visible) { + hmdMultiSelectMode = false; + webView.setLandscape(false); } - } else if (data.action === "moveAllToGrid") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; - dY = grid.getOrigin().y - bottomY; + } + + if (blockPropertyUpdates) { + return; + } + + var data = { + type: 'update', + spaceMode: selectionDisplay.getSpaceMode(), + isPropertiesToolUpdate: caller === this, + }; + + if (selectionUpdated) { + resetScriptStatus(); + + if (selectionManager.selections.length !== 1) { + if (statusMonitor !== null) { + statusMonitor.stop(); + statusMonitor = null; + } + currentSelectedEntityID = null; + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { + if (statusMonitor !== null) { + statusMonitor.stop(); + } + var entityID = selectionManager.selections[0]; + currentSelectedEntityID = entityID; + statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); + } + } + + var selections = []; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entity = {}; + entity.id = selectionManager.selections[i]; + entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); + if (entity.properties.rotation !== undefined) { + entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); + } + if (entity.properties.localRotation !== undefined) { + entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); + } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } + if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); + entity.properties.keyLight.direction.z = 0.0; + } + selections.push(entity); + } + data.selections = selections; + + emitScriptEvent(data); + } + selectionManager.addEventListener(updateSelections, this); + + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + return; + } + var i, properties, dY, diff, newPosition; + if (data.type === "update") { + + if (data.properties || data.propertiesMap) { + var propertiesMap = data.propertiesMap; + if (propertiesMap === undefined) { + propertiesMap = [{ + entityIDs: data.ids, + properties: data.properties, + }]; + } + + var sendListUpdate = false; + propertiesMap.forEach(function(propertiesObject) { + var properties = propertiesObject.properties; + var updateEntityIDs = propertiesObject.entityIDs; + if (properties.dynamic === false) { + // this object is leaving dynamic, so we zero its velocities + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + } + if (properties.rotation !== undefined) { + properties.rotation = Quat.fromVec3Degrees(properties.rotation); + } + if (properties.localRotation !== undefined) { + properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); + } + if (properties.emitOrientation !== undefined) { + properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + } + if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (properties.keyLight.direction.x === undefined) { + properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (properties.keyLight.direction.y === undefined) { + properties.keyLight.direction.y = currentKeyLightDirection.y; + } + properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); + } + + updateEntityIDs.forEach(function (entityID) { + Entities.editEntity(entityID, properties); + }); + + if (properties.name !== undefined || properties.modelURL !== undefined || properties.imageURL !== undefined || + properties.materialURL !== undefined || properties.visible !== undefined || properties.locked !== undefined) { + + sendListUpdate = true; + } + + }); + if (sendListUpdate) { + entityListTool.sendUpdate(); + } + } + + if (data.onlyUpdateEntities) { + blockPropertyUpdates = true; + } else { + createApp.pushCommandForSelections(); + SelectionManager.saveProperties(); + } + selectionManager._update(false, this); + blockPropertyUpdates = false; + + if (data.snapToGrid !== undefined) { + entityListTool.setListMenuSnapToGrid(data.snapToGrid); + } + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { + data.ids.forEach(function(entityID) { + Entities.editEntity(entityID, data.properties); + }); + } else if (data.type === "showMarketplace") { + showMarketplace(); + } else if (data.type === "action") { + if (data.action === "moveSelectionToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); diff = { x: 0, y: dY, z: 0 }; - newPosition = Vec3.sum(properties.position, diff); - Entities.editEntity(selectionManager.selections[i], { - position: newPosition - }); - } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "resetToNaturalDimensions") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - var naturalDimensions = properties.naturalDimensions; - - // If any of the natural dimensions are not 0, resize - if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && - naturalDimensions.z === 0) { - Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + - " is invalid or the model has not yet been loaded."); - } else { + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + newPosition = Vec3.sum(properties.position, diff); Entities.editEntity(selectionManager.selections[i], { - dimensions: properties.naturalDimensions + position: newPosition + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "moveAllToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; + dY = grid.getOrigin().y - bottomY; + diff = { + x: 0, + y: dY, + z: 0 + }; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "resetToNaturalDimensions") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var naturalDimensions = properties.naturalDimensions; + + // If any of the natural dimensions are not 0, resize + if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && + naturalDimensions.z === 0) { + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + + " is invalid or the model has not yet been loaded."); + } else { + Entities.editEntity(selectionManager.selections[i], { + dimensions: properties.naturalDimensions + }); + } + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "previewCamera") { + if (selectionManager.hasSelection()) { + Camera.mode = "entity"; + Camera.cameraEntity = selectionManager.selections[0]; + } + } else if (data.action === "rescaleDimensions") { + var multiplier = data.percentage / 100.0; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions) + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "reloadClientScripts") { + if (selectionManager.hasSelection()) { + var timestamp = Date.now(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + scriptTimestamp: timestamp }); } } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "previewCamera") { - if (selectionManager.hasSelection()) { - Camera.mode = "entity"; - Camera.cameraEntity = selectionManager.selections[0]; - } - } else if (data.action === "rescaleDimensions") { - var multiplier = data.percentage / 100.0; - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - Entities.editEntity(selectionManager.selections[i], { - dimensions: Vec3.multiply(multiplier, properties.dimensions) - }); + } else if (data.action === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "reloadClientScripts") { - if (selectionManager.hasSelection()) { - var timestamp = Date.now(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - scriptTimestamp: timestamp - }); + } else if (data.action === "copyPosition") { + if (selectionManager.selections.length === 1) { + selectionManager.saveProperties(); + properties = selectionManager.savedProperties[selectionManager.selections[0]]; + copiedPosition = properties.position; + Window.copyToClipboard(JSON.stringify(copiedPosition)); } - } - } else if (data.action === "reloadServerScripts") { - if (selectionManager.hasSelection()) { - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.reloadServerScripts(selectionManager.selections[i]); + } else if (data.action === "copyRotation") { + if (selectionManager.selections.length === 1) { + selectionManager.saveProperties(); + properties = selectionManager.savedProperties[selectionManager.selections[0]]; + copiedRotation = properties.rotation; + Window.copyToClipboard(JSON.stringify(copiedRotation)); } - } - } else if (data.action === "copyPosition") { - if (selectionManager.selections.length === 1) { - selectionManager.saveProperties(); - properties = selectionManager.savedProperties[selectionManager.selections[0]]; - copiedPosition = properties.position; - Window.copyToClipboard(JSON.stringify(copiedPosition)); - } - } else if (data.action === "copyRotation") { - if (selectionManager.selections.length === 1) { - selectionManager.saveProperties(); - properties = selectionManager.savedProperties[selectionManager.selections[0]]; - copiedRotation = properties.rotation; - Window.copyToClipboard(JSON.stringify(copiedRotation)); - } - } else if (data.action === "pastePosition") { - if (copiedPosition !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - position: copiedPosition - }); - } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - } - } else if (data.action === "pasteRotation") { - if (copiedRotation !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - rotation: copiedRotation - }); - } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - } - } else if (data.action === "setRotationToZero") { - if (selectionManager.selections.length === 1 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - var parentState = getParentState(selectionManager.selections[0]); - if ((parentState === "PARENT_CHILDREN" || parentState === "CHILDREN") && selectionDisplay.getSpaceMode() === "local" ) { - Entities.editEntity(selectionManager.selections[0], { - localRotation: Quat.IDENTITY - }); + } else if (data.action === "pastePosition") { + if (copiedPosition !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + position: copiedPosition + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } else { - Entities.editEntity(selectionManager.selections[0], { - rotation: Quat.IDENTITY - }); + audioFeedback.rejection(); + } + } else if (data.action === "pasteRotation") { + if (copiedRotation !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + rotation: copiedRotation + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); + } + } else if (data.action === "setRotationToZero") { + if (selectionManager.selections.length === 1 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + var parentState = getParentState(selectionManager.selections[0]); + if ((parentState === "PARENT_CHILDREN" || parentState === "CHILDREN") && selectionDisplay.getSpaceMode() === "local" ) { + Entities.editEntity(selectionManager.selections[0], { + localRotation: Quat.IDENTITY + }); + } else { + Entities.editEntity(selectionManager.selections[0], { + rotation: Quat.IDENTITY + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); } + } else if (data.type === "propertiesPageReady") { + updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); + } else if (data.type === "materialTargetRequest") { + var parentModelData; + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { + var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; + if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { + parentModelData = Graphics.getModel(properties.parentID); + } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { + parentModelData = {}; + parentModelData.numMeshes = 1; + parentModelData.materialNames = []; + } + } + emitScriptEvent({ + type: 'materialTargetReply', + entityID: data.entityID, + materialTargetData: parentModelData, + }); + } else if (data.type === "zoneListRequest") { + emitScriptEvent({ + type: 'zoneListRequest', + zones: getExistingZoneList() + }); } - } else if (data.type === "propertiesPageReady") { - updateSelections(true); - } else if (data.type === "tooltipsRequest") { + }; + + HMD.displayModeChanged.connect(function() { emitScriptEvent({ - type: 'tooltipsReply', - tooltips: Script.require('./assets/data/createAppTooltips.json'), + type: 'hmdActiveChanged', hmdActive: HMD.active, }); - } else if (data.type === "propertyRangeRequest") { - var propertyRanges = {}; - data.properties.forEach(function (property) { - propertyRanges[property] = Entities.getPropertyInfo(property); - }); - emitScriptEvent({ - type: 'propertyRangeReply', - propertyRanges: propertyRanges, - }); - } else if (data.type === "materialTargetRequest") { - var parentModelData; - var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); - if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { - var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; - if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { - parentModelData = Graphics.getModel(properties.parentID); - } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { - parentModelData = {}; - parentModelData.numMeshes = 1; - parentModelData.materialNames = []; - } - } - emitScriptEvent({ - type: 'materialTargetReply', - entityID: data.entityID, - materialTargetData: parentModelData, - }); - } else if (data.type === "zoneListRequest") { - emitScriptEvent({ - type: 'zoneListRequest', - zones: getExistingZoneList() - }); - } - }; - - HMD.displayModeChanged.connect(function() { - emitScriptEvent({ - type: 'hmdActiveChanged', - hmdActive: HMD.active, }); - }); - createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); - webView.webEventReceived.connect(this, onWebEventReceived); + webView.webEventReceived.connect(this, onWebEventReceived); - return that; -}; - - -var PopupMenu = function () { - var self = this; - - var MENU_ITEM_HEIGHT = 21; - var MENU_ITEM_SPACING = 1; - var TEXT_MARGIN = 7; - - var overlays = []; - var overlayInfo = {}; - - var visible = false; - - var upColor = { - red: 0, - green: 0, - blue: 0 - }; - var downColor = { - red: 192, - green: 192, - blue: 192 - }; - var overColor = { - red: 128, - green: 128, - blue: 128 + return that; }; - self.onSelectMenuItem = function () {}; - self.addMenuItem = function (name) { - var id = Overlays.addOverlay("text", { - text: name, - backgroundAlpha: 1.0, - backgroundColor: upColor, - topMargin: TEXT_MARGIN, - leftMargin: TEXT_MARGIN, - width: 210, - height: MENU_ITEM_HEIGHT, - font: { - size: 12 - }, - visible: false - }); - overlays.push(id); - overlayInfo[id] = { - name: name + var PopupMenu = function () { + var self = this; + + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; + + var overlays = []; + var overlayInfo = {}; + + var visible = false; + + var upColor = { + red: 0, + green: 0, + blue: 0 + }; + var downColor = { + red: 192, + green: 192, + blue: 192 + }; + var overColor = { + red: 128, + green: 128, + blue: 128 }; - return id; - }; - self.updateMenuItemText = function (id, newText) { - Overlays.editOverlay(id, { - text: newText - }); - }; + self.onSelectMenuItem = function () {}; - self.setPosition = function (x, y) { - for (var key in overlayInfo) { - Overlays.editOverlay(key, { - x: x, - y: y + self.addMenuItem = function (name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { + size: 12 + }, + visible: false }); - y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; - } - }; + overlays.push(id); + overlayInfo[id] = { + name: name + }; + return id; + }; - self.onSelected = function () {}; - - var pressingOverlay = null; - var hoveringOverlay = null; - - self.mousePressEvent = function (event) { - if (event.isLeftButton) { - var overlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText }); - if (overlay in overlayInfo) { - pressingOverlay = overlay; - Overlays.editOverlay(pressingOverlay, { - backgroundColor: downColor - }); - } else { - self.hide(); - } - return false; - } - }; - self.mouseMoveEvent = function (event) { - if (visible) { - var overlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - if (!pressingOverlay) { - if (hoveringOverlay !== null && overlay !== hoveringOverlay) { - Overlays.editOverlay(hoveringOverlay, { - backgroundColor: upColor - }); - hoveringOverlay = null; - } - if (overlay !== hoveringOverlay && overlay in overlayInfo) { - Overlays.editOverlay(overlay, { - backgroundColor: overColor - }); - hoveringOverlay = overlay; - } - } - } - return false; - }; - self.mouseReleaseEvent = function (event) { - var overlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - if (pressingOverlay !== null && pressingOverlay !== undefined) { - if (overlay === pressingOverlay) { - self.onSelectMenuItem(overlayInfo[overlay].name); - } - Overlays.editOverlay(pressingOverlay, { - backgroundColor: upColor - }); - pressingOverlay = null; - self.hide(); - } - }; + }; - self.setVisible = function (newVisible) { - if (newVisible !== visible) { - visible = newVisible; + self.setPosition = function (x, y) { for (var key in overlayInfo) { Overlays.editOverlay(key, { - visible: newVisible + x: x, + y: y }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; + + self.onSelected = function () {}; + + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function (event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { + backgroundColor: downColor + }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function (event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (!pressingOverlay) { + if (hoveringOverlay !== null && overlay !== hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { + backgroundColor: upColor + }); + hoveringOverlay = null; + } + if (overlay !== hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { + backgroundColor: overColor + }); + hoveringOverlay = overlay; + } + } + } + return false; + }; + self.mouseReleaseEvent = function (event) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (pressingOverlay !== null && pressingOverlay !== undefined) { + if (overlay === pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); + } + Overlays.editOverlay(pressingOverlay, { + backgroundColor: upColor + }); + pressingOverlay = null; + self.hide(); + } + }; + + self.setVisible = function (newVisible) { + if (newVisible !== visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + visible: newVisible + }); + } + } + }; + self.show = function () { + self.setVisible(true); + }; + self.hide = function () { + self.setVisible(false); + }; + + function cleanup() { + ContextOverlay.enabled = true; + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; + }; + + function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; + } + + function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); + } + }; + } + + var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + + var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); + } else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); + } + mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); + mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); + mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey); + mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey); + mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey); + mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey); + mapping.from([Controller.Hardware.Keyboard["0"]]).to(rotateAsNextClickedSurfaceKey); + mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey); + mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey); + mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey); + mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); + mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); + mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); + mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.duplicateSelection() })); + + // Bind undo to ctrl-shift-z to maintain backwards-compatibility + mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { undoHistory.redo() })); + + + mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { unparentSelectedEntities(); })); + + mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { parentSelectedEntities(); })); + + var keyUpEventFromUIWindow = function(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + + deleteKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "T") { + toggleKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "F") { + focusKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "J") { + gridKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "G") { + viewGridKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "H") { + snapKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "K") { + gridToAvatarKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "0") { + rotateAsNextClickedSurfaceKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "7") { + quickRotate90xKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "8") { + quickRotate90yKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "9") { + quickRotate90zKey(pressedValue); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { + selectionManager.duplicateSelection(); + } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + undoHistory.undo(); // undo is only handled via handleMenuItem on Mac + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + parentSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + unparentSelectedEntities(); + } else if (!isOnMacPlatform && + ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { + undoHistory.redo(); // redo is only handled via handleMenuItem on Mac + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + } + }; + + var propertyMenu = new PopupMenu(); + + propertyMenu.onSelectMenuItem = function (name) { + + if (propertyMenu.marketplaceID) { + showMarketplace(propertyMenu.marketplaceID); + } + }; + + var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); + + var propertiesTool = new PropertiesTool(); + + selectionDisplay.onSpaceModeChange = function(spaceMode) { + entityListTool.setSpaceMode(spaceMode); + propertiesTool.setSpaceMode(spaceMode); + }; + + function getExistingZoneList() { + var center = { "x": 0, "y": 0, "z": 0 }; + var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_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(zoneSortOrder); + return listExistingZones; + } + + function zoneSortOrder(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; + } + + //print("getParentState added"); + //function getParentState(id) { + createApp.getParentState = function(id) { + var state = "NONE"; + var properties = Entities.getEntityProperties(id, ["parentID"]); + var children = getDomainOnlyChildrenIDs(id); + if (properties.parentID !== Uuid.NULL) { + if (children.length > 0) { + state = "PARENT_CHILDREN"; + } else { + state = "CHILDREN"; + } + } else { + if (children.length > 0) { + state = "PARENT"; } } - }; - self.show = function () { - self.setVisible(true); - }; - self.hide = function () { - self.setVisible(false); - }; + return state; + } - function cleanup() { - ContextOverlay.enabled = true; - for (var i = 0; i < overlays.length; i++) { - Overlays.deleteOverlay(overlays[i]); + //print("Global object after getParentState" + JSON.stringify(globalThis)); + + function getDomainOnlyChildrenIDs(id) { + var allChildren = Entities.getChildrenIDs(id); + var realChildren = []; + var properties; + for (var i = 0; i < allChildren.length; i++) { + properties = Entities.getEntityProperties(allChildren[i], ["name"]); + if (properties.name !== undefined && properties.name !== entityShapeVisualizerSessionName) { + realChildren.push(allChildren[i]); + } } - Controller.mousePressEvent.disconnect(self.mousePressEvent); - Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); - Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); - - Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + return realChildren; } - Controller.mousePressEvent.connect(self.mousePressEvent); - Controller.mouseMoveEvent.connect(self.mouseMoveEvent); - Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); - Script.scriptEnding.connect(cleanup); - - return this; -}; - -function whenPressed(fn) { - return function(value) { - if (value > 0) { - fn(); - } - }; -} - -function whenReleased(fn) { - return function(value) { - if (value === 0) { - fn(); - } - }; -} - -var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); - -var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); -if (isOnMacPlatform) { - mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); -} else { - mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); -} -mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); -mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); -mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey); -mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey); -mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey); -mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey); -mapping.from([Controller.Hardware.Keyboard["0"]]).to(rotateAsNextClickedSurfaceKey); -mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey); -mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey); -mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey); -mapping.from([Controller.Hardware.Keyboard.X]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); -mapping.from([Controller.Hardware.Keyboard.C]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.copySelectedEntities() })); -mapping.from([Controller.Hardware.Keyboard.V]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.pasteEntities() })); -mapping.from([Controller.Hardware.Keyboard.D]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.duplicateSelection() })); - -// Bind undo to ctrl-shift-z to maintain backwards-compatibility -mapping.from([Controller.Hardware.Keyboard.Z]) - .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) - .to(whenPressed(function() { undoHistory.redo() })); - - -mapping.from([Controller.Hardware.Keyboard.P]) - .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) - .to(whenReleased(function() { unparentSelectedEntities(); })); - -mapping.from([Controller.Hardware.Keyboard.P]) - .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) - .to(whenReleased(function() { parentSelectedEntities(); })); - -var keyUpEventFromUIWindow = function(keyUpEvent) { - var WANT_DEBUG_MISSING_SHORTCUTS = false; - - var pressedValue = 0.0; - - if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") - || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { - - deleteKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "T") { - toggleKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "F") { - focusKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "J") { - gridKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "G") { - viewGridKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "H") { - snapKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "K") { - gridToAvatarKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "0") { - rotateAsNextClickedSurfaceKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "7") { - quickRotate90xKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "8") { - quickRotate90yKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "9") { - quickRotate90zKey(pressedValue); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { - selectionManager.cutSelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { - selectionManager.copySelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { - selectionManager.pasteEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { - selectionManager.duplicateSelection(); - } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { - undoHistory.undo(); // undo is only handled via handleMenuItem on Mac - } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { - parentSelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { - unparentSelectedEntities(); - } else if (!isOnMacPlatform && - ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || - (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { - undoHistory.redo(); // redo is only handled via handleMenuItem on Mac - } else if (WANT_DEBUG_MISSING_SHORTCUTS) { - console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + function importEntitiesFromFile() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select .json to Import", "", "*.json"); } -}; -var propertyMenu = new PopupMenu(); - -propertyMenu.onSelectMenuItem = function (name) { - - if (propertyMenu.marketplaceID) { - showMarketplace(propertyMenu.marketplaceID); + function importEntitiesFromUrl() { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of a .json to import", ""); } -}; -var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); - -var propertiesTool = new PropertiesTool(); - -selectionDisplay.onSpaceModeChange = function(spaceMode) { - entityListTool.setSpaceMode(spaceMode); - propertiesTool.setSpaceMode(spaceMode); -}; - -function getExistingZoneList() { - var center = { "x": 0, "y": 0, "z": 0 }; - var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_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(zoneSortOrder); - return listExistingZones; -} - -function zoneSortOrder(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; -} - -//print("getParentState added"); -function getParentState(id) { - var state = "NONE"; - var properties = Entities.getEntityProperties(id, ["parentID"]); - var children = getDomainOnlyChildrenIDs(id); - if (properties.parentID !== Uuid.NULL) { - if (children.length > 0) { - state = "PARENT_CHILDREN"; - } else { - state = "CHILDREN"; - } - } else { - if (children.length > 0) { - state = "PARENT"; + function setCameraFocusToSelection() { + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } } - return state; -} -//print("Global object after getParentState" + JSON.stringify(globalThis)); - -function getDomainOnlyChildrenIDs(id) { - var allChildren = Entities.getChildrenIDs(id); - var realChildren = []; - var properties; - for (var i = 0; i < allChildren.length; i++) { - properties = Entities.getEntityProperties(allChildren[i], ["name"]); - if (properties.name !== undefined && properties.name !== entityShapeVisualizerSessionName) { - realChildren.push(allChildren[i]); + function alignGridToSelection() { + if (selectionManager.hasSelection()) { + if (!grid.getVisible()) { + grid.setVisible(true, true); + } + grid.moveToSelection(); } } - return realChildren; -} -function importEntitiesFromFile() { - Window.browseChanged.connect(onFileOpenChanged); - Window.browseAsync("Select .json to Import", "", "*.json"); -} - -function importEntitiesFromUrl() { - Window.promptTextChanged.connect(onPromptTextChanged); - Window.promptAsync("URL of a .json to import", ""); -} - -function setCameraFocusToSelection() { - cameraManager.enable(); - if (selectionManager.hasSelection()) { - cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - } -} - -function alignGridToSelection() { - if (selectionManager.hasSelection()) { + function alignGridToAvatar() { if (!grid.getVisible()) { grid.setVisible(true, true); } - grid.moveToSelection(); + grid.moveToAvatar(); } -} -function alignGridToAvatar() { - if (!grid.getVisible()) { - grid.setVisible(true, true); + function toggleGridVisibility() { + if (!grid.getVisible()) { + grid.setVisible(true, true); + } else { + grid.setVisible(false, true); + } } - grid.moveToAvatar(); -} -function toggleGridVisibility() { - if (!grid.getVisible()) { - grid.setVisible(true, true); - } else { - grid.setVisible(false, true); + function rotateAsNextClickedSurface() { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + expectingRotateAsClickedSurface = false; + } else { + expectingRotateAsClickedSurface = true; + } } -} -function rotateAsNextClickedSurface() { - if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected, or the selection is locked."); - expectingRotateAsClickedSurface = false; - } else { - expectingRotateAsClickedSurface = true; - } -} - -//}()); // END LOCAL_SCOPE +}()); // END LOCAL_SCOPE +//}(); // END LOCAL_SCOPE diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index 47a04ad0bd..bd82d9a424 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -218,7 +218,7 @@ var EntityListTool = function(shouldUseEditTabletApp) { url = properties.imageURL; } //print("Global object before getParentState call: " + JSON.stringify(globalThis)); - var parentStatus = getParentState(ids[i]); + var parentStatus = that.createApp.getParentState(ids[i]); var parentState = ""; if (parentStatus === "PARENT") { parentState = "A"; diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index f5469bada7..397ce4b030 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -83,12 +83,15 @@ SelectionManager = (function() { // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES function handleEntitySelectionToolUpdates(channel, message, sender) { + //print("Channel: " + channel + " Sender: " + sender + " Message: " + JSON.stringify(message)); if (channel !== 'entityToolUpdates') { return; } + print("handleEntitySelectionToolUpdates entityToolUpdates"); if (sender !== MyAvatar.sessionUUID) { return; } + print("handleEntitySelectionToolUpdates MyAvatar.sessionUUID"); var wantDebug = false; var messageParsed; @@ -99,6 +102,7 @@ SelectionManager = (function() { return; } + print("handleEntitySelectionToolUpdates JSON.parse(message): " + messageParsed.method); if (messageParsed.method === "selectEntity") { if (!that.editEnabled) { return; @@ -125,7 +129,7 @@ SelectionManager = (function() { } selectionDisplay.moveSelection(Vec3.sum(messageParsed.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); that._update(false, this); - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); expectingRotateAsClickedSurface = false; audioFeedback.action(); } @@ -147,6 +151,7 @@ SelectionManager = (function() { that.clearSelections(); } } else if (messageParsed.method === "pointingAt") { + print("handleEntitySelectionToolUpdates pointingAt"); if (messageParsed.hand === Controller.Standard.RightHand) { that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; that.pointingAtTabletRight = messageParsed.tablet; @@ -232,6 +237,8 @@ SelectionManager = (function() { }; that.setSelections = function(entityIDs, caller) { + print("setSelections: " + JSON.stringify(entityIDs)); + Script.logBacktrace("setSelections"); that.selections = []; for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; @@ -723,7 +730,7 @@ SelectionManager = (function() { var newPosition = Vec3.sum(relativePosition, targetPosition); Entities.editEntity(id, { "position": newPosition }); } - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); that._update(false, this); } else { audioFeedback.rejection(); @@ -2002,7 +2009,7 @@ SelectionDisplay = (function() { var handleBoundingBoxColor = COLOR_BOUNDING_EDGE; if (SelectionManager.selections.length === 1) { - var parentState = getParentState(SelectionManager.selections[0]); + var parentState = that.createApp.getParentState(SelectionManager.selections[0]); if (parentState === "CHILDREN") { handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN; } else if (parentState === "PARENT") { @@ -2504,7 +2511,7 @@ SelectionDisplay = (function() { } updateSelectionsRotation(axisRotation, SelectionManager.worldPosition); SelectionManager._update(false, this); - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); audioFeedback.action(); } }; @@ -2585,7 +2592,7 @@ SelectionDisplay = (function() { print(" starting elevation: " + startingElevation); } - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); @@ -2598,7 +2605,7 @@ SelectionDisplay = (function() { } }, onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); + that.createApp.pushCommandForSelections(duplicatedEntityIDs); if (isConstrained) { Entities.editEntity(xRailToolEntity, { visible: false, @@ -2617,7 +2624,7 @@ SelectionDisplay = (function() { var wantDebug = false; var pickRay = generalComputePickRay(event.x, event.y); - var newPick = rayPlaneIntersection2(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection2(pickRay, pickPlanePosition, pickPlaneNormal); // If the pick ray doesn't hit the pick plane in this direction, do nothing. // this will happen when someone drags across the horizon from the side they started on. @@ -2722,7 +2729,7 @@ SelectionDisplay = (function() { var negateAndHalve = -0.5; var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); vector = Vec3.subtract( - grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + that.grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); // editing a parent will cause all the children to automatically follow along, so don't @@ -2801,7 +2808,7 @@ SelectionDisplay = (function() { axisVector = Vec3.multiplyQbyV(rotation, axisVector); pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); pickPlanePosition = SelectionManager.worldPosition; - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); SelectionManager.saveProperties(); that.resetPreviousHandleColor(); @@ -2822,7 +2829,7 @@ SelectionDisplay = (function() { } }, onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); + that.createApp.pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -2832,7 +2839,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -2850,8 +2857,8 @@ SelectionDisplay = (function() { var dotVector = Vec3.dot(vector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector); - var gridOrigin = grid.getOrigin(); - vector = Vec3.subtract(grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); + var gridOrigin = that.grid.getOrigin(); + vector = Vec3.subtract(that.grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); var wantDebug = false; if (wantDebug) { @@ -2962,7 +2969,7 @@ SelectionDisplay = (function() { pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); pickPlanePosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); that.setHandleTranslateVisible(false); that.setHandleRotateVisible(false); @@ -3008,8 +3015,8 @@ SelectionDisplay = (function() { Entities.editEntity(stretchPanel, { visible: false, ignorePickIntersection: true }); } activeStretchCubePanelOffset = null; - - pushCommandForSelections(); + + that.createApp.pushCommandForSelections(); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -3019,7 +3026,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -3029,7 +3036,7 @@ SelectionDisplay = (function() { changeInDimensions = Vec3.multiply(dotVector, axisVector); changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(rotation), changeInDimensions); changeInDimensions = Vec3.multiplyVbyV(mask, changeInDimensions); - changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = that.grid.snapToSpacing(changeInDimensions); changeInDimensions = Vec3.multiply(NEGATE_VECTOR, Vec3.multiplyVbyV(signs, changeInDimensions)); var newDimensions = Vec3.sum(initialDimensions, changeInDimensions); @@ -3088,7 +3095,7 @@ SelectionDisplay = (function() { pickPlanePosition = initialPosition; pickPlaneNormal = Vec3.subtract(pickRay.origin, pickPlanePosition); - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); that.setHandleTranslateVisible(false); that.setHandleRotateVisible(false); @@ -3120,8 +3127,8 @@ SelectionDisplay = (function() { Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); that.replaceCollisionsAfterStretch = false; } - - pushCommandForSelections(); + + that.createApp.pushCommandForSelections(); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -3131,7 +3138,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -3140,7 +3147,7 @@ SelectionDisplay = (function() { var dimensionsMultiple = toCameraDistance * SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE; var changeInDimensions = Vec3.subtract(newPick, initialPick); changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(Camera.orientation), changeInDimensions); - changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = that.grid.snapToSpacing(changeInDimensions); changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); var averageDimensionChange = (changeInDimensions.x + changeInDimensions.y + changeInDimensions.z) / 3; @@ -3309,7 +3316,7 @@ SelectionDisplay = (function() { // editOverlays may not have committed rotation changes. // Compute zero position based on where the toolEntity will be eventually. - var initialPick = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + var initialPick = that.createApp.rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); // In case of a parallel ray, this will be null, which will cause early-out // in the onMove helper. rotationZero = initialPick; @@ -3343,7 +3350,7 @@ SelectionDisplay = (function() { } }); Entities.editEntity(handleRotateCurrentRing, { visible: false, ignorePickIntersection: true }); - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); if (wantDebug) { print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); } @@ -3364,7 +3371,7 @@ SelectionDisplay = (function() { } var pickRay = generalComputePickRay(event.x, event.y); - var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + var result = that.createApp.rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); if (result) { var centerToZero = Vec3.subtract(rotationZero, rotationCenter); var centerToIntersect = Vec3.subtract(result, rotationCenter); diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 4674e5925d..81edf761bd 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -292,8 +292,8 @@ GridTool = function(opts) { var dataString = JSON.stringify(data); webView.emitScriptEvent(dataString); createToolsWindow.emitScriptEvent(dataString); - if (selectionDisplay) { - selectionDisplay.updateHandles(); + if (that.selectionDisplay) { + that.selectionDisplay.updateHandles(); } });