From 714a3f6b086e7210059a1c5d028622d712ed3d02 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Tue, 9 Jul 2019 13:02:54 -0700 Subject: [PATCH] Fixed path to tabletAssetServer in HMD, simplifiedUI system folder --- interface/src/Application.cpp | 2 +- .../system/assets/images/hourglass.svg | 18 + .../assets/data/createAppTooltips.json | 0 .../system/create/assets/images/hourglass.svg | 18 + .../images/icon-particles.svg} | 0 .../create/assets/images/icon-point-light.svg | 57 + .../create/assets/images/icon-spot-light.svg | 37 + .../system/create/assets/images/icon-zone.svg | 73 + scripts/simplifiedUI/system/create/edit.js | 2858 ++++++++++++++++ .../system/create/entityList/entityList.js | 330 ++ .../create/entityList/html/entityList.html | 93 + .../entityList}/html/js/entityList.js | 0 .../html/js/entityListContextMenu.js | 0 .../entityList}/html/js/listView.js | 0 .../{ => entityList/qml}/EditEntityList.qml | 2 +- .../{ => entityList/qml}/EntityList.qml | 2 +- .../html/entityProperties.html | 52 + .../html/js/createAppTooltip.js | 116 + .../html/js/draggableNumber.js | 0 .../html/js/entityProperties.js | 0 .../html/js/underscore-min.js | 0 .../entitySelectionTool.js | 2925 +++++++++++++++++ .../{ => create}/modules/createWindow.js | 0 .../modules/entityShapeVisualizer.js | 4 +- .../system/create/{ => qml}/Edit.qml | 2 +- .../system/create/{ => qml}/EditTabButton.qml | 0 .../system/create/{ => qml}/EditTabView.qml | 24 +- .../system/create/{ => qml}/EditTools.qml | 0 .../create/{ => qml}/EditToolsTabView.qml | 22 +- .../create/{ => qml}/NewEntityButton.qml | 0 .../create/{ => qml}/NewMaterialDialog.qml | 2 +- .../create/{ => qml}/NewMaterialWindow.qml | 0 .../create/{ => qml}/NewModelDialog.qml | 11 +- .../create/{ => qml}/NewModelWindow.qml | 0 .../icons}/126-material-01.svg | 0 .../icons}/20-text-01.svg | 0 .../icons}/21-cube-01.svg | 0 .../icons}/22-sphere-01.svg | 0 .../icons}/23-zone-01.svg | 0 .../icons}/24-light-01.svg | 0 .../icons}/25-web-1-01.svg | 0 .../create/qml/icons/90-particles-01.svg | 29 + .../icons}/94-model-01.svg | 0 .../{create-icons => qml/icons}/image.svg | 0 .../keyboardShortcuts/keyboardShortcuts.js | 29 + scripts/system/create/qml/Edit.qml | 2 +- scripts/system/create/qml/NewModelDialog.qml | 9 +- 47 files changed, 6669 insertions(+), 48 deletions(-) create mode 100644 scripts/simplifiedUI/system/assets/images/hourglass.svg rename scripts/simplifiedUI/system/{ => create}/assets/data/createAppTooltips.json (100%) create mode 100644 scripts/simplifiedUI/system/create/assets/images/hourglass.svg rename scripts/simplifiedUI/system/create/{create-icons/90-particles-01.svg => assets/images/icon-particles.svg} (100%) create mode 100644 scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg create mode 100644 scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg create mode 100644 scripts/simplifiedUI/system/create/assets/images/icon-zone.svg create mode 100644 scripts/simplifiedUI/system/create/edit.js create mode 100644 scripts/simplifiedUI/system/create/entityList/entityList.js create mode 100644 scripts/simplifiedUI/system/create/entityList/html/entityList.html rename scripts/simplifiedUI/system/{ => create/entityList}/html/js/entityList.js (100%) rename scripts/simplifiedUI/system/{ => create/entityList}/html/js/entityListContextMenu.js (100%) rename scripts/simplifiedUI/system/{ => create/entityList}/html/js/listView.js (100%) rename scripts/simplifiedUI/system/create/{ => entityList/qml}/EditEntityList.qml (82%) rename scripts/simplifiedUI/system/create/{ => entityList/qml}/EntityList.qml (58%) create mode 100644 scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html create mode 100644 scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js rename scripts/simplifiedUI/system/{ => create/entityProperties}/html/js/draggableNumber.js (100%) rename scripts/simplifiedUI/system/{ => create/entityProperties}/html/js/entityProperties.js (100%) rename scripts/simplifiedUI/system/{ => create/entityProperties}/html/js/underscore-min.js (100%) create mode 100644 scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js rename scripts/simplifiedUI/system/{ => create}/modules/createWindow.js (100%) rename scripts/simplifiedUI/system/{ => create}/modules/entityShapeVisualizer.js (98%) rename scripts/simplifiedUI/system/create/{ => qml}/Edit.qml (95%) rename scripts/simplifiedUI/system/create/{ => qml}/EditTabButton.qml (100%) rename scripts/simplifiedUI/system/create/{ => qml}/EditTabView.qml (93%) rename scripts/simplifiedUI/system/create/{ => qml}/EditTools.qml (100%) rename scripts/simplifiedUI/system/create/{ => qml}/EditToolsTabView.qml (93%) rename scripts/simplifiedUI/system/create/{ => qml}/NewEntityButton.qml (100%) rename scripts/simplifiedUI/system/create/{ => qml}/NewMaterialDialog.qml (99%) rename scripts/simplifiedUI/system/create/{ => qml}/NewMaterialWindow.qml (100%) rename scripts/simplifiedUI/system/create/{ => qml}/NewModelDialog.qml (95%) rename scripts/simplifiedUI/system/create/{ => qml}/NewModelWindow.qml (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/126-material-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/20-text-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/21-cube-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/22-sphere-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/23-zone-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/24-light-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/25-web-1-01.svg (100%) create mode 100644 scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/94-model-01.svg (100%) rename scripts/simplifiedUI/system/create/{create-icons => qml/icons}/image.svg (100%) create mode 100644 scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3c37bb6a6f..b0da721823 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7880,7 +7880,7 @@ void Application::showAssetServerWidget(QString filePath) { if (!hmd->getShouldShowTablet() && !isHMDMode()) { getOffscreenUI()->show(url, "AssetServer", startUpload); } else { - static const QUrl url("hifi/dialogs/TabletAssetServer.qml"); + static const QUrl url("qrc:///qml/hifi/dialogs/TabletAssetServer.qml"); if (!tablet->isPathLoaded(url)) { tablet->pushOntoStack(url); } diff --git a/scripts/simplifiedUI/system/assets/images/hourglass.svg b/scripts/simplifiedUI/system/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/data/createAppTooltips.json b/scripts/simplifiedUI/system/create/assets/data/createAppTooltips.json similarity index 100% rename from scripts/simplifiedUI/system/assets/data/createAppTooltips.json rename to scripts/simplifiedUI/system/create/assets/data/createAppTooltips.json diff --git a/scripts/simplifiedUI/system/create/assets/images/hourglass.svg b/scripts/simplifiedUI/system/create/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/assets/images/icon-particles.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg rename to scripts/simplifiedUI/system/create/assets/images/icon-particles.svg diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg b/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg new file mode 100644 index 0000000000..896c35b63b --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg b/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg new file mode 100644 index 0000000000..ac2f87bb27 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg b/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/create/edit.js b/scripts/simplifiedUI/system/create/edit.js new file mode 100644 index 0000000000..f50bc547e5 --- /dev/null +++ b/scripts/simplifiedUI/system/create/edit.js @@ -0,0 +1,2858 @@ +// edit.js +// +// Created by Brad Hefta-Gaub on 10/2/14. +// Persist toolbar by HRS 6/11/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, + Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, + keyUpEventFromUIWindow:true */ + +(function() { // BEGIN LOCAL_SCOPE + +"use strict"; + +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + +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" +]); + +var CreateWindow = Script.require('./modules/createWindow.js'); + +var TITLE_OFFSET = 60; +var CREATE_TOOLS_WIDTH = 490; +var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + +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 + } + } + }, + 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)); +}; + + +var selectionDisplay = SelectionDisplay; +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 entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); + if (properties.type === 'Light') { + return { + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; + } else { + return { + url: PARTICLE_SYSTEM_URL, + }; + } +}); + +var cameraManager = new CameraManager(); + +var grid = new Grid(); +var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp +}); +gridTool.setVisible(false); + +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + +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_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; +var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones 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 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_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; +} + +var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; + +// 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, + 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 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + 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" + }, + 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://highfidelity.com/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + 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; + + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = getPositionToCreateEntity(); + var entityID = null; + + var properties = {}; + + 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; + } else { + 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 = 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.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } + } + + entityID = Entities.addEntity(properties); + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // 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); + } + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); + } + + 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"; + } + + 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, + }); + } + } + } + + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + //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 + }); + } + } + } + + 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; + } + } + + 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() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + }); + + 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 }; + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + flags: 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")); + + 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); + + 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); + } 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(); + } + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + }; + + initialize(); + return that; +})(); + +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); + + 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 (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) { + 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) { + selectionManager.clearSelections(this); + } + 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) { + 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)); + } + } + } 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 modelMenuAddedDelete = false; +var originalLightsArePickable = Entities.getLightsArePickable(); + +function setupModelMenus() { + // adj our menuitems + 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: "Entities", + isSeparator: true + }); + if (!Menu.menuItemExists("Edit", "Delete")) { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Delete", + shortcutKeyEvent: { + text: "delete" + }, + afterItem: "Entities", + }); + modelMenuAddedDelete = true; + } + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last" + }); + + Menu.addMenuItem({ + menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + afterItem: "Unparent Entity", + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true) + }); + + Menu.addMenuItem({ + menuName: "Edit", + 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: "Edit", + 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: "Edit", + 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: "Edit", + menuItemName: "Select All Entities In Box", + afterItem: "Allow Selecting of Lights" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities Touching Box", + afterItem: "Select All Entities In Box" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Export Entities", + afterItem: "Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities", + afterItem: "Export Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities from URL", + afterItem: "Import Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, + afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false" + }); + + Entities.setLightsArePickable(false); +} + +setupModelMenus(); // do this when first running our script. + +function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeSeparator("Edit", "Entities"); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); + Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem("Edit", "Select All Entities In Box"); + Menu.removeMenuItem("Edit", "Select All Entities Touching Box"); + + Menu.removeMenuItem("Edit", "Export Entities"); + Menu.removeMenuItem("Edit", "Import Entities"); + Menu.removeMenuItem("Edit", "Import Entities from URL"); + + Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); +} + +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_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_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 = null; +var lastPosition = null; + +// 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()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + + if (selectedEntities.length < 1) { + 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; + } + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + 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}); + } + }); + + if (parentCheck) { + Window.notify("Entities parented"); + } else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} +function deleteSelectedEntities() { + if (SelectionManager.hasSelection()) { + 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); + pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } + } +} + +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 onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } +} + +function handleMenuEvent(menuItem) { + if (menuItem === "Allow Selecting of Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); + } else if (menuItem === "Allow Selecting of Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); + } else if (menuItem === "Allow Selecting of Lights") { + Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); + } else if (menuItem === "Delete") { + deleteSelectedEntities(); + } else if (menuItem === "Undo") { + undoHistory.undo(); + } else if (menuItem === "Redo") { + undoHistory.redo(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); + } else if (menuItem === "Export Entities") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { + if (menuItem === "Import Entities") { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + } else { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of SVO to import", ""); + } + } else if (menuItem === "Select All Entities In Box") { + selectAllEntitiesInCurrentSelectionBox(false); + } else if (menuItem === "Select All Entities Touching Box") { + selectAllEntitiesInCurrentSelectionBox(true); + } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } + 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 (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 + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +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); + } + }, 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 (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.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; + } 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 + }; + 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); + } + } 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 + }); + } + 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 + }); + } + } + 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) + }); + } + 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 === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } + } + } + } 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, + }); + } + }; + + HMD.displayModeChanged.connect(function() { + emitScriptEvent({ + type: 'hmdActiveChanged', + hmdActive: HMD.active, + }); + }); + + createToolsWindow.webEventReceived.addListener(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 + }; + + 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 + }; + return id; + }; + + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText + }); + }; + + self.setPosition = function (x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + 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.G]).to(gridKey); +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(); })); + +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 === "G") { + gridKey(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); +}; + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/create/entityList/entityList.js b/scripts/simplifiedUI/system/create/entityList/entityList.js new file mode 100644 index 0000000000..06e100f457 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityList/entityList.js @@ -0,0 +1,330 @@ +"use strict"; + +// entityList.js +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, + cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible, + keyUpEventFromUIWindow, Script, SelectionDisplay, SelectionManager, Clipboard */ + +var PROFILING_ENABLED = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +}; +const PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + +EntityListTool = function(shouldUseEditTabletApp) { + var that = {}; + + var CreateWindow = Script.require('../modules/createWindow.js'); + + var TITLE_OFFSET = 60; + var ENTITY_LIST_WIDTH = 495; + var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; + var entityListWindow = new CreateWindow( + Script.resolvePath("./qml/EditEntityList.qml"), + 'Entity List', + 'com.highfidelity.create.entityListWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_CREATE_TOOLS_HEIGHT) { + windowHeight = MAX_DEFAULT_CREATE_TOOLS_HEIGHT; + } + return { + size: { + x: ENTITY_LIST_WIDTH, + y: windowHeight + }, + position: { + x: Window.x, + y: Window.y + TITLE_OFFSET + } + }; + }, + false + ); + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value){ }; + + var filterInView = false; + var searchRadius = 100; + + var visible = false; + + that.webView = webView; + + that.setVisible = function(newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.isVisible = function() { + return entityListWindow.isVisible(); + }; + + that.setVisible(false); + + function emitJSONScriptEvent(data) { + var dataString; + PROFILE("Script-JSON.stringify", function() { + dataString = JSON.stringify(data); + }); + PROFILE("Script-emitScriptEvent", function() { + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + }); + } + + that.toggleVisible = function() { + that.setVisible(!visible); + }; + + selectionManager.addEventListener(function(isSelectionUpdate, caller) { + if (caller === that) { + // ignore events that we emitted from the entity list itself + return; + } + var selectedIDs = []; + + for (var i = 0; i < selectionManager.selections.length; i++) { + selectedIDs.push(selectionManager.selections[i]); + } + + emitJSONScriptEvent({ + type: 'selectionUpdate', + selectedIDs: selectedIDs + }); + }); + + that.setSpaceMode = function(spaceMode) { + emitJSONScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }); + }; + + that.clearEntityList = function() { + emitJSONScriptEvent({ + type: 'clearEntityList' + }); + }; + + that.removeEntities = function (deletedIDs, selectedIDs) { + emitJSONScriptEvent({ + type: 'removeEntities', + deletedIDs: deletedIDs, + selectedIDs: selectedIDs + }); + }; + + that.deleteEntities = function (deletedIDs) { + emitJSONScriptEvent({ + type: "deleted", + ids: deletedIDs + }); + }; + + function valueIfDefined(value) { + return value !== undefined ? value : ""; + } + + function entityIsBaked(properties) { + if (properties.type === "Model") { + var lowerModelURL = properties.modelURL.toLowerCase(); + return lowerModelURL.endsWith(".baked.fbx") || lowerModelURL.endsWith(".baked.fst"); + } else if (properties.type === "Zone") { + var lowerSkyboxURL = properties.skybox ? properties.skybox.url.toLowerCase() : ""; + var lowerAmbientURL = properties.ambientLight ? properties.ambientLight.ambientURL.toLowerCase() : ""; + return (lowerSkyboxURL === "" || lowerSkyboxURL.endsWith(".texmeta.json")) && + (lowerAmbientURL === "" || lowerAmbientURL.endsWith(".texmeta.json")); + } else { + return false; + } + } + + that.sendUpdate = function() { + PROFILE('Script-sendUpdate', function() { + var entities = []; + + var ids; + PROFILE("findEntities", function() { + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); + } + }); + + var cameraPosition = Camera.position; + PROFILE("getMultipleProperties", function () { + var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID', + 'skybox.url', 'ambientLight.url']); + for (var i = 0; i < multipleProperties.length; i++) { + var properties = multipleProperties[i]; + + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type === "Model") { + url = properties.modelURL; + } else if (properties.type === "Material") { + url = properties.materialURL; + } else if (properties.type === "Image") { + url = properties.imageURL; + } + entities.push({ + id: ids[i], + name: properties.name, + type: properties.type, + url: url, + locked: properties.locked, + visible: properties.visible, + certificateID: properties.certificateID, + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: entityIsBaked(properties), + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), + hasScript: properties.script !== "" + }); + } + } + }); + + var selectedIDs = []; + for (var j = 0; j < selectionManager.selections.length; j++) { + selectedIDs.push(selectionManager.selections[j]); + } + + emitJSONScriptEvent({ + type: "update", + entities: entities, + selectedIDs: selectedIDs, + spaceMode: SelectionDisplay.getSpaceMode(), + }); + }); + }; + + function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } + } + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + print("entityList.js: Error parsing JSON"); + return; + } + + if (data.type === "selectionUpdate") { + var ids = data.entityIds; + var entityIDs = []; + for (var i = 0; i < ids.length; i++) { + entityIDs.push(ids[i]); + } + selectionManager.setSelections(entityIDs, that); + if (data.focus) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } else if (data.type === "refresh") { + that.sendUpdate(); + } else if (data.type === "teleport") { + if (selectionManager.hasSelection()) { + MyAvatar.position = selectionManager.worldPosition; + } + } else if (data.type === "export") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (data.type === "pal") { + var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. + selectionManager.selections.forEach(function (id) { + var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; + if (lastEditedBy) { + sessionIds[lastEditedBy] = true; + } + }); + var dedupped = Object.keys(sessionIds); + if (!selectionManager.selections.length) { + Window.alert('No objects selected.'); + } else if (!dedupped.length) { + Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.'); + } else { + // No need to subscribe if we're just sending. + Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local'); + } + } else if (data.type === "delete") { + deleteSelectedEntities(); + } else if (data.type === "toggleLocked") { + toggleSelectedEntitiesLocked(); + } else if (data.type === "toggleVisible") { + toggleSelectedEntitiesVisible(); + } else if (data.type === "filterInView") { + filterInView = data.filterInView === true; + } else if (data.type === "radius") { + searchRadius = data.radius; + } else if (data.type === "cut") { + SelectionManager.cutSelectedEntities(); + } else if (data.type === "copy") { + SelectionManager.copySelectedEntities(); + } else if (data.type === "paste") { + SelectionManager.pasteEntities(); + } else if (data.type === "duplicate") { + SelectionManager.duplicateSelection(); + that.sendUpdate(); + } else if (data.type === "rename") { + Entities.editEntity(data.entityID, {name: data.name}); + // make sure that the name also gets updated in the properties window + SelectionManager._update(); + } else if (data.type === "toggleSpaceMode") { + SelectionDisplay.toggleSpaceMode(); + } else if (data.type === 'keyUpEvent') { + keyUpEventFromUIWindow(data.keyUpEvent); + } + }; + + webView.webEventReceived.connect(onWebEventReceived); + entityListWindow.webEventReceived.addListener(onWebEventReceived); + that.interactiveWindowHidden = entityListWindow.interactiveWindowHidden; + + return that; +}; diff --git a/scripts/simplifiedUI/system/create/entityList/html/entityList.html b/scripts/simplifiedUI/system/create/entityList/html/entityList.html new file mode 100644 index 0000000000..3e17a66df5 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityList/html/entityList.html @@ -0,0 +1,93 @@ + + + + Entity List + + + + + + + + + + + + + +
+ +
+ + +
+ + + +
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+ Y +
+ +
+ + +
+
+
+ + + + +
+
+
+ +
+
+
+ +
+
+
+ There are no entities to display. Please check your filters or create an entity to begin. +
+
+
+ + + diff --git a/scripts/simplifiedUI/system/html/js/entityList.js b/scripts/simplifiedUI/system/create/entityList/html/js/entityList.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityList.js rename to scripts/simplifiedUI/system/create/entityList/html/js/entityList.js diff --git a/scripts/simplifiedUI/system/html/js/entityListContextMenu.js b/scripts/simplifiedUI/system/create/entityList/html/js/entityListContextMenu.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityListContextMenu.js rename to scripts/simplifiedUI/system/create/entityList/html/js/entityListContextMenu.js diff --git a/scripts/simplifiedUI/system/html/js/listView.js b/scripts/simplifiedUI/system/create/entityList/html/js/listView.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/listView.js rename to scripts/simplifiedUI/system/create/entityList/html/js/listView.js diff --git a/scripts/simplifiedUI/system/create/EditEntityList.qml b/scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml similarity index 82% rename from scripts/simplifiedUI/system/create/EditEntityList.qml rename to scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml index 94935c7bb5..1d5beb9914 100644 --- a/scripts/simplifiedUI/system/create/EditEntityList.qml +++ b/scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml @@ -10,7 +10,7 @@ import stylesUit 1.0 WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../html/entityList.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/EntityList.qml b/scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml similarity index 58% rename from scripts/simplifiedUI/system/create/EntityList.qml rename to scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml index 2f8a8863be..8f92ffe6ce 100644 --- a/scripts/simplifiedUI/system/create/EntityList.qml +++ b/scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml @@ -1,6 +1,6 @@ WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../html/entityList.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html b/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html new file mode 100644 index 0000000000..876e75ec35 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html @@ -0,0 +1,52 @@ + + + + + Properties + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js new file mode 100644 index 0000000000..3eb206d8a3 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js @@ -0,0 +1,116 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; + this._delayTimeout = null; + this._isEnabled = false; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + _delayTimeout: null, + _isEnabled: null, + + _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID, jsPropertyName) { + element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = { tooltip: 'PLEASE SET THIS TOOLTIP' }; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "create-app-tooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "create-app-tooltip-description"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = jsPropertyName; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "create-app-tooltip-js-attribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip below by default + elTip.style.top = desiredTooltipTop; + } + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } + + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); + }.bind(this), false); + element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/draggableNumber.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/draggableNumber.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/draggableNumber.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/draggableNumber.js diff --git a/scripts/simplifiedUI/system/html/js/entityProperties.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/entityProperties.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityProperties.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/entityProperties.js diff --git a/scripts/simplifiedUI/system/html/js/underscore-min.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/underscore-min.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/underscore-min.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/underscore-min.js diff --git a/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js new file mode 100644 index 0000000000..9c993d6d73 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js @@ -0,0 +1,2925 @@ +// +// entitySelectionTool.js +// examples +// +// Created by Brad hefta-Gaub on 10/1/14. +// Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017 +// Modified by David Back on 1/9/2018 +// Copyright 2014 High Fidelity, Inc. +// +// This script implements a class useful for building tools for editing entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, + getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ + +const SPACE_LOCAL = "local"; +const SPACE_WORLD = "world"; +const HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; + +Script.include([ + "../../libraries/controllers.js", + "../../libraries/controllerDispatcherUtils.js", + "../../libraries/utils.js" +]); + + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + +SelectionManager = (function() { + var that = {}; + + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES + function subscribeToUpdateMessages() { + Messages.subscribe("entityToolUpdates"); + Messages.messageReceived.connect(handleEntitySelectionToolUpdates); + } + + // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES + function handleEntitySelectionToolUpdates(channel, message, sender) { + if (channel !== 'entityToolUpdates') { + return; + } + if (sender !== MyAvatar.sessionUUID) { + return; + } + + var wantDebug = false; + var messageParsed; + try { + messageParsed = JSON.parse(message); + } catch (err) { + print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message"); + return; + } + + if (messageParsed.method === "selectEntity") { + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } + that.setSelections([messageParsed.entityID], that); + } + } else if (messageParsed.method === "clearSelection") { + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + that.clearSelections(); + } + } else if (messageParsed.method === "pointingAt") { + if (messageParsed.hand === Controller.Standard.RightHand) { + that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; + that.pointingAtTabletRight = messageParsed.tablet; + } else { + that.pointingAtDesktopWindowLeft = messageParsed.desktopWindow; + that.pointingAtTabletLeft = messageParsed.tablet; + } + } + } + + subscribeToUpdateMessages(); + + // disabling this for now as it is causing rendering issues with the other handle overlays + /* + var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 }; + var editHandleOutlineStyle = { + outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillOccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineUnoccludedAlpha: 1, + outlineOccludedAlpha: 0, + fillUnoccludedAlpha: 0, + fillOccludedAlpha: 0, + outlineWidth: 3, + isOutlineSmooth: true + }; + Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); + */ + + that.savedProperties = {}; + that.selections = []; + var listeners = []; + + that.localRotation = Quat.IDENTITY; + that.localPosition = Vec3.ZERO; + that.localDimensions = Vec3.ZERO; + that.localRegistrationPoint = Vec3.HALF; + + that.worldRotation = Quat.IDENTITY; + that.worldPosition = Vec3.ZERO; + that.worldDimensions = Vec3.ZERO; + that.worldRegistrationPoint = Vec3.HALF; + that.centerPosition = Vec3.ZERO; + + that.pointingAtDesktopWindowLeft = false; + that.pointingAtDesktopWindowRight = false; + that.pointingAtTabletLeft = false; + that.pointingAtTabletRight = false; + + that.saveProperties = function() { + that.savedProperties = {}; + for (var i = 0; i < that.selections.length; i++) { + var entityID = that.selections[i]; + that.savedProperties[entityID] = Entities.getEntityProperties(entityID); + } + }; + + that.addEventListener = function(func, thisContext) { + listeners.push({ + callback: func, + thisContext: thisContext + }); + }; + + that.hasSelection = function() { + return that.selections.length > 0; + }; + + that.setSelections = function(entityIDs, caller) { + that.selections = []; + for (var i = 0; i < entityIDs.length; i++) { + var entityID = entityIDs[i]; + that.selections.push(entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + + that._update(true, caller); + }; + + that.addEntity = function(entityID, toggleSelection, caller) { + if (entityID) { + var idx = -1; + for (var i = 0; i < that.selections.length; i++) { + if (entityID === that.selections[i]) { + idx = i; + break; + } + } + if (idx === -1) { + that.selections.push(entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } else if (toggleSelection) { + that.selections.splice(idx, 1); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + } + + that._update(true, caller); + }; + + function removeEntityByID(entityID) { + var idx = that.selections.indexOf(entityID); + if (idx >= 0) { + that.selections.splice(idx, 1); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + } + + that.removeEntity = function (entityID, caller) { + removeEntityByID(entityID); + that._update(true, caller); + }; + + that.removeEntities = function(entityIDs, caller) { + for (var i = 0, length = entityIDs.length; i < length; i++) { + removeEntityByID(entityIDs[i]); + } + that._update(true, caller); + }; + + that.clearSelections = function(caller) { + that.selections = []; + that._update(true, caller); + }; + + that.addChildrenEntities = function(parentEntityID, entityList, entityHostType) { + var wantDebug = false; + var children = Entities.getChildrenIDs(parentEntityID); + var entityHostTypes = Entities.getMultipleEntityProperties(children, 'entityHostType'); + for (var i = 0; i < children.length; i++) { + var childID = children[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping addition of entity " + childID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + if (entityList.indexOf(childID) < 0) { + entityList.push(childID); + } + that.addChildrenEntities(childID, entityList, entityHostType); + } + }; + + // Determine if an entity is being grabbed. + // This is mostly a heuristic - there is no perfect way to know if an entity is being + // grabbed. + // + // @return {boolean} true if the given entity with `properties` is being grabbed by an avatar + function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { + if (properties.dynamic || Uuid.isNull(properties.parentID)) { + return false; + } + + var avatar = AvatarList.getAvatar(properties.parentID); + if (Uuid.isNull(avatar.sessionUUID)) { + return false; + } + + var grabJointNames = [ + 'RightHand', 'LeftHand', + '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND', + '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND', + '_FARGRAB_RIGHTHAND', '_FARGRAB_LEFTHAND', '_FARGRAB_MOUSE' + ]; + + for (var i = 0; i < grabJointNames.length; ++i) { + if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) { + return true; + } + } + + return false; + } + + var entityClipboard = { + entities: {}, // Map of id -> properties for copied entities + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 0, y: 0, z: 0 }, + }; + + that.duplicateSelection = function() { + var entitiesToDuplicate = []; + var duplicatedEntityIDs = []; + var duplicatedChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + SelectionManager.saveProperties(); + + // build list of entities to duplicate by including any unselected children of selected parent entities + var originalEntityIDs = Object.keys(that.savedProperties); + var entityHostTypes = Entities.getMultipleEntityProperties(originalEntityIDs, 'entityHostType'); + for (var i = 0; i < originalEntityIDs.length; i++) { + var originalEntityID = originalEntityIDs[i]; + if (entitiesToDuplicate.indexOf(originalEntityID) === -1) { + entitiesToDuplicate.push(originalEntityID); + } + that.addChildrenEntities(originalEntityID, entitiesToDuplicate, entityHostTypes[i].entityHostType); + } + + // duplicate entities from above and store their original to new entity mappings and children needing re-parenting + for (var i = 0; i < entitiesToDuplicate.length; i++) { + var originalEntityID = entitiesToDuplicate[i]; + var properties = that.savedProperties[originalEntityID]; + if (properties === undefined) { + properties = Entities.getEntityProperties(originalEntityID); + } + if (!properties.locked && (!properties.avatarEntity || properties.owningAvatarID === MyAvatar.sessionUUID)) { + if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { + properties.parentID = null; + properties.parentJointIndex = null; + properties.localPosition = properties.position; + properties.localRotation = properties.rotation; + } + + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + // Re-apply actions from the original entity + var actionIDs = Entities.getActionIDs(properties.id); + for (var j = 0; j < actionIDs.length; ++j) { + var actionID = actionIDs[j]; + var actionArguments = Entities.getActionArguments(properties.id, actionID); + if (actionArguments) { + var type = actionArguments.type; + if (type === 'hold' || type === 'far-grab') { + continue; + } + delete actionArguments.ttl; + Entities.addAction(type, newEntityID, actionArguments); + } + } + + duplicatedEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + duplicatedChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[originalEntityID] = newEntityID; + } + } + + // re-parent duplicated children to the duplicate entities of their original parents (if they were duplicated) + Object.keys(duplicatedChildrenWithOldParents).forEach(function(childIDNeedingNewParent) { + var originalParentID = duplicatedChildrenWithOldParents[childIDNeedingNewParent]; + var newParentID = originalEntityToNewEntityID[originalParentID]; + if (newParentID) { + Entities.editEntity(childIDNeedingNewParent, { parentID: newParentID }); + for (var i = 0; i < duplicatedEntityIDs.length; i++) { + var duplicatedEntity = duplicatedEntityIDs[i]; + if (duplicatedEntity.entityID === childIDNeedingNewParent) { + duplicatedEntity.properties.parentID = newParentID; + } + } + } + }); + + return duplicatedEntityIDs; + }; + + // Create the entities in entityProperties, maintaining parent-child relationships. + // @param entityProperties {array} - Array of entity property objects + that.createEntities = function(entityProperties) { + var entitiesToCreate = []; + var createdEntityIDs = []; + var createdChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + that.saveProperties(); + + for (var i = 0; i < entityProperties.length; ++i) { + var properties = entityProperties[i]; + if (properties.parentID in originalEntityToNewEntityID) { + properties.parentID = originalEntityToNewEntityID[properties.parentID]; + } else { + delete properties.parentID; + } + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + if (newEntityID) { + createdEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + createdChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[properties.id] = newEntityID; + properties.id = newEntityID; + } + } + + return createdEntityIDs; + }; + + that.cutSelectedEntities = function() { + that.copySelectedEntities(); + deleteSelectedEntities(); + }; + + that.copySelectedEntities = function() { + var entityProperties = Entities.getMultipleEntityProperties(that.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(that.selections, 'entityHostType'); + var entities = {}; + entityProperties.forEach(function(props) { + entities[props.id] = props; + }); + + function appendChildren(entityID, entities, entityHostType) { + var wantDebug = false; + var childrenIDs = Entities.getChildrenIDs(entityID); + var entityHostTypes = Entities.getMultipleEntityProperties(childrenIDs, 'entityHostType'); + for (var i = 0; i < childrenIDs.length; ++i) { + var id = childrenIDs[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.warn("Skipping addition of entity " + id + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + if (!(id in entities)) { + entities[id] = Entities.getEntityProperties(id); + appendChildren(id, entities, entityHostType); + } + } + } + + var len = entityProperties.length; + for (var i = 0; i < len; ++i) { + appendChildren(entityProperties[i].id, entities, entityHostTypes[i].entityHostType); + } + + for (var id in entities) { + var parentID = entities[id].parentID; + entities[id].root = !(parentID in entities); + } + + entityClipboard.entities = []; + + var ids = Object.keys(entities); + while (ids.length > 0) { + // Go through all remaining entities. + // If an entity does not have a parent left, move it into the list + for (var i = 0; i < ids.length; ++i) { + var id = ids[i]; + var parentID = entities[id].parentID; + if (parentID in entities) { + continue; + } + entityClipboard.entities.push(entities[id]); + delete entities[id]; + } + ids = Object.keys(entities); + } + + // Calculate size + if (entityClipboard.entities.length === 0) { + entityClipboard.dimensions = { x: 0, y: 0, z: 0 }; + entityClipboard.position = { x: 0, y: 0, z: 0 }; + } else { + var properties = entityClipboard.entities; + var brn = properties[0].boundingBox.brn; + var tfl = properties[0].boundingBox.tfl; + for (var i = 1; i < properties.length; i++) { + var bb = properties[i].boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + entityClipboard.dimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + entityClipboard.position = { + x: brn.x + entityClipboard.dimensions.x / 2, + y: brn.y + entityClipboard.dimensions.y / 2, + z: brn.z + entityClipboard.dimensions.z / 2 + }; + } + }; + + that.pasteEntities = function() { + var dimensions = entityClipboard.dimensions; + var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + var pastePosition = getPositionToCreateEntity(maxDimension); + var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); + + var copiedProperties = []; + var ids = []; + entityClipboard.entities.forEach(function(originalProperties) { + var properties = deepCopy(originalProperties); + if (properties.root) { + properties.position = Vec3.sum(properties.position, deltaPosition); + delete properties.localPosition; + } else { + delete properties.position; + } + copiedProperties.push(properties); + }); + + var currentSelections = deepCopy(SelectionManager.selections); + + function redo(copiedProperties) { + var created = that.createEntities(copiedProperties); + var ids = []; + for (var i = 0; i < created.length; ++i) { + ids.push(created[i].entityID); + } + SelectionManager.setSelections(ids); + } + + function undo(copiedProperties) { + for (var i = 0; i < copiedProperties.length; ++i) { + Entities.deleteEntity(copiedProperties[i].id); + } + SelectionManager.setSelections(currentSelections); + } + + redo(copiedProperties); + undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); + }; + + that._update = function(selectionUpdated, caller) { + var properties = null; + if (that.selections.length === 0) { + that.localDimensions = null; + that.localPosition = null; + that.worldDimensions = null; + that.worldPosition = null; + that.worldRotation = null; + } else if (that.selections.length === 1) { + properties = Entities.getEntityProperties(that.selections[0], + ['dimensions', 'position', 'rotation', 'registrationPoint', 'boundingBox', 'type']); + that.localDimensions = properties.dimensions; + that.localPosition = properties.position; + that.localRotation = properties.rotation; + that.localRegistrationPoint = properties.registrationPoint; + + that.worldDimensions = properties.boundingBox.dimensions; + that.worldPosition = properties.boundingBox.center; + that.worldRotation = Quat.IDENTITY; + + that.entityType = properties.type; + + if (selectionUpdated) { + SelectionDisplay.useDesiredSpaceMode(); + } + } else { + properties = Entities.getEntityProperties(that.selections[0], ['type', 'boundingBox']); + + that.entityType = properties.type; + + var brn = properties.boundingBox.brn; + var tfl = properties.boundingBox.tfl; + + for (var i = 1; i < that.selections.length; i++) { + properties = Entities.getEntityProperties(that.selections[i], 'boundingBox'); + var bb = properties.boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + + that.localRotation = null; + that.localDimensions = null; + that.localPosition = null; + that.worldDimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + that.worldRotation = Quat.IDENTITY; + that.worldPosition = { + x: brn.x + (that.worldDimensions.x / 2), + y: brn.y + (that.worldDimensions.y / 2), + z: brn.z + (that.worldDimensions.z / 2) + }; + + // For 1+ selections we can only modify selections in world space + SelectionDisplay.setSpaceMode(SPACE_WORLD, false); + } + + for (var j = 0; j < listeners.length; j++) { + try { + listeners[j].callback.call(listeners[j].thisContext, selectionUpdated === true, caller); + } catch (e) { + print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); + } + } + }; + + return that; +})(); + +// Normalize degrees to be in the range (-180, 180) +function normalizeDegrees(degrees) { + var maxDegrees = 360; + var halfMaxDegrees = maxDegrees / 2; + degrees = ((degrees + halfMaxDegrees) % maxDegrees) - halfMaxDegrees; + if (degrees <= -halfMaxDegrees) { + degrees += maxDegrees; + } + return degrees; +} + +// SELECTION DISPLAY DEFINITION +SelectionDisplay = (function() { + var that = {}; + + const COLOR_GREEN = { red: 31, green: 198, blue: 166 }; + const COLOR_BLUE = { red: 0, green: 147, blue: 197 }; + const COLOR_RED = { red: 226, green: 51, blue: 77 }; + const COLOR_HOVER = { red: 227, green: 227, blue: 227 }; + const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; + const COLOR_BOUNDING_EDGE = { red: 87, green: 87, blue: 87 }; + const COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; + const COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; + const COLOR_DEBUG_PICK_PLANE = { red: 255, green: 255, blue: 255 }; + const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 }; + + const TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; + const TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; + const TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; + const TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; + const TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE = 0.83; + + const ROTATE_RING_CAMERA_DISTANCE_MULTIPLE = 0.15; + const ROTATE_CTRL_SNAP_ANGLE = 22.5; + const ROTATE_DEFAULT_SNAP_ANGLE = 1; + const ROTATE_DEFAULT_TICK_MARKS_ANGLE = 5; + const ROTATE_RING_IDLE_INNER_RADIUS = 0.92; + const ROTATE_RING_SELECTED_INNER_RADIUS = 0.9; + + // These are multipliers for sizing the rotation degrees display while rotating an entity + const ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 2; + const ROTATE_DISPLAY_SIZE_X_MULTIPLIER = 0.2; + const ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; + const ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; + + const STRETCH_CUBE_OFFSET = 0.06; + const STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; + const STRETCH_PANEL_WIDTH = 0.01; + + const SCALE_OVERLAY_CAMERA_DISTANCE_MULTIPLE = 0.02; + const SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE = 0.5; + + const BOUNDING_EDGE_OFFSET = 0.5; + + const DUPLICATOR_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; + + const CTRL_KEY_CODE = 16777249; + + const RAIL_AXIS_LENGTH = 10000; + + const NEGATE_VECTOR = -1; + const NO_HAND = -1; + + const DEBUG_PICK_PLANE_HIT_LIMIT = 200; + const DEBUG_PICK_PLANE_HIT_CAMERA_DISTANCE_MULTIPLE = 0.01; + + const TRANSLATE_DIRECTION = { + X: 0, + Y: 1, + Z: 2 + }; + + const STRETCH_DIRECTION = { + X: 0, + Y: 1, + Z: 2, + ALL: 3 + }; + + const ROTATE_DIRECTION = { + PITCH: 0, + YAW: 1, + ROLL: 2 + }; + + const INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status"; + + /** + * The current space mode, this could have been a forced space mode since we do not support multi selection while in + * local space mode. + * @type {string} - should only be set to SPACE_LOCAL or SPACE_WORLD + */ + var spaceMode = SPACE_LOCAL; + + /** + * The desired space mode, this is the user set space mode, which should be respected whenever it is possible. In the case + * of multi entity selection this space mode may differ from the actual spaceMode. + * @type {string} - should only be set to SPACE_LOCAL or SPACE_WORLD + */ + var desiredSpaceMode = SPACE_LOCAL; + + var overlayNames = []; + var lastControllerPoses = [ + getControllerWorldLocation(Controller.Standard.LeftHand, true), + getControllerWorldLocation(Controller.Standard.RightHand, true) + ]; + + var worldRotationX; + var worldRotationY; + var worldRotationZ; + + var activeStretchCubePanelOffset = null; + + var previousHandle = null; + var previousHandleHelper = null; + var previousHandleColor; + + var ctrlPressed = false; + + that.replaceCollisionsAfterStretch = false; + + var handlePropertiesTranslateArrowCones = { + alpha: 1, + shape: "Cone", + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handlePropertiesTranslateArrowCylinders = { + alpha: 1, + shape: "Cylinder", + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateXCone, { color: COLOR_RED }); + Overlays.editOverlay(handleTranslateXCylinder, { color: COLOR_RED }); + var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateYCone, { color: COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCylinder, { color: COLOR_GREEN }); + var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateZCone, { color: COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCylinder, { color: COLOR_BLUE }); + + var handlePropertiesRotateRings = { + alpha: 1, + solid: true, + startAt: 0, + endAt: 360, + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE, + majorTickMarksLength: 0.1, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotatePitchRing, { + color: COLOR_RED, + majorTickMarksColor: COLOR_RED + }); + var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateYawRing, { + color: COLOR_GREEN, + majorTickMarksColor: COLOR_GREEN + }); + var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateRollRing, { + color: COLOR_BLUE, + majorTickMarksColor: COLOR_BLUE + }); + + var handleRotateCurrentRing = Overlays.addOverlay("circle3d", { + alpha: 1, + color: COLOR_ROTATE_CURRENT_RING, + solid: true, + innerRadius: 0.9, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }); + + var rotationDegreesDisplay = Overlays.addOverlay("text3d", { + text: "", + color: { red: 0, green: 0, blue: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + alpha: 0.7, + backgroundAlpha: 0.7, + visible: false, + isFacingAvatar: true, + drawInFront: true, + ignorePickIntersection: true, + dimensions: { x: 0, y: 0 }, + lineHeight: 0.0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + leftMargin: 0 + }); + + var handlePropertiesStretchCubes = { + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchXCube, { color: COLOR_RED }); + var handleStretchYCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchYCube, { color: COLOR_GREEN }); + var handleStretchZCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); + + var handlePropertiesStretchPanel = { + alpha: 0.5, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleStretchXPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchXPanel, { color: COLOR_RED }); + var handleStretchYPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchYPanel, { color: COLOR_GREEN }); + var handleStretchZPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); + + var handleScaleCube = Overlays.addOverlay("cube", { + size: 0.025, + color: COLOR_SCALE_CUBE, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + borderSize: 1.4 + }); + + var handleBoundingBox = Overlays.addOverlay("cube", { + alpha: 1, + color: COLOR_BOUNDING_EDGE, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + isSolid: false + }); + + var handleDuplicator = Overlays.addOverlay("cube", { + alpha: 1, + size: 0.05, + color: COLOR_GREEN, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + borderSize: 1.4 + }); + + // setting to 0 alpha for now to keep this hidden vs using visible false + // because its used as the translate xz tool handle overlay + var selectionBox = Overlays.addOverlay("cube", { + size: 1, + color: COLOR_RED, + alpha: 0, + solid: false, + visible: false, + ignorePickIntersection: true, + dashed: false + }); + + // Handle for x-z translation of particle effect and light entities while inside the bounding box. + // Limitation: If multiple entities are selected, only the first entity's icon translates the selection. + var iconSelectionBox = Overlays.addOverlay("cube", { + size: 0.3, // Match entity icon size. + color: COLOR_RED, + alpha: 0, + solid: false, + visible: false, + ignorePickIntersection: true, + dashed: false + }); + + var xRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignorePickIntersection: true // always ignore this + }); + var yRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignorePickIntersection: true // always ignore this + }); + var zRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignorePickIntersection: true // always ignore this + }); + + var allOverlays = [ + handleTranslateXCone, + handleTranslateXCylinder, + handleTranslateYCone, + handleTranslateYCylinder, + handleTranslateZCone, + handleTranslateZCylinder, + handleRotatePitchRing, + handleRotateYawRing, + handleRotateRollRing, + handleRotateCurrentRing, + rotationDegreesDisplay, + handleStretchXCube, + handleStretchYCube, + handleStretchZCube, + handleStretchXPanel, + handleStretchYPanel, + handleStretchZPanel, + handleScaleCube, + handleBoundingBox, + handleDuplicator, + selectionBox, + iconSelectionBox, + xRailOverlay, + yRailOverlay, + zRailOverlay + ]; + + const nonLayeredOverlays = [selectionBox, iconSelectionBox]; + + var maximumHandleInAllOverlays = handleDuplicator; + + overlayNames[handleTranslateXCone] = "handleTranslateXCone"; + overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder"; + overlayNames[handleTranslateYCone] = "handleTranslateYCone"; + overlayNames[handleTranslateYCylinder] = "handleTranslateYCylinder"; + overlayNames[handleTranslateZCone] = "handleTranslateZCone"; + overlayNames[handleTranslateZCylinder] = "handleTranslateZCylinder"; + + overlayNames[handleRotatePitchRing] = "handleRotatePitchRing"; + overlayNames[handleRotateYawRing] = "handleRotateYawRing"; + overlayNames[handleRotateRollRing] = "handleRotateRollRing"; + overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; + overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; + + overlayNames[handleStretchXCube] = "handleStretchXCube"; + overlayNames[handleStretchYCube] = "handleStretchYCube"; + overlayNames[handleStretchZCube] = "handleStretchZCube"; + overlayNames[handleStretchXPanel] = "handleStretchXPanel"; + overlayNames[handleStretchYPanel] = "handleStretchYPanel"; + overlayNames[handleStretchZPanel] = "handleStretchZPanel"; + + overlayNames[handleScaleCube] = "handleScaleCube"; + + overlayNames[handleBoundingBox] = "handleBoundingBox"; + + overlayNames[handleDuplicator] = "handleDuplicator"; + overlayNames[selectionBox] = "selectionBox"; + overlayNames[iconSelectionBox] = "iconSelectionBox"; + + var activeTool = null; + var handleTools = {}; + + var debugPickPlaneEnabled = false; + var debugPickPlane = Overlays.addOverlay("shape", { + shape: "Quad", + alpha: 0.25, + color: COLOR_DEBUG_PICK_PLANE, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: false + }); + var debugPickPlaneHits = []; + + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we dont' get mousePressEvents. + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; + that.editingHand = NO_HAND; + that.triggered = function() { + return that.triggeredHand !== NO_HAND; + }; + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; + } + function makeClickHandler(hand) { + return function (clicked) { + // Don't allow both hands to trigger at the same time + if (that.triggered() && hand !== that.triggeredHand) { + return; + } + if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { + that.triggeredHand = hand; + that.mousePressEvent({}); + } else if (that.triggered() && !clicked) { + that.triggeredHand = NO_HAND; + that.mouseReleaseEvent({}); + } + }; + } + function makePressHandler(hand) { + return function (value) { + if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + that.updateHighlight({}); + } else { + that.pressedHand = NO_HAND; + that.resetPreviousHandleColor(); + } + } + } + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + }; + Script.scriptEnding.connect(that.disableTriggerMapping); + + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { + var wantDebug = false; + if ((queryRay === undefined) || (queryRay === null)) { + if (wantDebug) { + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + // We want to first check the drawInFront overlays (i.e. the handles, but really everything except the selectionBoxes) + // so that you can click on them even when they're behind things + var overlayIncludesLayered = []; + var overlayIncludesNonLayered = []; + for (var i = 0; i < overlayIncludes.length; i++) { + var value = overlayIncludes[i]; + var contains = false; + for (var j = 0; j < nonLayeredOverlays.length; j++) { + if (nonLayeredOverlays[j] === value) { + contains = true; + break; + } + } + if (contains) { + overlayIncludesNonLayered.push(value); + } else { + overlayIncludesLayered.push(value); + } + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesLayered, overlayExcludes); + + if (!intersectObj.intersects && overlayIncludesNonLayered.length > 0) { + intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesNonLayered, overlayExcludes); + } + + if (wantDebug) { + if (!overlayIncludes) { + print("testRayIntersect - no overlayIncludes provided."); + } + if (!overlayExcludes) { + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + function isPointInsideBox(point, box) { + var position = Vec3.subtract(point, box.position); + position = Vec3.multiplyQbyV(Quat.inverse(box.rotation), position); + return Math.abs(position.x) <= box.dimensions.x / 2 && Math.abs(position.y) <= box.dimensions.y / 2 + && Math.abs(position.z) <= box.dimensions.z / 2; + } + + that.isEditHandle = function(overlayID) { + var overlayIndex = allOverlays.indexOf(overlayID); + var maxHandleIndex = allOverlays.indexOf(maximumHandleInAllOverlays); + return overlayIndex >= 0 && overlayIndex <= maxHandleIndex; + }; + + // FUNCTION: MOUSE PRESS EVENT + that.mousePressEvent = function (event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); + } + if (!event.isLeftButton && !that.triggered()) { + // EARLY EXIT-(if another mouse button than left is pressed ignore it) + return false; + } + + var pickRay = generalComputePickRay(event.x, event.y); + // TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = getMainTabletIDs(); + for (var key in handleTools) { + if (handleTools.hasOwnProperty(key)) { + interactiveOverlays.push(key); + } + } + + // Start with unknown mode, in case no tool can handle this. + activeTool = null; + + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { + var hitOverlayID = results.overlayID; + if ((HMD.tabletID && hitOverlayID === HMD.tabletID) || (HMD.tabletScreenID && hitOverlayID === HMD.tabletScreenID) + || (HMD.homeButtonID && hitOverlayID === HMD.homeButtonID)) { + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; + } + + var hitTool = handleTools[ hitOverlayID ]; + if (hitTool) { + activeTool = hitTool; + that.clearDebugPickPlane(); + if (activeTool.onBegin) { + that.editingHand = that.triggeredHand; + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: true + })); + activeTool.onBegin(event, pickRay, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); + } + } else { + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }// End_if (hitTool) + }// End_If(results.intersects) + + if (wantDebug) { + print(" DisplayMode: " + getMode()); + print("=============== eST::MousePressEvent END ======================="); + } + + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool. + return activeTool; + }; + + that.resetPreviousHandleColor = function() { + if (previousHandle !== null) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor }); + previousHandle = null; + } + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); + previousHandleHelper = null; + } + }; + + that.getHandleHelper = function(overlay) { + if (overlay === handleTranslateXCone) { + return handleTranslateXCylinder; + } else if (overlay === handleTranslateXCylinder) { + return handleTranslateXCone; + } else if (overlay === handleTranslateYCone) { + return handleTranslateYCylinder; + } else if (overlay === handleTranslateYCylinder) { + return handleTranslateYCone; + } else if (overlay === handleTranslateZCone) { + return handleTranslateZCylinder; + } else if (overlay === handleTranslateZCylinder) { + return handleTranslateZCone; + } + return Uuid.NULL; + }; + + that.updateHighlight = function(event) { + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = testRayIntersect(pickRay, allOverlays); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case handleTranslateXCone: + case handleTranslateXCylinder: + case handleRotatePitchRing: + case handleStretchXCube: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case handleTranslateYCone: + case handleTranslateYCylinder: + case handleRotateYawRing: + case handleStretchYCube: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case handleTranslateZCone: + case handleTranslateZCylinder: + case handleRotateRollRing: + case handleStretchZCube: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case handleScaleCube: + pickedColor = COLOR_SCALE_CUBE; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + }; + + // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; + that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } + lastMouseEvent = event; + if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } + activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } + SelectionManager._update(false, that); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) + return true; + } + + that.updateHighlight(event); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + return false; + }; + + // FUNCTION: MOUSE RELEASE EVENT + that.mouseReleaseEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseReleaseEvent BEG ======================="); + } + var showHandles = false; + if (activeTool) { + if (activeTool.onEnd) { + if (wantDebug) { + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); + } + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: false + })); + that.editingHand = NO_HAND; + activeTool.onEnd(event); + } else if (wantDebug) { + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); + } + } + + showHandles = activeTool; // base on prior tool value + activeTool = null; + + // if something is selected, then reset the "original" properties for any potential next click+move operation + if (SelectionManager.hasSelection()) { + if (showHandles) { + if (wantDebug) { + print(" Triggering that.select"); + } + that.select(SelectionManager.selections[0], event); + } + } + + if (wantDebug) { + print("=============== eST::MouseReleaseEvent END ======================="); + } + }; + + // Control key remains active only while key is held down + that.keyReleaseEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { + ctrlPressed = false; + that.updateActiveRotateRing(); + } + that.updateLastMouseEvent(event); + }; + + // Triggers notification on specific key driven events + that.keyPressEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { + ctrlPressed = true; + that.updateActiveRotateRing(); + } + that.updateLastMouseEvent(event); + }; + + that.updateLastMouseEvent = function(event) { + if (activeTool && lastMouseEvent !== null) { + var change = lastMouseEvent.isShifted !== event.isShifted || lastMouseEvent.isMeta !== event.isMeta || + lastMouseEvent.isControl !== event.isControl || lastMouseEvent.isAlt !== event.isAlt; + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + if (change) { + activeTool.onMove(lastMouseEvent); + } + } + }; + + // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: + // Controller.mousePressEvent.connect(that.mousePressEvent); + // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); + Controller.keyPressEvent.connect(that.keyPressEvent); + Controller.keyReleaseEvent.connect(that.keyReleaseEvent); + + that.checkControllerMove = function() { + if (SelectionManager.hasSelection()) { + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var hand = (that.triggeredHand === Controller.Standard.LeftHand) ? 0 : 1; + if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered()) { + if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || + !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { + that.mouseMoveEvent({}); + } + } + lastControllerPoses[hand] = controllerPose; + } + }; + + function controllerComputePickRay() { + var hand = that.triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + var controllerPosition = controllerPose.translation; + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(controllerPose.rotation); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } + + function getControllerAvatarFramePositionFromPickRay(pickRay) { + var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position); + controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition); + return controllerPosition; + } + + function getDistanceToCamera(position) { + var cameraPosition = Camera.getPosition(); + var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); + return toCameraDistance; + } + + function usePreviousPickRay(pickRayDirection, previousPickRayDirection, normal) { + return (Vec3.dot(pickRayDirection, normal) > 0 && Vec3.dot(previousPickRayDirection, normal) < 0) || + (Vec3.dot(pickRayDirection, normal) < 0 && Vec3.dot(previousPickRayDirection, normal) > 0); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); + } + + that.cleanup = function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + that.clearDebugPickPlane(); + }; + + that.select = function(entityID, event) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + + if (event !== false) { + var wantDebug = false; + if (wantDebug) { + print("select() with EVENT...... "); + print(" event.y:" + event.y); + Vec3.print(" current position:", properties.position); + } + } + + that.updateHandles(); + }; + + + /** + * This callback is used for spaceMode changes. + * @callback spaceModeChangedCallback + * @param {string} spaceMode + */ + + /** + * set this property with a callback to keep track of spaceMode changes. + * @type {spaceModeChangedCallback} + */ + that.onSpaceModeChange = null; + + // FUNCTION: SET SPACE MODE + that.setSpaceMode = function(newSpaceMode, isDesiredChange) { + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } + if (isDesiredChange) { + desiredSpaceMode = newSpaceMode; + } + spaceMode = newSpaceMode; + + if (that.onSpaceModeChange !== null) { + that.onSpaceModeChange(newSpaceMode); + } + + that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + + spaceMode + " DesiredMode: " + newSpaceMode); + } + if (wantDebug) { + print("====== SetSpaceMode called. <========"); + } + }; + + // FUNCTION: TOGGLE SPACE MODE + that.toggleSpaceMode = function() { + var wantDebug = false; + if (wantDebug) { + print("========> ToggleSpaceMode called. ========="); + } + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { + if (wantDebug) { + print("Local space editing is not available with multiple selections"); + } + return; + } + if (wantDebug) { + print("PreToggle: " + spaceMode); + } + that.setSpaceMode((spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL, true); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } + }; + + /** + * Switches the display mode back to the set desired display mode + */ + that.useDesiredSpaceMode = function() { + var wantDebug = false; + if (wantDebug) { + print("========> UseDesiredSpaceMode called. ========="); + } + that.setSpaceMode(desiredSpaceMode, false); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== UseDesiredSpaceMode called. <========="); + } + }; + + /** + * Get the currently set SpaceMode + * @returns {string} spaceMode + */ + that.getSpaceMode = function() { + return spaceMode; + }; + + function addHandleTool(overlay, tool) { + handleTools[overlay] = tool; + return tool; + } + + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!handleTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + + toolHandle + ". Tools should be registered via addHandleTool."); + // EARLY EXIT + return false; + } + + return (activeTool === handleTools[ toolHandle ]); + } + + // FUNCTION: UPDATE HANDLES + that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } + + if (SelectionManager.selections.length === 0) { + that.setOverlaysVisible(false); + that.clearDebugPickPlane(); + return; + } + + if (SelectionManager.hasSelection()) { + var position = SelectionManager.worldPosition; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions; + var rotationInverse = Quat.inverse(rotation); + var toCameraDistance = getDistanceToCamera(position); + + var rotationDegrees = 90; + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -rotationDegrees); + var rotationX = Quat.multiply(rotation, localRotationX); + worldRotationX = rotationX; + var localRotationY = Quat.fromPitchYawRollDegrees(0, rotationDegrees, 0); + var rotationY = Quat.multiply(rotation, localRotationY); + worldRotationY = rotationY; + var localRotationZ = Quat.fromPitchYawRollDegrees(rotationDegrees, 0, 0); + var rotationZ = Quat.multiply(rotation, localRotationZ); + worldRotationZ = rotationZ; + + var selectionBoxGeometry = { + position: position, + rotation: rotation, + dimensions: dimensions + }; + var isCameraInsideBox = isPointInsideBox(Camera.position, selectionBoxGeometry); + + // in HMD if outside the bounding box clamp the overlays to the bounding box for now so lasers can hit them + var maxHandleDimension = 0; + if (HMD.active && !isCameraInsideBox) { + maxHandleDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + } + + // UPDATE ROTATION RINGS + // rotateDimension is used as the base dimension for all overlays + var rotateDimension = Math.max(maxHandleDimension, toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE); + var rotateDimensions = { x: rotateDimension, y: rotateDimension, z: rotateDimension }; + if (!isActiveTool(handleRotatePitchRing)) { + Overlays.editOverlay(handleRotatePitchRing, { + position: position, + rotation: rotationY, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateYawRing)) { + Overlays.editOverlay(handleRotateYawRing, { + position: position, + rotation: rotationZ, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateRollRing)) { + Overlays.editOverlay(handleRotateRollRing, { + position: position, + rotation: rotationX, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + Overlays.editOverlay(handleRotateCurrentRing, { dimensions: rotateDimensions }); + that.updateActiveRotateRing(); + + // UPDATE TRANSLATION ARROWS + var arrowCylinderDimension = rotateDimension * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowCylinderDimensions = { + x: arrowCylinderDimension, + y: arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, + z: arrowCylinderDimension + }; + var arrowConeDimension = rotateDimension * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowConeDimensions = { x: arrowConeDimension, y: arrowConeDimension, z: arrowConeDimension }; + var arrowCylinderOffset = rotateDimension * TRANSLATE_ARROW_CYLINDER_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE; + var cylinderXPosition = { x: arrowCylinderOffset, y: 0, z: 0 }; + cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); + Overlays.editOverlay(handleTranslateXCylinder, { + position: cylinderXPosition, + rotation: rotationX, + dimensions: arrowCylinderDimensions + }); + var cylinderXOffset = Vec3.subtract(cylinderXPosition, position); + var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateXCone, { + position: coneXPosition, + rotation: rotationX, + dimensions: arrowConeDimensions + }); + var cylinderYPosition = { x: 0, y: arrowCylinderOffset, z: 0 }; + cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition)); + Overlays.editOverlay(handleTranslateYCylinder, { + position: cylinderYPosition, + rotation: rotationY, + dimensions: arrowCylinderDimensions + }); + var cylinderYOffset = Vec3.subtract(cylinderYPosition, position); + var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateYCone, { + position: coneYPosition, + rotation: rotationY, + dimensions: arrowConeDimensions + }); + var cylinderZPosition = { x: 0, y: 0, z: arrowCylinderOffset }; + cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition)); + Overlays.editOverlay(handleTranslateZCylinder, { + position: cylinderZPosition, + rotation: rotationZ, + dimensions: arrowCylinderDimensions + }); + var cylinderZOffset = Vec3.subtract(cylinderZPosition, position); + var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateZCone, { + position: coneZPosition, + rotation: rotationZ, + dimensions: arrowConeDimensions + }); + + // UPDATE SCALE CUBE + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_OVERLAY_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleCube, { + position: position, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + + // UPDATE BOUNDING BOX + Overlays.editOverlay(handleBoundingBox, { + position: position, + rotation: rotation, + dimensions: dimensions + }); + + // UPDATE STRETCH HIGHLIGHT PANELS + var edgeOffsetX = BOUNDING_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = BOUNDING_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = BOUNDING_EDGE_OFFSET * dimensions.z; + var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); + var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); + var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); + + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); + var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); + var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); + var RTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTNPosition); + var stretchPanelXDimensions = Vec3.subtract(RTNPositionRotated, RBFPositionRotated); + var tempY = Math.abs(stretchPanelXDimensions.y); + stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; + stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); + stretchPanelXDimensions.z = tempY; + var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: dimensions.x / 2, y: 0, z: 0 })); + Overlays.editOverlay(handleStretchXPanel, { + position: stretchPanelXPosition, + rotation: rotationZ, + dimensions: stretchPanelXDimensions + }); + var stretchPanelYDimensions = Vec3.subtract(LTNPositionRotated, RTFPositionRotated); + var tempX = Math.abs(stretchPanelYDimensions.x); + stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); + stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; + stretchPanelYDimensions.z = tempX; + var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: dimensions.y / 2, z: 0 })); + Overlays.editOverlay(handleStretchYPanel, { + position: stretchPanelYPosition, + rotation: rotationY, + dimensions: stretchPanelYDimensions + }); + var stretchPanelZDimensions = Vec3.subtract(LTNPositionRotated, RBFPositionRotated); + tempX = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); + stretchPanelZDimensions.y = tempX; + stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH; + var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: dimensions.z / 2 })); + Overlays.editOverlay(handleStretchZPanel, { + position: stretchPanelZPosition, + rotation: rotationX, + dimensions: stretchPanelZDimensions + }); + + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition, stretchYPosition, stretchZPosition; + if (isActiveTool(handleStretchXCube)) { + stretchXPosition = Vec3.subtract(stretchPanelXPosition, activeStretchCubePanelOffset); + } else { + stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + } + if (isActiveTool(handleStretchYCube)) { + stretchYPosition = Vec3.subtract(stretchPanelYPosition, activeStretchCubePanelOffset); + } else { + stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + } + if (isActiveTool(handleStretchZCube)) { + stretchZPosition = Vec3.subtract(stretchPanelZPosition, activeStretchCubePanelOffset); + } else { + stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + } + Overlays.editOverlay(handleStretchXCube, { + position: stretchXPosition, + rotation: rotationX, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchYCube, { + position: stretchYPosition, + rotation: rotationY, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions + }); + + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) + var inModeRotate = isActiveTool(handleRotatePitchRing) || + isActiveTool(handleRotateYawRing) || + isActiveTool(handleRotateRollRing); + selectionBoxGeometry.visible = !inModeRotate && !isCameraInsideBox; + selectionBoxGeometry.ignorePickIntersection = !selectionBoxGeometry.visible; + Overlays.editOverlay(selectionBox, selectionBoxGeometry); + + // UPDATE ICON TRANSLATE HANDLE + if (SelectionManager.entityType === "ParticleEffect" || SelectionManager.entityType === "Light") { + var iconSelectionBoxGeometry = { + position: position, + rotation: rotation + }; + iconSelectionBoxGeometry.visible = !inModeRotate && isCameraInsideBox; + iconSelectionBoxGeometry.ignorePickIntersection = !iconSelectionBoxGeometry.visible; + Overlays.editOverlay(iconSelectionBox, iconSelectionBoxGeometry); + } else { + Overlays.editOverlay(iconSelectionBox, { + visible: false, + ignorePickIntersection: true + }); + } + + // UPDATE DUPLICATOR (CURRENTLY HIDDEN FOR NOW) + var handleDuplicatorOffset = { + x: DUPLICATOR_OFFSET.x * dimensions.x, + y: DUPLICATOR_OFFSET.y * dimensions.y, + z: DUPLICATOR_OFFSET.z * dimensions.z + }; + var handleDuplicatorPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleDuplicatorOffset)); + Overlays.editOverlay(handleDuplicator, { + position: handleDuplicatorPos, + rotation: rotation, + dimensions: scaleCubeDimensions + }); + } + + that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) || + isActiveTool(handleTranslateXCylinder)); + that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) || + isActiveTool(handleTranslateYCylinder)); + that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) || + isActiveTool(handleTranslateZCylinder)); + that.setHandleRotatePitchVisible(!activeTool || isActiveTool(handleRotatePitchRing)); + that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing)); + that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); + + var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXCube)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYCube)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZCube)); + that.setHandleScaleVisible(showScaleStretch || isActiveTool(handleScaleCube)); + + var showOutlineForZone = (SelectionManager.selections.length === 1 && + typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && + SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); + that.setHandleBoundingBoxVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + !isActiveTool(handleRotateYawRing) && + !isActiveTool(handleRotateRollRing))); + + // keep duplicator always hidden for now since you can hold Alt to duplicate while + // translating an entity - we may bring duplicator back for HMD only later + // that.setHandleDuplicatorVisible(!activeTool || isActiveTool(handleDuplicator)); + + if (wantDebug) { + print("====== Update Handles <======="); + } + }; + Script.update.connect(that.updateHandles); + + // FUNCTION: UPDATE ACTIVE ROTATE RING + that.updateActiveRotateRing = function() { + var activeRotateRing = null; + if (isActiveTool(handleRotatePitchRing)) { + activeRotateRing = handleRotatePitchRing; + } else if (isActiveTool(handleRotateYawRing)) { + activeRotateRing = handleRotateYawRing; + } else if (isActiveTool(handleRotateRollRing)) { + activeRotateRing = handleRotateRollRing; + } + if (activeRotateRing !== null) { + var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; + Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); + } + }; + + // FUNCTION: SET OVERLAYS VISIBLE + that.setOverlaysVisible = function(isVisible) { + for (var i = 0, length = allOverlays.length; i < length; i++) { + Overlays.editOverlay(allOverlays[i], { visible: isVisible, ignorePickIntersection: !isVisible }); + } + }; + + // FUNCTION: SET HANDLE TRANSLATE VISIBLE + that.setHandleTranslateVisible = function(isVisible) { + that.setHandleTranslateXVisible(isVisible); + that.setHandleTranslateYVisible(isVisible); + that.setHandleTranslateZVisible(isVisible); + }; + + that.setHandleTranslateXVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateXCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleTranslateYVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateYCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleTranslateZVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateZCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE ROTATE VISIBLE + that.setHandleRotateVisible = function(isVisible) { + that.setHandleRotatePitchVisible(isVisible); + that.setHandleRotateYawVisible(isVisible); + that.setHandleRotateRollVisible(isVisible); + }; + + that.setHandleRotatePitchVisible = function(isVisible) { + Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleRotateYawVisible = function(isVisible) { + Overlays.editOverlay(handleRotateYawRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleRotateRollVisible = function(isVisible) { + Overlays.editOverlay(handleRotateRollRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE STRETCH VISIBLE + that.setHandleStretchVisible = function(isVisible) { + that.setHandleStretchXVisible(isVisible); + that.setHandleStretchYVisible(isVisible); + that.setHandleStretchZVisible(isVisible); + }; + + that.setHandleStretchXVisible = function(isVisible) { + Overlays.editOverlay(handleStretchXCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleStretchYVisible = function(isVisible) { + Overlays.editOverlay(handleStretchYCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleStretchZVisible = function(isVisible) { + Overlays.editOverlay(handleStretchZCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE SCALE VISIBLE + that.setHandleScaleVisible = function(isVisible) { + that.setHandleScaleVisible(isVisible); + that.setHandleBoundingBoxVisible(isVisible); + }; + + that.setHandleScaleVisible = function(isVisible) { + Overlays.editOverlay(handleScaleCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleBoundingBoxVisible = function(isVisible) { + Overlays.editOverlay(handleBoundingBox, { visible: isVisible, ignorePickIntersection: true }); + }; + + // FUNCTION: SET HANDLE DUPLICATOR VISIBLE + that.setHandleDuplicatorVisible = function(isVisible) { + Overlays.editOverlay(handleDuplicator, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: DEBUG PICK PLANE + that.showDebugPickPlane = function(pickPlanePosition, pickPlaneNormal) { + var planePlusNormal = Vec3.sum(pickPlanePosition, pickPlaneNormal); + var rotation = Quat.lookAtSimple(planePlusNormal, pickPlanePosition); + var dimensionXZ = getDistanceToCamera(pickPlanePosition) * 1.25; + var dimensions = { x:dimensionXZ, y:dimensionXZ, z:STRETCH_PANEL_WIDTH }; + Overlays.editOverlay(debugPickPlane, { + position: pickPlanePosition, + rotation: rotation, + dimensions: dimensions, + visible: true + }); + }; + + that.showDebugPickPlaneHit = function(pickHitPosition) { + var dimension = getDistanceToCamera(pickHitPosition) * DEBUG_PICK_PLANE_HIT_CAMERA_DISTANCE_MULTIPLE; + var pickPlaneHit = Overlays.addOverlay("shape", { + alpha: 0.5, + shape: "Sphere", + solid: true, + visible: true, + ignorePickIntersection: true, + drawInFront: false, + color: COLOR_DEBUG_PICK_PLANE_HIT, + position: pickHitPosition, + dimensions: { x: dimension, y: dimension, z: dimension } + }); + debugPickPlaneHits.push(pickPlaneHit); + if (debugPickPlaneHits.length > DEBUG_PICK_PLANE_HIT_LIMIT) { + var removedPickPlaneHit = debugPickPlaneHits.shift(); + Overlays.deleteOverlay(removedPickPlaneHit); + } + }; + + that.clearDebugPickPlane = function() { + Overlays.editOverlay(debugPickPlane, { visible: false }); + for (var i = 0; i < debugPickPlaneHits.length; i++) { + Overlays.deleteOverlay(debugPickPlaneHits[i]); + } + debugPickPlaneHits = []; + }; + + // TOOL DEFINITION: HANDLE TRANSLATE XZ TOOL + function addHandleTranslateXZTool(overlay, mode, doDuplicate) { + var initialPick = null; + var isConstrained = false; + var constrainMajorOnly = false; + var startPosition = null; + var duplicatedEntityIDs = null; + var pickPlanePosition = null; + var pickPlaneNormal = { x: 0, y: 1, z: 0 }; + var greatestDimension = 0.0; + var startingDistance = 0.0; + var startingElevation = 0.0; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt || doDuplicate) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(false); + that.setHandleStretchVisible(false); + that.setHandleDuplicatorVisible(false); + + startPosition = SelectionManager.worldPosition; + pickPlanePosition = pickResult.intersection; + greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, + SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.z); + startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + startingElevation = this.elevation(pickRay.origin, pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + greatestDimension); + print(" starting distance: " + startingDistance); + print(" starting elevation: " + startingElevation); + } + + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + + isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + } + }, + elevation: function(origin, intersection) { + return (origin.y - intersection.y) / Vec3.distance(origin, intersection); + }, + onMove: function(event) { + var wantDebug = false; + var pickRay = generalComputePickRay(event.x, event.y); + + var newPick = 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. + if (!newPick) { + if (wantDebug) { + print(" "+ mode + "Pick ray does not intersect XZ plane."); + } + + // EARLY EXIT--(Invalid ray detected.) + return; + } + + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var vector = Vec3.subtract(newPick, initialPick); + + // If the mouse is too close to the horizon of the pick plane, stop moving + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var elevation = this.elevation(pickRay.origin, newPick); + if (wantDebug) { + print("Start Elevation: " + startingElevation + ", elevation: " + elevation); + } + if ((startingElevation > 0.0 && elevation < MIN_ELEVATION) || + (startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { + if (wantDebug) { + print(" "+ mode + " - too close to horizon!"); + } + + // EARLY EXIT--(Don't proceed past the reached limit.) + return; + } + + // If the angular size of the object is too small, stop moving + var MIN_ANGULAR_SIZE = 0.01; // Radians + if (greatestDimension > 0) { + var angularSize = Math.atan(greatestDimension / Vec3.distance(pickRay.origin, newPick)); + if (wantDebug) { + print("Angular size = " + angularSize); + } + if (angularSize < MIN_ANGULAR_SIZE) { + return; + } + } + + // If shifted, constrain to one axis + if (event.isShifted) { + if (Math.abs(vector.x) > Math.abs(vector.z)) { + vector.z = 0; + } else { + vector.x = 0; + } + if (!isConstrained) { + var xStart = Vec3.sum(startPosition, { + x: -RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -RAIL_AXIS_LENGTH + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: RAIL_AXIS_LENGTH + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true, + ignorePickIntersection: true + }); + isConstrained = true; + } + } else { + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + isConstrained = false; + } + } + + constrainMajorOnly = event.isControl; + var negateAndHalve = -0.5; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); + vector = Vec3.subtract( + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var properties = SelectionManager.savedProperties[toMove[i]]; + if (!properties) { + continue; + } + var newPosition = Vec3.sum(properties.position, { + x: vector.x, + y: 0, + z: vector.z + }); + Entities.editEntity(toMove[i], { + position: newPosition + }); + + if (wantDebug) { + print("translateXZ... "); + Vec3.print(" vector:", vector); + Vec3.print(" newPosition:", properties.position); + Vec3.print(" newPosition:", newPosition); + } + } + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE TRANSLATE TOOL + function addHandleTranslateTool(overlay, mode, direction) { + var pickPlanePosition = null; + var pickPlaneNormal = null; + var initialPick = null; + var projectionVector = null; + var previousPickRay = null; + var rotation = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + + var axisVector; + if (direction === TRANSLATE_DIRECTION.X) { + axisVector = { x: 1, y: 0, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + axisVector = { x: 0, y: 1, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + axisVector = { x: 0, y: 0, z: 1 }; + } + + rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + axisVector = Vec3.multiplyQbyV(rotation, axisVector); + pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); + pickPlanePosition = SelectionManager.worldPosition; + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateXVisible(direction === TRANSLATE_DIRECTION.X); + that.setHandleTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); + that.setHandleTranslateZVisible(direction === TRANSLATE_DIRECTION.Z); + that.setHandleRotateVisible(false); + that.setHandleStretchVisible(false); + that.setHandleScaleVisible(false); + that.setHandleDuplicatorVisible(false); + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var vector = Vec3.subtract(newPick, initialPick); + + if (direction === TRANSLATE_DIRECTION.X) { + projectionVector = { x: 1, y: 0, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + projectionVector = { x: 0, y: 1, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + projectionVector = { x: 0, y: 0, z: 1 }; + } + projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); + + 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 wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + } + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var id = toMove[i]; + var properties = SelectionManager.savedProperties[id]; + var newPosition = Vec3.sum(properties.position, vector); + Entities.editEntity(id, { position: newPosition }); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE STRETCH TOOL + function addHandleStretchTool(overlay, mode, directionEnum) { + var initialPick = null; + var initialPosition = null; + var initialDimensions = null; + var rotation = null; + var registrationPoint = null; + var pickPlanePosition = null; + var pickPlaneNormal = null; + var previousPickRay = null; + var directionVector = null; + var axisVector = null; + var signs = null; + var mask = null; + var stretchPanel = null; + var handleStretchCube = null; + var deltaPivot = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + if (directionEnum === STRETCH_DIRECTION.X) { + stretchPanel = handleStretchXPanel; + handleStretchCube = handleStretchXCube; + directionVector = { x: -1, y: 0, z: 0 }; + } else if (directionEnum === STRETCH_DIRECTION.Y) { + stretchPanel = handleStretchYPanel; + handleStretchCube = handleStretchYCube; + directionVector = { x: 0, y: -1, z: 0 }; + } else if (directionEnum === STRETCH_DIRECTION.Z) { + stretchPanel = handleStretchZPanel; + handleStretchCube = handleStretchZCube; + directionVector = { x: 0, y: 0, z: -1 }; + } + + rotation = SelectionManager.localRotation; + initialPosition = SelectionManager.localPosition; + initialDimensions = SelectionManager.localDimensions; + registrationPoint = SelectionManager.localRegistrationPoint; + + axisVector = Vec3.multiply(NEGATE_VECTOR, directionVector); + axisVector = Vec3.multiplyQbyV(rotation, axisVector); + + signs = { + x: directionVector.x < 0 ? -1 : (directionVector.x > 0 ? 1 : 0), + y: directionVector.y < 0 ? -1 : (directionVector.y > 0 ? 1 : 0), + z: directionVector.z < 0 ? -1 : (directionVector.z > 0 ? 1 : 0) + }; + mask = { + x: Math.abs(directionVector.x) > 0 ? 1 : 0, + y: Math.abs(directionVector.y) > 0 ? 1 : 0, + z: Math.abs(directionVector.z) > 0 ? 1 : 0 + }; + + var pivot = directionVector; + var offset = Vec3.multiply(directionVector, NEGATE_VECTOR); + + // Modify range of registrationPoint to be [-0.5, 0.5] + var centeredRP = Vec3.subtract(registrationPoint, { + x: 0.5, + y: 0.5, + z: 0.5 + }); + + // Scale pivot to be in the same range as registrationPoint + var scaledPivot = Vec3.multiply(0.5, pivot); + deltaPivot = Vec3.subtract(centeredRP, scaledPivot); + + var scaledOffset = Vec3.multiply(0.5, offset); + + // Offset from the registration point + var offsetRP = Vec3.subtract(scaledOffset, centeredRP); + + // Scaled offset in world coordinates + var scaledOffsetWorld = Vec3.multiplyVbyV(initialDimensions, offsetRP); + + pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); + pickPlanePosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(true); + that.setHandleStretchXVisible(directionEnum === STRETCH_DIRECTION.X); + that.setHandleStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); + that.setHandleStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); + that.setHandleDuplicatorVisible(false); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + var collisionToRemove = "myAvatar"; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + if (properties.collidesWith.indexOf(collisionToRemove) > -1) { + var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = true; + } + + if (stretchPanel !== null) { + Overlays.editOverlay(stretchPanel, { visible: true, ignorePickIntersection: false }); + } + var stretchCubePosition = Overlays.getProperty(handleStretchCube, "position"); + var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); + activeStretchCubePanelOffset = Vec3.subtract(stretchPanelPosition, stretchCubePosition); + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + if (that.replaceCollisionsAfterStretch) { + var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = false; + } + + if (stretchPanel !== null) { + Overlays.editOverlay(stretchPanel, { visible: false, ignorePickIntersection: true }); + } + activeStretchCubePanelOffset = null; + + pushCommandForSelections(); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var changeInDimensions = Vec3.subtract(newPick, initialPick); + var dotVector = Vec3.dot(changeInDimensions, axisVector); + changeInDimensions = Vec3.multiply(dotVector, axisVector); + changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(rotation), changeInDimensions); + changeInDimensions = Vec3.multiplyVbyV(mask, changeInDimensions); + changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = Vec3.multiply(NEGATE_VECTOR, Vec3.multiplyVbyV(signs, changeInDimensions)); + + var newDimensions = Vec3.sum(initialDimensions, changeInDimensions); + + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } + + var changeInPosition = Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(deltaPivot, changeInDimensions)); + var newPosition = Vec3.sum(initialPosition, changeInPosition); + + Entities.editEntity(SelectionManager.selections[0], { + position: newPosition, + dimensions: newDimensions + }); + + var wantDebug = false; + if (wantDebug) { + print(mode); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE SCALE TOOL + function addHandleScaleTool(overlay, mode) { + var initialPick = null; + var initialPosition = null; + var initialDimensions = null; + var pickPlanePosition = null; + var pickPlaneNormal = null; + var previousPickRay = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + initialPosition = SelectionManager.localPosition; + initialDimensions = SelectionManager.localDimensions; + + pickPlanePosition = initialPosition; + pickPlaneNormal = Vec3.subtract(pickRay.origin, pickPlanePosition); + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(true); + that.setHandleStretchVisible(false); + that.setHandleDuplicatorVisible(false); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + var collisionToRemove = "myAvatar"; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + if (properties.collidesWith.indexOf(collisionToRemove) > -1) { + var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = true; + } + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + if (that.replaceCollisionsAfterStretch) { + var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = false; + } + + pushCommandForSelections(); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var toCameraDistance = getDistanceToCamera(initialPosition); + 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 = Vec3.multiply(changeInDimensions, dimensionsMultiple); + + var averageDimensionChange = (changeInDimensions.x + changeInDimensions.y + changeInDimensions.z) / 3; + var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3; + percentChange = averageDimensionChange / averageInitialDimension; + percentChange += 1.0; + + var newDimensions = Vec3.multiply(percentChange, initialDimensions); + newDimensions.x = Math.abs(newDimensions.x); + newDimensions.y = Math.abs(newDimensions.y); + newDimensions.z = Math.abs(newDimensions.z); + + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } + + Entities.editEntity(SelectionManager.selections[0], { dimensions: newDimensions }); + + var wantDebug = false; + if (wantDebug) { + print(mode); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // FUNCTION: UPDATE ROTATION DEGREES OVERLAY + function updateRotationDegreesOverlay(angleFromZero, position) { + var toCameraDistance = getDistanceToCamera(position); + var overlayProps = { + position: position, + dimensions: { + x: toCameraDistance * ROTATE_DISPLAY_SIZE_X_MULTIPLIER, + y: toCameraDistance * ROTATE_DISPLAY_SIZE_Y_MULTIPLIER + }, + lineHeight: toCameraDistance * ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER, + text: normalizeDegrees(-angleFromZero) + "°" + }; + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); + } + + // FUNCTION DEF: updateSelectionsRotation + // Helper func used by rotation handle tools + function updateSelectionsRotation(rotationChange, initialPosition) { + if (!rotationChange) { + print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); + + // EARLY EXIT + return; + } + + // Entities should only reposition if we are rotating multiple selections around + // the selections center point. Otherwise, the rotation will be around the entities + // registration point which does not need repositioning. + var reposition = (SelectionManager.selections.length > 1); + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toRotate = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toRotate.length; i++) { + var entityID = toRotate[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + + var newProperties = { + rotation: Quat.multiply(rotationChange, initialProperties.rotation) + }; + + if (reposition) { + var dPos = Vec3.subtract(initialProperties.position, initialPosition); + dPos = Vec3.multiplyQbyV(rotationChange, dPos); + newProperties.position = Vec3.sum(initialPosition, dPos); + } + + Entities.editEntity(entityID, newProperties); + } + } + + // TOOL DEFINITION: HANDLE ROTATION TOOL + function addHandleRotateTool(overlay, mode, direction) { + var selectedHandle = null; + var worldRotation = null; + var initialRotation = null; + var rotationCenter = null; + var rotationNormal = null; + var rotationZero = null; + var rotationDegreesPosition = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) -> ======================="); + } + + if (direction === ROTATE_DIRECTION.PITCH) { + rotationNormal = { x: 1, y: 0, z: 0 }; + worldRotation = worldRotationY; + selectedHandle = handleRotatePitchRing; + } else if (direction === ROTATE_DIRECTION.YAW) { + rotationNormal = { x: 0, y: 1, z: 0 }; + worldRotation = worldRotationZ; + selectedHandle = handleRotateYawRing; + } else if (direction === ROTATE_DIRECTION.ROLL) { + rotationNormal = { x: 0, y: 0, z: 1 }; + worldRotation = worldRotationX; + selectedHandle = handleRotateRollRing; + } + + initialRotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + rotationNormal = Vec3.multiplyQbyV(initialRotation, rotationNormal); + rotationCenter = SelectionManager.worldPosition; + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateVisible(false); + that.setHandleRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); + that.setHandleRotateYawVisible(direction === ROTATE_DIRECTION.YAW); + that.setHandleRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); + that.setHandleStretchVisible(false); + that.setHandleScaleVisible(false); + that.setHandleDuplicatorVisible(false); + + Overlays.editOverlay(selectedHandle, { + hasTickMarks: true, + solid: false, + innerRadius: ROTATE_RING_SELECTED_INNER_RADIUS + }); + + Overlays.editOverlay(rotationDegreesDisplay, { visible: true }); + Overlays.editOverlay(handleRotateCurrentRing, { + position: rotationCenter, + rotation: worldRotation, + startAt: 0, + endAt: 0, + visible: true, + ignorePickIntersection: false + }); + + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. + var initialPick = 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; + + var rotationCenterToZero = Vec3.subtract(rotationZero, rotationCenter); + var rotationCenterToZeroLength = Vec3.length(rotationCenterToZero); + rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero), + rotationCenterToZeroLength * ROTATE_DISPLAY_DISTANCE_MULTIPLIER)); + updateRotationDegreesOverlay(0, rotationDegreesPosition); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(rotationCenter, rotationNormal); + that.showDebugPickPlaneHit(initialPick); + } + + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) <- ======================="); + } + }, + onEnd: function(event, reason) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) -> ======================="); + } + Overlays.editOverlay(rotationDegreesDisplay, { visible: false, ignorePickIntersection: true }); + Overlays.editOverlay(selectedHandle, { + hasTickMarks: false, + solid: true, + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS + }); + Overlays.editOverlay(handleRotateCurrentRing, { visible: false, ignorePickIntersection: true }); + pushCommandForSelections(); + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); + } + }, + onMove: function(event) { + if (!rotationZero) { + print("ERROR: entitySelectionTool.addHandleRotateTool.onMove - " + + "Invalid RotationZero Specified (missed rotation target plane?)"); + + // EARLY EXIT + return; + } + + var wantDebug = false; + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) -> ======================="); + Vec3.print(" rotationZero: ", rotationZero); + } + + var pickRay = generalComputePickRay(event.x, event.y); + var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + if (result) { + var centerToZero = Vec3.subtract(rotationZero, rotationCenter); + var centerToIntersect = Vec3.subtract(result, rotationCenter); + + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotationZero: ", rotationZero); + Vec3.print(" rotationCenter: ", rotationCenter); + Vec3.print(" intersect: ", result); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); + } + + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + var snapAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_SNAP_ANGLE; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + var rotationChange = Quat.angleAxis(angleFromZero, rotationNormal); + updateSelectionsRotation(rotationChange, rotationCenter); + updateRotationDegreesOverlay(-angleFromZero, rotationDegreesPosition); + + if (direction === ROTATE_DIRECTION.YAW) { + angleFromZero *= -1; + } + + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var maxDegrees = 360; + if (angleFromZero < 0) { + startAtCurrent = maxDegrees + angleFromZero; + endAtCurrent = maxDegrees; + } + Overlays.editOverlay(handleRotateCurrentRing, { + startAt: startAtCurrent, + endAt: endAtCurrent + }); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(result); + } + } + + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) <- ======================="); + } + } + }); + } + + addHandleTranslateXZTool(selectionBox, "TRANSLATE_XZ", false); + addHandleTranslateXZTool(iconSelectionBox, "TRANSLATE_XZ", false); + addHandleTranslateXZTool(handleDuplicator, "DUPLICATE", true); + + addHandleTranslateTool(handleTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + addHandleTranslateTool(handleTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + + addHandleRotateTool(handleRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH); + addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); + addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); + + addHandleStretchTool(handleStretchXCube, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYCube, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); + + addHandleScaleTool(handleScaleCube, "SCALE"); + + return that; +}()); diff --git a/scripts/simplifiedUI/system/modules/createWindow.js b/scripts/simplifiedUI/system/create/modules/createWindow.js similarity index 100% rename from scripts/simplifiedUI/system/modules/createWindow.js rename to scripts/simplifiedUI/system/create/modules/createWindow.js diff --git a/scripts/simplifiedUI/system/modules/entityShapeVisualizer.js b/scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js similarity index 98% rename from scripts/simplifiedUI/system/modules/entityShapeVisualizer.js rename to scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js index da28369cdd..dbf09a1cb7 100644 --- a/scripts/simplifiedUI/system/modules/entityShapeVisualizer.js +++ b/scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js @@ -146,8 +146,8 @@ EntityShape.prototype = { parentID: this.entity, priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", - materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), - ignorePickIntersection: true, + materialURL: Script.resolvePath("../../assets/images/materials/GridPattern.json"), + ignorePickIntersection: true }, "local"); }, update: function() { diff --git a/scripts/simplifiedUI/system/create/Edit.qml b/scripts/simplifiedUI/system/create/qml/Edit.qml similarity index 95% rename from scripts/simplifiedUI/system/create/Edit.qml rename to scripts/simplifiedUI/system/create/qml/Edit.qml index ca18388def..13e7874ca8 100644 --- a/scripts/simplifiedUI/system/create/Edit.qml +++ b/scripts/simplifiedUI/system/create/qml/Edit.qml @@ -34,7 +34,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(path); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/scripts/simplifiedUI/system/create/EditTabButton.qml b/scripts/simplifiedUI/system/create/qml/EditTabButton.qml similarity index 100% rename from scripts/simplifiedUI/system/create/EditTabButton.qml rename to scripts/simplifiedUI/system/create/qml/EditTabButton.qml diff --git a/scripts/simplifiedUI/system/create/EditTabView.qml b/scripts/simplifiedUI/system/create/qml/EditTabView.qml similarity index 93% rename from scripts/simplifiedUI/system/create/EditTabView.qml rename to scripts/simplifiedUI/system/create/qml/EditTabView.qml index 7e8789487c..a0cff70d50 100644 --- a/scripts/simplifiedUI/system/create/EditTabView.qml +++ b/scripts/simplifiedUI/system/create/qml/EditTabView.qml @@ -72,7 +72,7 @@ TabBar { NewEntityButton { - icon: "create-icons/94-model-01.svg" + icon: "icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -84,7 +84,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/21-cube-01.svg" + icon: "icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -96,7 +96,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/24-light-01.svg" + icon: "icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -108,7 +108,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/20-text-01.svg" + icon: "icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -120,7 +120,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/image.svg" + icon: "icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -132,7 +132,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/25-web-1-01.svg" + icon: "icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -144,7 +144,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/23-zone-01.svg" + icon: "icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -156,7 +156,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/90-particles-01.svg" + icon: "icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -168,7 +168,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/126-material-01.svg" + icon: "icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ @@ -231,7 +231,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../entityList/html/entityList.html") enabled: true blurOnCtrlShift: false } @@ -247,7 +247,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityPropertiesWebView - url: Paths.defaultScripts + "/system/html/entityProperties.html" + url: Qt.resolvedUrl("../entityProperties/html/entityProperties.html") enabled: true blurOnCtrlShift: false } @@ -263,7 +263,7 @@ TabBar { property Component visualItem: Component { WebView { id: gridControlsWebView - url: Paths.defaultScripts + "/system/html/gridControls.html" + url: Qt.resolvedUrl("../../html/gridControls.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/EditTools.qml b/scripts/simplifiedUI/system/create/qml/EditTools.qml similarity index 100% rename from scripts/simplifiedUI/system/create/EditTools.qml rename to scripts/simplifiedUI/system/create/qml/EditTools.qml diff --git a/scripts/simplifiedUI/system/create/EditToolsTabView.qml b/scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml similarity index 93% rename from scripts/simplifiedUI/system/create/EditToolsTabView.qml rename to scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml index a333acc586..0ce8d8e8d4 100644 --- a/scripts/simplifiedUI/system/create/EditToolsTabView.qml +++ b/scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml @@ -78,7 +78,7 @@ TabBar { NewEntityButton { - icon: "create-icons/94-model-01.svg" + icon: "icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -90,7 +90,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/21-cube-01.svg" + icon: "icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -102,7 +102,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/24-light-01.svg" + icon: "icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -114,7 +114,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/20-text-01.svg" + icon: "icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -126,7 +126,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/image.svg" + icon: "icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -138,7 +138,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/25-web-1-01.svg" + icon: "icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -150,7 +150,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/23-zone-01.svg" + icon: "icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -162,7 +162,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/90-particles-01.svg" + icon: "icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -174,7 +174,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/126-material-01.svg" + icon: "icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ @@ -237,7 +237,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityPropertiesWebView - url: Paths.defaultScripts + "/system/html/entityProperties.html" + url: Qt.resolvedUrl("../entityProperties/html/entityProperties.html") enabled: true blurOnCtrlShift: false } @@ -253,7 +253,7 @@ TabBar { property Component visualItem: Component { WebView { id: gridControlsWebView - url: Paths.defaultScripts + "/system/html/gridControls.html" + url: Qt.resolvedUrl("../../html/gridControls.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/NewEntityButton.qml b/scripts/simplifiedUI/system/create/qml/NewEntityButton.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewEntityButton.qml rename to scripts/simplifiedUI/system/create/qml/NewEntityButton.qml diff --git a/scripts/simplifiedUI/system/create/NewMaterialDialog.qml b/scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml similarity index 99% rename from scripts/simplifiedUI/system/create/NewMaterialDialog.qml rename to scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml index 75570327e0..1631632fb4 100644 --- a/scripts/simplifiedUI/system/create/NewMaterialDialog.qml +++ b/scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml @@ -15,7 +15,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import dialogs 1.0 +import hifi.dialogs 1.0 Rectangle { id: newMaterialDialog diff --git a/scripts/simplifiedUI/system/create/NewMaterialWindow.qml b/scripts/simplifiedUI/system/create/qml/NewMaterialWindow.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewMaterialWindow.qml rename to scripts/simplifiedUI/system/create/qml/NewMaterialWindow.qml diff --git a/scripts/simplifiedUI/system/create/NewModelDialog.qml b/scripts/simplifiedUI/system/create/qml/NewModelDialog.qml similarity index 95% rename from scripts/simplifiedUI/system/create/NewModelDialog.qml rename to scripts/simplifiedUI/system/create/qml/NewModelDialog.qml index 1ded00d701..741902fa7f 100644 --- a/scripts/simplifiedUI/system/create/NewModelDialog.qml +++ b/scripts/simplifiedUI/system/create/qml/NewModelDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import dialogs 1.0 +import hifi.dialogs 1.0 Rectangle { id: newModelDialog @@ -135,19 +135,12 @@ Rectangle { height: 400 spacing: 20 - Image { - id: image1 - width: 30 - height: 30 - source: "qrc:/qtquickplugin/images/template_image.png" - } - Text { id: text2 width: 160 x: dynamic.width / 2 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic.") wrapMode: Text.WordWrap font.pixelSize: 12 } diff --git a/scripts/simplifiedUI/system/create/NewModelWindow.qml b/scripts/simplifiedUI/system/create/qml/NewModelWindow.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewModelWindow.qml rename to scripts/simplifiedUI/system/create/qml/NewModelWindow.qml diff --git a/scripts/simplifiedUI/system/create/create-icons/126-material-01.svg b/scripts/simplifiedUI/system/create/qml/icons/126-material-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/126-material-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/126-material-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/20-text-01.svg b/scripts/simplifiedUI/system/create/qml/icons/20-text-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/20-text-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/20-text-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg b/scripts/simplifiedUI/system/create/qml/icons/21-cube-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/21-cube-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg b/scripts/simplifiedUI/system/create/qml/icons/22-sphere-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/22-sphere-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg b/scripts/simplifiedUI/system/create/qml/icons/23-zone-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/23-zone-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/24-light-01.svg b/scripts/simplifiedUI/system/create/qml/icons/24-light-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/24-light-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/24-light-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg b/scripts/simplifiedUI/system/create/qml/icons/25-web-1-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/25-web-1-01.svg diff --git a/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg new file mode 100644 index 0000000000..5e0105d7cd --- /dev/null +++ b/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/create-icons/94-model-01.svg b/scripts/simplifiedUI/system/create/qml/icons/94-model-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/94-model-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/94-model-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/image.svg b/scripts/simplifiedUI/system/create/qml/icons/image.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/image.svg rename to scripts/simplifiedUI/system/create/qml/icons/image.svg diff --git a/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js b/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js new file mode 100644 index 0000000000..cf3927ac2d --- /dev/null +++ b/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js @@ -0,0 +1,29 @@ +"use strict"; + +// +// keyboardShortcuts.js +// scripts/system/keyboardShortcuts +// +// Created by Preston Bezos on 06/28/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { // BEGIN LOCAL_SCOPE + function keyPressEvent(event) { + if (event.text.toUpperCase() === "B" && event.isControl) { + Window.openWebBrowser(); + } else if (event.text.toUpperCase() === "N" && event.isControl) { + Users.toggleIgnoreRadius(); + } + } + + function scriptEnding() { + Controller.keyPressEvent.disconnect(keyPressEvent); + } + + Controller.keyPressEvent.connect(keyPressEvent); + Script.scriptEnding.connect(scriptEnding); +}()); // END LOCAL_SCOPE diff --git a/scripts/system/create/qml/Edit.qml b/scripts/system/create/qml/Edit.qml index ca18388def..13e7874ca8 100644 --- a/scripts/system/create/qml/Edit.qml +++ b/scripts/system/create/qml/Edit.qml @@ -34,7 +34,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(path); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/scripts/system/create/qml/NewModelDialog.qml b/scripts/system/create/qml/NewModelDialog.qml index 92a08df10d..741902fa7f 100644 --- a/scripts/system/create/qml/NewModelDialog.qml +++ b/scripts/system/create/qml/NewModelDialog.qml @@ -135,19 +135,12 @@ Rectangle { height: 400 spacing: 20 - Image { - id: image1 - width: 30 - height: 30 - source: "qrc:/qtquickplugin/images/template_image.png" - } - Text { id: text2 width: 160 x: dynamic.width / 2 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic.") wrapMode: Text.WordWrap font.pixelSize: 12 }