diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 282bdc351b..606bbf2bf0 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -7894,7 +7894,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 @@
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
}