overte/scripts/system/edit.js
Ryan Huffman 0e0966004c Fix edit.js not being able to delete after creating entity
This fix puts focus back on the main window when creating an entity so that the user can use shortcuts like 'f' and 'delete'
2018-07-25 14:52:56 -07:00

2477 lines
89 KiB
JavaScript

// 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, ParticleExplorerTool, OverlaySystemWindow */
(function() { // BEGIN LOCAL_SCOPE
"use strict";
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
Script.include([
"libraries/stringHelpers.js",
"libraries/dataViewHelpers.js",
"libraries/progressDialog.js",
"libraries/entitySelectionTool.js",
"libraries/ToolTip.js",
"libraries/entityCameraTool.js",
"libraries/gridTool.js",
"libraries/entityList.js",
"libraries/utils.js",
"particle_explorer/particleExplorerTool.js",
"libraries/entityIconOverlayManager.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 createToolsWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/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 entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) {
var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']);
if (properties.type === 'Light') {
return {
url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_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 entityListTool = new EntityListTool(shouldUseEditTabletApp);
selectionManager.addEventListener(function () {
selectionDisplay.updateHandles();
entityIconOverlayManager.updatePositions();
// Update particle explorer
var needToDestroyParticleExplorer = false;
if (selectionManager.selections.length === 1) {
var selectedEntityID = selectionManager.selections[0];
if (selectedEntityID === selectedParticleEntityID) {
return;
}
var type = Entities.getEntityProperties(selectedEntityID, "type").type;
if (type === "ParticleEffect") {
selectParticleEntity(selectedEntityID);
} else {
needToDestroyParticleExplorer = true;
}
} else {
needToDestroyParticleExplorer = true;
}
if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) {
selectedParticleEntityID = null;
particleExplorerTool.destroyWebView();
}
});
var KEY_P = 80; //Key code for letter p used for Parenting hotkey.
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();
}
}
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(properties) {
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
var position = getPositionToCreateEntity();
var entityID = null;
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);
// Align entity with Avatar orientation.
properties.rotation = MyAvatar.orientation;
var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "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 (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) &&
!(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) {
properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
} else {
properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } });
}
entityID = Entities.addEntity(properties);
if (properties.type === "ParticleEffect") {
selectParticleEntity(entityID);
}
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();
} 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();
entityListTool.sendUpdate();
selectionManager.setSelections([entityID]);
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.textInput;
var shapeType;
switch (result.comboBox) {
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.checkBox !== null ? result.checkBox : 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,
dynamic: dynamic,
gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
});
}
}
}
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);
}
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 !== "hifi/tablet/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("hifi/tablet/New" + entityType + "Dialog.qml");
} else {
closeExistingDialogWindow();
var qmlPath = Script.resourcesPath() + "qml/hifi/tablet/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("newCubeButton", function () {
createNewEntity({
type: "Box",
dimensions: DEFAULT_DIMENSIONS,
color: {
red: 255,
green: 0,
blue: 0
}
});
});
addButton("newSphereButton", function () {
createNewEntity({
type: "Sphere",
dimensions: DEFAULT_DIMENSIONS,
color: {
red: 255,
green: 0,
blue: 0
}
});
});
addButton("newLightButton", function () {
createNewEntity({
type: "Light",
dimensions: DEFAULT_LIGHT_DIMENSIONS,
isSpotlight: false,
color: {
red: 150,
green: 150,
blue: 150
},
constantAttenuation: 1,
linearAttenuation: 0,
quadraticAttenuation: 0,
exponent: 0,
cutoff: 180 // in degrees
});
});
addButton("newTextButton", function () {
createNewEntity({
type: "Text",
dimensions: {
x: 0.65,
y: 0.3,
z: 0.01
},
backgroundColor: {
red: 64,
green: 64,
blue: 64
},
textColor: {
red: 255,
green: 255,
blue: 255
},
text: "some text",
lineHeight: 0.06
});
});
addButton("newImageButton", function () {
var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
createNewEntity({
type: "Model",
dimensions: {
x: 0.5385,
y: 0.2819,
z: 0.0092
},
shapeType: "box",
collisionless: true,
modelURL: IMAGE_MODEL,
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
});
});
addButton("newWebButton", function () {
createNewEntity({
type: "Web",
dimensions: {
x: 1.6,
y: 0.9,
z: 0.01
},
sourceUrl: "https://highfidelity.com/"
});
});
addButton("newZoneButton", function () {
createNewEntity({
type: "Zone",
dimensions: {
x: 10,
y: 10,
z: 10
}
});
});
addButton("newParticleButton", function () {
createNewEntity({
type: "ParticleEffect",
isEmitting: true,
emitterShouldTrail: true,
color: {
red: 200,
green: 200,
blue: 200
},
colorSpread: {
red: 0,
green: 0,
blue: 0
},
colorStart: {
red: 200,
green: 200,
blue: 200
},
colorFinish: {
red: 0,
green: 0,
blue: 0
},
emitAcceleration: {
x: -0.5,
y: 2.5,
z: -0.5
},
accelerationSpread: {
x: 0.5,
y: 1,
z: 0.5
},
emitRate: 5.5,
emitSpeed: 0,
speedSpread: 0,
lifespan: 1.5,
maxParticles: 10,
particleRadius: 0.25,
radiusStart: 0,
radiusFinish: 0.1,
radiusSpread: 0,
alpha: 0,
alphaStart: 1,
alphaFinish: 0,
polarStart: 0,
polarFinish: 0,
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png"
});
});
addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material"));
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});
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();
cameraManager.disable();
selectionDisplay.triggerMapping.disable();
tablet.landscape = false;
} else {
if (shouldUseEditTabletApp()) {
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
} else {
// make other apps inactive while in desktop mode
tablet.gotoHomeScreen();
}
UserActivityLogger.enabledEdit();
entityListTool.setVisible(true);
gridTool.setVisible(true);
grid.setEnabled(true);
propertiesTool.setVisible(true);
selectionDisplay.triggerMapping.enable();
print("starting tablet in landscape mode");
tablet.landscape = true;
// 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 (wantDebug) {
print("setting selection to overlay " + data.overlayID);
}
var entity = entityIconOverlayManager.findEntity(data.overlayID);
if (entity !== null) {
selectionManager.setSelections([entity]);
}
}
}
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);
tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event);
if (tabletOrEditHandleClicked) {
return;
}
if (result === null || result === undefined) {
if (!event.isShifted) {
selectionManager.clearSelections();
}
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) {
particleExplorerTool.destroyWebView();
}
if (properties.type !== "ParticleEffect") {
particleExplorerTool.destroyWebView();
}
if (!event.isShifted) {
selectionManager.setSelections([foundEntity]);
} else {
selectionManager.addEntity(foundEntity, true);
}
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: "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.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_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE));
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();
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 selectAllEtitiesInCurrentSelectionBox(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);
}
}
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) {
var entitiesLength = entities.length;
for (var i = 0; i < entitiesLength; i++) {
var entityID = entities[i];
var children = Entities.getChildrenIDs(entityID);
var grandchildrenList = [];
recursiveDelete(children, grandchildrenList, deletedIDs);
var initialProperties = Entities.getEntityProperties(entityID);
childrenList.push({
entityID: entityID,
properties: initialProperties,
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 inorder 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 = [];
selectedParticleEntityID = null;
particleExplorerTool.destroyWebView();
SelectionManager.saveProperties();
var savedProperties = [];
var newSortedSelection = sortSelectedEntities(selectionManager.selections);
for (var i = 0; i < newSortedSelection.length; i++) {
var entityID = newSortedSelection[i];
var initialProperties = SelectionManager.savedProperties[entityID];
if (!initialProperties.locked) {
var children = Entities.getChildrenIDs(entityID);
var childList = [];
recursiveDelete(children, childList, deletedIDs);
savedProperties.push({
entityID: entityID,
properties: initialProperties,
children: childList
});
deletedIDs.push(entityID);
Entities.deleteEntity(entityID);
}
}
if (savedProperties.length > 0) {
SelectionManager.clearSelections();
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();
}
}
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();
}
}
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 handeMenuEvent(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 === "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") {
selectAllEtitiesInCurrentSelectionBox(false);
} else if (menuItem === "Select All Entities Touching Box") {
selectAllEtitiesInCurrentSelectionBox(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));
}
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));
position.y += 0.5;
}
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);
}
} 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(handeMenuEvent);
var keyPressEvent = function (event) {
if (isActive) {
cameraManager.keyPressEvent(event);
}
};
var keyReleaseEvent = function (event) {
if (isActive) {
cameraManager.keyReleaseEvent(event);
}
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text === "DELETE") {
deleteSelectedEntities();
} else if (event.text === 'd' && event.isControl) {
selectionManager.clearSelections();
} else if (event.text === "t") {
selectionDisplay.toggleSpaceMode();
} else if (event.text === "f") {
if (isActive) {
if (selectionManager.hasSelection()) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
}
} else if (event.text === '[') {
if (isActive) {
cameraManager.enable();
}
} else if (event.text === 'g') {
if (isActive && selectionManager.hasSelection()) {
grid.moveToSelection();
}
} else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) {
if (event.isShifted) {
unparentSelectedEntities();
} else {
parentSelectedEntities();
}
}
};
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);
function recursiveAdd(newParentID, parentData) {
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]);
}
}
// 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 properties = data.setProperties;
var selectedEntityIDs = [];
var i, entityID;
for (i = 0; i < properties.length; i++) {
entityID = properties[i].entityID;
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
entityID = DELETED_ENTITY_MAP[entityID];
}
Entities.editEntity(entityID, properties[i].properties);
selectedEntityIDs.push(entityID);
}
for (i = 0; i < data.createEntities.length; i++) {
entityID = data.createEntities[i].entityID;
var entityProperties = data.createEntities[i].properties;
var newEntityID = Entities.addEntity(entityProperties);
recursiveAdd(newEntityID, data.createEntities[i]);
DELETED_ENTITY_MAP[entityID] = newEntityID;
if (data.selectCreated) {
selectedEntityIDs.push(newEntityID);
}
}
for (i = 0; i < data.deleteEntities.length; i++) {
entityID = data.deleteEntities[i].entityID;
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
entityID = DELETED_ENTITY_MAP[entityID];
}
Entities.deleteEntity(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);
}
}
// 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) {
var undoData = {
setProperties: [],
createEntities: deletedEntityData || [],
deleteEntities: createdEntityData || [],
selectCreated: true
};
var redoData = {
setProperties: [],
createEntities: createdEntityData || [],
deleteEntities: deletedEntityData || [],
selectCreated: false
};
for (var i = 0; i < SelectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID];
var currentProperties = Entities.getEntityProperties(entityID);
if (!initialProperties) {
continue;
}
undoData.setProperties.push({
entityID: entityID,
properties: initialProperties
});
redoData.setProperties.push({
entityID: entityID,
properties: currentProperties
});
}
UndoStack.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;
that.setVisible = function (newVisible) {
visible = newVisible;
webView.setVisible(shouldUseEditTabletApp() && visible);
createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible);
};
that.setVisible(false);
function updateScriptStatus(info) {
info.type = "server_script_status";
webView.emitScriptEvent(JSON.stringify(info));
}
function resetScriptStatus() {
updateScriptStatus({
statusRetrieved: undefined,
isRunning: undefined,
status: "",
errorInfo: ""
});
}
function updateSelections(selectionUpdated) {
var data = {
type: 'update'
};
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.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) {
entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES,
Vec3.toPolar(entity.properties.keyLight.direction));
entity.properties.keyLight.direction.z = 0.0;
}
selections.push(entity);
}
data.selections = selections;
webView.emitScriptEvent(JSON.stringify(data));
createToolsWindow.emitScriptEvent(JSON.stringify(data));
}
selectionManager.addEventListener(updateSelections);
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
}
catch(e) {
print('Edit.js received web event that was not valid json.');
return;
}
var i, properties, dY, diff, newPosition;
if (data.type === "print") {
if (data.message) {
print(data.message);
}
} else if (data.type === "update") {
selectionManager.saveProperties();
if (selectionManager.selections.length > 1) {
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], data.properties);
}
} else if (data.properties) {
if (data.properties.dynamic === false) {
// this object is leaving dynamic, so we zero its velocities
data.properties.velocity = Vec3.ZERO;
data.properties.angularVelocity = Vec3.ZERO;
}
if (data.properties.rotation !== undefined) {
var rotation = data.properties.rotation;
data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z);
}
if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) {
data.properties.keyLight.direction = Vec3.fromPolar(
data.properties.keyLight.direction.x * DEGREES_TO_RADIANS,
data.properties.keyLight.direction.y * DEGREES_TO_RADIANS
);
}
Entities.editEntity(selectionManager.selections[0], data.properties);
if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined ||
data.properties.visible !== undefined || data.properties.locked !== undefined) {
entityListTool.sendUpdate();
}
}
pushCommandForSelections();
selectionManager._update();
} else if (data.type === 'parent') {
parentSelectedEntities();
} else if (data.type === 'unparent') {
unparentSelectedEntities();
} else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') {
//the event bridge and json parsing handle our avatar id string differently.
var actualID = data.id.split('"')[1];
Entities.editEntity(actualID, 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();
}
} 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();
}
} 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();
}
} 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();
}
} 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);
}
};
createToolsWindow.webEventReceived.addListener(this, onWebEventReceived);
webView.webEventReceived.connect(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 && 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;
};
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();
var particleExplorerTool = new ParticleExplorerTool(createToolsWindow);
var selectedParticleEntityID = null;
function selectParticleEntity(entityID) {
selectedParticleEntityID = entityID;
var properties = Entities.getEntityProperties(entityID);
if (properties.emitOrientation) {
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
}
particleExplorerTool.destroyWebView();
particleExplorerTool.createWebView();
particleExplorerTool.setActiveParticleEntity(entityID);
// Switch to particle explorer
var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } };
if (shouldUseEditTabletApp()) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.sendToQml(selectTabMethod);
} else {
createToolsWindow.sendToQml(selectTabMethod);
}
}
entityListTool.webView.webEventReceived.connect(function(data) {
try {
data = JSON.parse(data);
} catch(e) {
print("edit.js: Error parsing JSON: " + e.name + " data " + data);
return;
}
if (data.type === 'parent') {
parentSelectedEntities();
} else if (data.type === 'unparent') {
unparentSelectedEntities();
} else if (data.type === "selectionUpdate") {
var ids = data.entityIds;
if (ids.length === 1) {
if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) {
// This particle entity is already selected, so return
return;
}
// Destroy the old particles web view first
} else {
selectedParticleEntityID = 0;
particleExplorerTool.destroyWebView();
}
}
}
});
}()); // END LOCAL_SCOPE