overte-AleziaKurdis/scripts/system/create/edit.js
Alezia Kurdis 14f1fa01f7
Exclude Grid from Local EntityList
Exclude Grid from Local EntityList
2025-03-21 21:43:24 -04:00

3401 lines
136 KiB
JavaScript

// edit.js
//
// Created by Brad Hefta-Gaub on October 2nd, 2014.
// Persist toolbar by HRS on June 2nd, 2015.
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// Copyright 2022-2024 Overte e.V.
//
// 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
// SPDX-License-Identifier: Apache-2.0
//
"use strict";
/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EditTools, EditVoxels, EntityListTool, Vec3, SelectionManager,
Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera,
progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */
(function() { // BEGIN LOCAL_SCOPE
var createApp = {};
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",
"audioFeedback/audioFeedback.js",
"modules/brokenURLReport.js",
"modules/renderWithZonesManager.js",
"editModes/editModes.js",
"editModes/editVoxels.js"
]);
var CreateWindow = Script.require('./modules/createWindow.js');
var TITLE_OFFSET = 60;
var CREATE_TOOLS_WIDTH = 750;
var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942;
var ENTIRE_DOMAIN_SCAN_RADIUS = 27713;
var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg");
var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png");
var createToolsWindow = new CreateWindow(
Script.resolvePath("qml/EditTools.qml"),
'Create Tools',
'com.highfidelity.create.createToolsWindow',
function () {
var windowHeight = Window.innerHeight - TITLE_OFFSET;
if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) {
windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT;
}
return {
size: {
x: CREATE_TOOLS_WIDTH,
y: windowHeight
},
position: {
x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH,
y: Window.y + TITLE_OFFSET
}
}
},
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;
selectionDisplay.createApp = createApp;
var selectionManager = SelectionManager;
selectionManager.createApp = createApp;
var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg");
var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg");
var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg");
var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg");
var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg");
var SOUND_URL = Script.resolvePath("assets/images/icon-sound.svg");
var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "ProceduralParticleEffect", "Zone", "Material", "Sound"], function(entityID) {
var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]);
if (properties.type === "Light") {
return {
imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL
};
} else if (properties.type === "Zone") {
return { imageURL: ZONE_URL };
} else if (properties.type === "Material") {
if (properties.parentID !== Uuid.NONE && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) {
return { imageURL: MATERIAL_URL };
} else {
return { imageURL: "" };
}
} else if (properties.type === "Sound") {
return { imageURL: SOUND_URL, rotation: Quat.fromPitchYawRollDegrees(0, 0, 0) };
} else {
return { imageURL: PARTICLE_SYSTEM_URL };
}
});
createApp.hmdMultiSelectMode = false;
createApp.expectingRotateAsClickedSurface = false;
var keepSelectedOnNextClick = false;
var copiedPosition;
var copiedRotation;
var copiedDimensions;
var importUiPersistedData = {
"elJsonUrl": "",
"elImportAtAvatar": true,
"elImportAtSpecificPosition": false,
"elPositionX": 0,
"elPositionY": 0,
"elPositionZ": 0,
"elEntityHostTypeDomain": true,
"elEntityHostTypeAvatar": false
};
var cameraManager = new CameraManager();
var grid = new Grid();
selectionDisplay.grid = grid;
var gridTool = new GridTool({
horizontalGrid: grid,
createToolsWindow: createToolsWindow,
shouldUseEditTabletApp: shouldUseEditTabletApp
});
gridTool.selectionDisplay = selectionDisplay;
gridTool.createApp = createApp;
gridTool.setVisible(false);
var editTools = new EditTools({
createToolsWindow: createToolsWindow,
});
var editVoxels = new EditVoxels();
editVoxels.editTools = editTools;
editTools.addListener(editVoxels.updateEditSettings);
editTools.addListener(selectionManager.updateEditSettings);
var entityShapeVisualizerSessionName = "SHAPE_VISUALIZER_" + Uuid.generate();
createApp.entityShapeVisualizerLocalEntityToExclude = [];
var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js');
var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"], entityShapeVisualizerSessionName);
var entityListTool = new EntityListTool(shouldUseEditTabletApp, selectionManager);
entityListTool.createApp = createApp;
entityListTool.grid = grid;
entityListTool.cameraManager = cameraManager;
entityListTool.selectionDisplay = selectionDisplay;
selectionManager.addEventListener(function () {
selectionDisplay.updateHandles();
entityIconOverlayManager.updatePositions(selectionManager.selections);
entityShapeVisualizer.setEntities(selectionManager.selections);
createApp.entityShapeVisualizerLocalEntityToExclude = entityShapeVisualizer.getLocalEntityToExclude();
});
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = true;
var allowSmallModels = true;
var DEFAULT_DIMENSION = 0.20;
var DEFAULT_DIMENSIONS = {
x: DEFAULT_DIMENSION,
y: DEFAULT_DIMENSION,
z: DEFAULT_DIMENSION
};
var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);
var MENU_IMPORT_FROM_FILE = "Import Entities (.json) From a File";
var MENU_IMPORT_FROM_URL = "Import Entities (.json) From a URL";
var MENU_CREATE_SEPARATOR = "Create Application";
var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences";
var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
createApp.MENU_EASE_ON_FOCUS = MENU_EASE_ON_FOCUS;
var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode";
var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)";
var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models";
var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models";
var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights";
var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius";
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode";
createApp.SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";
createApp.SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup";
createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius";
var SETTING_EDIT_PREFIX = "Edit/";
var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg";
var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg";
// marketplace info, etc. not quite ready yet.
var SHOULD_SHOW_PROPERTY_MENU = false;
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain.";
var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain.";
var isActive = false;
var createButton = null;
var IMPORTING_SVO_OVERLAY_WIDTH = 144;
var IMPORTING_SVO_OVERLAY_HEIGHT = 30;
var IMPORTING_SVO_OVERLAY_MARGIN = 5;
var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34;
var importingSVOImageOverlay = Overlays.addOverlay("image", {
imageURL: Script.resolvePath("assets/images/hourglass.svg"),
width: 20,
height: 20,
alpha: 1.0,
x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH,
y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT,
visible: false
});
var importingSVOTextOverlay = Overlays.addOverlay("text", {
font: {
size: 14
},
text: "Importing SVO...",
leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN,
x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN,
y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN,
width: IMPORTING_SVO_OVERLAY_WIDTH,
height: IMPORTING_SVO_OVERLAY_HEIGHT,
backgroundColor: {
red: 80,
green: 80,
blue: 80
},
backgroundAlpha: 0.7,
visible: false
});
var savedClippingEnabled = false;
function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
// Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original
// position in the given direction.
var CORNERS = [
{ x: 0, y: 0, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 1, z: 1 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 0 },
{ x: 1, y: 1, z: 1 },
];
// Go through all corners and find least (most negative) distance in front of position.
var distance = 0;
for (var i = 0, length = CORNERS.length; i < length; i++) {
var cornerVector =
Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
var cornerDistance = Vec3.dot(cornerVector, direction);
distance = Math.min(cornerDistance, distance);
}
position = Vec3.sum(Vec3.multiply(distance, direction), position);
return position;
}
// Handles any edit mode updates required when domains have switched
function checkEditPermissionsAndUpdate() {
if ((createButton === null) || (createButton === undefined)) {
//--EARLY EXIT--( nothing to safely update )
return;
}
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp());
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://overte.org/",
dpi: 30,
},
ParticleEffect: {
lifespan: 1.5,
maxParticles: 10,
textures: DEFAULT_PARTICLE,
emitRate: 5.5,
emitSpeed: 0,
speedSpread: 0,
emitDimensions: { x: 0, y: 0, z: 0 },
emitOrientation: { x: 0, y: 0, z: 0, w: 1 },
emitterShouldTrail: true,
particleRadius: 0.25,
radiusStart: 0,
radiusSpread: 0,
particleColor: {
red: 255,
green: 255,
blue: 255
},
colorSpread: {
red: 0,
green: 0,
blue: 0
},
alpha: 0,
alphaStart: 1,
alphaSpread: 0,
emitAcceleration: {
x: 0,
y: 2.5,
z: 0
},
accelerationSpread: {
x: 0,
y: 0,
z: 0
},
particleSpin: 0,
spinSpread: 0,
rotateWithEntity: false,
polarStart: 0,
polarFinish: Math.PI,
azimuthStart: -Math.PI,
azimuthFinish: Math.PI
},
ProceduralParticleEffect: {
dimensions: 3,
numParticles: 10000,
numTrianglesPerParticle: 6,
numUpdateProps: 3,
particleUpdateData: JSON.stringify({
version: 1.0,
fragmentShaderURL: "qrc:///shaders/proceduralParticleSwarmUpdate.frag",
uniforms: {
lifespan: 3.0,
speed: 2.0,
speedSpread: 0.25,
mass: 50000000000
}
}),
particleRenderData: JSON.stringify({
version: 3.0,
vertexShaderURL: "qrc:///shaders/proceduralParticleSwarmRender.vert",
fragmentShaderURL: "qrc:///shaders/proceduralParticleSwarmRender.frag",
uniforms: {
radius: 0.03,
lifespan: 3.0
}
})
},
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,
},
Sound: {
volume: 1.0,
playing: true,
loop: true,
positional: true,
localOnly: false
},
};
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 = createApp.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 === "Sound"
|| properties.type === "ParticleEffect" || properties.type == "ProceduralParticleEffect"
|| properties.type === "Web")) {
properties.grab.grabbable = true;
} else {
properties.grab.grabbable = false;
}
}
if (type === "Model") {
properties.visible = false;
}
entityID = Entities.addEntity(properties);
var dimensionsCheckCallback = function(){
// Adjust position of entity per bounding box after it has been created and auto-resized.
var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions;
var DIMENSIONS_CHECK_INTERVAL = 200;
var MAX_DIMENSIONS_CHECKS = 10;
var dimensionsCheckCount = 0;
var dimensionsCheckFunction = function () {
dimensionsCheckCount++;
var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]);
if (!Vec3.equal(properties.dimensions, initialDimensions)) {
position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint,
properties.dimensions, properties.rotation);
position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions),
properties.dimensions);
Entities.editEntity(entityID, {
position: position
});
selectionManager._update(false, this);
} else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
}
};
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
}
// Make sure the model entity is loaded before we try to figure out
// its dimensions. We need to give ample time to load the entity.
var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds.
var LOADED_CHECK_INTERVAL = 100;
var isLoadedCheckCount = 0;
var entityIsLoadedCheck = function() {
isLoadedCheckCount++;
if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) {
var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions;
if (isLoadedCheckCount === MAX_LOADED_CHECKS) {
console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions))
}
Entities.editEntity(entityID, {
visible: true,
dimensions: naturalDimensions
})
dimensionsCheckCallback();
// We want to update the selection manager again since the script has moved on without us.
selectionManager.clearSelections(this);
entityListTool.sendUpdate();
selectionManager.setSelections([entityID], this);
return;
}
Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL);
}
var POST_ADJUST_ENTITY_TYPES = ["Model"];
if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL);
}
SelectionManager.addEntity(entityID, false, this);
SelectionManager.saveProperties();
createApp.pushCommandForSelections([{
entityID: entityID,
properties: properties
}], [], true);
} else {
Window.notifyEditError("Can't create " + properties.type + ": " +
properties.type + " would be out of bounds.");
}
selectionManager.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,
useOriginalPivot: result.useOriginalPivot
});
}
}
}
function handleNewPolyVoxDialogResult(result) {
if (result) {
var initialShape = result.initialShapeIndex;
var volumeSizeX = parseInt(result.volumeSizeX);
var volumeSizeY = parseInt(result.volumeSizeY);
var volumeSizeZ = parseInt(result.volumeSizeZ);
var voxelSurfaceStyle = parseInt(result.surfaceStyleIndex);
var voxelPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: volumeSizeZ * -1.6 }));
var polyVoxID = createNewEntity({
type: "PolyVox",
name: "terrain",
dimensions: {
x: volumeSizeX,
y: volumeSizeY,
z: volumeSizeZ
},
voxelVolumeSize: {
x: volumeSizeX,
y: volumeSizeY,
z: volumeSizeZ
},
xTextureURL: result.xTextureURL,
yTextureURL: result.yTextureURL,
zTextureURL: result.zTextureURL,
voxelSurfaceStyle: voxelSurfaceStyle,
collisionless: !(result.collisions),
grab: {
grabbable: result.grabbable
},
});
Entities.editEntity(polyVoxID, {
position: voxelPosition
});
if (polyVoxID){
switch (initialShape) {
case 0:
Entities.setVoxelsInCuboid(polyVoxID, {
x: Math.round(volumeSizeX / 4),
y: Math.round(volumeSizeY / 4),
z: Math.round(volumeSizeZ / 4)
}, {
x: Math.round(volumeSizeX / 2.0),
y: Math.round(volumeSizeY / 2.0),
z: Math.round(volumeSizeZ / 2.0)
}, 255);
break;
// Plane 1/4
case 1:
Entities.setVoxelsInCuboid(polyVoxID, {
x: 0,
y: 0,
z: 0
}, {
x: volumeSizeX,
y: Math.round(volumeSizeY / 4),
z: volumeSizeZ
}, 255);
break;
// Plane 3/4
case 2:
Entities.setVoxelsInCuboid(polyVoxID, {
x: 0,
y: 0,
z: 0
}, {
x: volumeSizeX,
y: Math.round(3 * volumeSizeY / 4),
z: volumeSizeZ
}, 255);
break;
// Single voxel at center
case 3:
Entities.setVoxel(polyVoxID, {
x: Math.round(volumeSizeX / 2),
y: Math.round(volumeSizeY / 2),
z: Math.round(volumeSizeZ / 2)
}, 255);
break;
}
}
}
}
function handleNewMaterialDialogResult(result) {
if (result) {
var materialURL = result.textInput;
if (materialURL === "") {
materialURL = "materialData";
}
//var materialMappingMode;
//switch (result.comboBox) {
// case MATERIAL_MODE_PROJECTED:
// materialMappingMode = "projected";
// break;
// default:
// shapeType = "uv";
//}
var materialData = "";
if (materialURL.startsWith("materialData")) {
materialData = JSON.stringify({
"materials": {}
});
}
var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1;
if (materialURL) {
createNewEntity({
type: "Material",
materialURL: materialURL,
//materialMappingMode: materialMappingMode,
priority: DEFAULT_LAYERED_MATERIAL_PRIORITY,
materialData: materialData
});
}
}
}
function handleNewParticleDialogResult(result) {
if (result) {
createNewEntity({
type: result.procedural ? "ProceduralParticleEffect" : "ParticleEffect"
});
}
}
function handleNewSoundDialogResult(result) {
if (result) {
var soundURL = result.textInput;
if (soundURL) {
createNewEntity({
type: "Sound",
soundURL: soundURL
});
}
}
}
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.popFromStack();
switch (message.method) {
case "newModelDialogAdd":
handleNewModelDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newModelDialogCancel":
closeExistingDialogWindow();
break;
case "newEntityButtonClicked":
buttonHandlers[message.params.buttonName]();
break;
case "newMaterialDialogAdd":
handleNewMaterialDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newMaterialDialogCancel":
closeExistingDialogWindow();
break;
case "newParticleDialogAdd":
handleNewParticleDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newParticleDialogCancel":
closeExistingDialogWindow();
break;
case "newPolyVoxDialogAdd":
handleNewPolyVoxDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newPolyVoxDialogCancel":
closeExistingDialogWindow();
break;
case "newSoundDialogAdd":
handleNewSoundDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newSoundDialogCancel":
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);
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp());
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()) ) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
return;
}
that.toggle();
});
addButton("importEntitiesButton", function() {
createApp.importEntitiesFromFile();
});
addButton("importEntitiesFromUrlButton", function() {
createApp.importEntitiesFromUrl();
});
addButton("openAssetBrowserButton", function() {
Window.showAssetServer();
});
function createNewEntityDialogButtonCallback(entityType) {
return function() {
if (shouldUseEditTabletApp()) {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml"));
} else {
closeExistingDialogWindow();
var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml");
var DIALOG_WINDOW_SIZE = { x: 500, y: 300 };
if( entityType === "PolyVox" ){
DIALOG_WINDOW_SIZE.x = 600;
DIALOG_WINDOW_SIZE.y = 500;
}
dialogWindow = Desktop.createWindow(qmlPath, {
title: "New " + entityType + " Entity",
additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES,
presentationMode: Desktop.PresentationMode.NATIVE,
size: DIALOG_WINDOW_SIZE,
visible: true
});
dialogWindow.fromQml.connect(fromQml);
}
};
}
addButton("newModelButton", createNewEntityDialogButtonCallback("Model"));
addButton("newShapeButton", function () {
createNewEntity({
type: "Shape",
shape: "Cube",
});
});
addButton("newLightButton", function () {
createNewEntity({
type: "Light",
});
});
addButton("newTextButton", function () {
createNewEntity({
type: "Text",
});
});
addButton("newImageButton", function () {
createNewEntity({
type: "Image",
});
});
addButton("newWebButton", function () {
createNewEntity({
type: "Web",
});
});
addButton("newZoneButton", function () {
createNewEntity({
type: "Zone",
});
});
addButton("newParticleButton", createNewEntityDialogButtonCallback("Particle"));
addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material"));
addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox"));
addButton("newSoundButton", createNewEntityDialogButtonCallback("Sound"));
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) {
Settings.setValue(EDIT_SETTING, active);
if (active) {
Controller.captureEntityClickEvents();
} else {
Controller.releaseEntityClickEvents();
closeExistingDialogWindow();
}
if (active === isActive) {
return;
}
if (active && !Entities.canRez() && !Entities.canRezTmp()) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
return;
}
Messages.sendLocalMessage("edit-events", JSON.stringify({
enabled: active
}));
isActive = active;
activeButton.editProperties({isActive: isActive});
createApp.undoHistory.setEnabled(isActive);
editVoxels.setActive(active);
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!isActive) {
entityListTool.setVisible(false);
gridTool.setVisible(false);
grid.setEnabled(false);
propertiesTool.setVisible(false);
selectionManager.clearSelections(this);
cameraManager.disable();
selectionDisplay.disableTriggerMapping();
tablet.landscape = false;
Controller.disableMapping(CONTROLLER_MAPPING_NAME);
Render.cameraClippingEnabled = savedClippingEnabled;
} 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);
savedClippingEnabled = Render.cameraClippingEnabled;
Render.cameraClippingEnabled = false;
// Not sure what the following was meant to accomplish, but it currently causes
// everybody else to think that Interface has lost focus overall. fogbugzid:558
// Window.setFocus();
}
entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE));
};
initialize();
return that;
})();
var selectedEntityID;
var orientation;
var intersection;
createApp.rayPlaneIntersection = function(pickRay, point, normal) { //
//
// This version of the test returns the intersection of a line with a plane
//
var collides = Vec3.dot(pickRay.direction, normal);
var d = -Vec3.dot(point, normal);
var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides;
return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
}
createApp.rayPlaneIntersection2 = function(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 (createApp.expectingRotateAsClickedSurface) {
if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected, or the selection is locked.");
createApp.expectingRotateAsClickedSurface = false;
} else {
//Rotate Selection according the Surface Normal
var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1));
selectionDisplay.rotateSelection(normalRotation);
//Translate Selection according the clicked Surface
var distanceFromSurface;
if (selectionDisplay.getSpaceMode() === "world"){
distanceFromSurface = SelectionManager.worldDimensions.z / 2;
} else {
distanceFromSurface = SelectionManager.localDimensions.z / 2;
}
selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface})));
selectionManager._update(false, this);
createApp.pushCommandForSelections();
createApp.expectingRotateAsClickedSurface = false;
audioFeedback.action();
}
keepSelectedOnNextClick = true;
return null;
} else {
if (iconResult.intersects) {
result = iconResult;
} else if (entityResult.intersects) {
result = entityResult;
} else {
return null;
}
if (!result.accurate) {
return null;
}
var foundEntity = result.entityID;
return {
pickRay: pickRay,
entityID: foundEntity,
intersection: result.intersection
};
}
}
// Handles selections on overlays while in edit mode by querying entities from
// entityIconOverlayManager.
function handleOverlaySelectionToolUpdates(channel, message, sender) {
var wantDebug = false;
if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates')
return;
var data = JSON.parse(message);
if (data.method === "selectOverlay") {
if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) {
if (wantDebug) {
print("setting selection to overlay " + data.overlayID);
}
var entity = entityIconOverlayManager.findEntity(data.overlayID);
if (entity !== null) {
if (createApp.hmdMultiSelectMode) {
selectionManager.addEntity(entity, true, this);
} else {
selectionManager.setSelections([entity], this);
}
}
}
}
}
function handleMessagesReceived(channel, message, sender) {
switch( channel ){
case 'entityToolUpdates': {
handleOverlaySelectionToolUpdates( channel, message, sender );
break;
}
default: {
return;
}
}
}
Messages.subscribe("entityToolUpdates");
Messages.messageReceived.connect(handleMessagesReceived);
var mouseHasMovedSincePress = false;
var mousePressStartTime = 0;
var mousePressStartPosition = {
x: 0,
y: 0
};
var mouseDown = false;
function mousePressEvent(event) {
mouseDown = true;
mousePressStartPosition = {
x: event.x,
y: event.y
};
mousePressStartTime = Date.now();
mouseHasMovedSincePress = false;
mouseCapturedByTool = false;
if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
mouseCapturedByTool = true;
return;
}
if (isActive) {
if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
// Event handled; do nothing.
return;
}
}
}
var mouseCapturedByTool = false;
var lastMousePosition = null;
var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms
var CLICK_MOVE_DISTANCE_THRESHOLD = 20;
var IDLE_MOUSE_TIMEOUT = 200;
var lastMouseMoveEvent = null;
function mouseMoveEventBuffered(event) {
lastMouseMoveEvent = event;
}
function mouseMove(event) {
if (mouseDown && !mouseHasMovedSincePress) {
var timeSincePressMicro = Date.now() - mousePressStartTime;
var dX = mousePressStartPosition.x - event.x;
var dY = mousePressStartPosition.y - event.y;
var sqDist = (dX * dX) + (dY * dY);
// If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved
// less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move
// yet. The goal is to provide mouse clicks that are more lenient to small movements.
if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) {
return;
}
mouseHasMovedSincePress = true;
}
if (!isActive) {
return;
}
// allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing
if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) {
return;
}
lastMousePosition = {
x: event.x,
y: event.y
};
}
function mouseReleaseEvent(event) {
mouseDown = false;
if (lastMouseMoveEvent) {
mouseMove(lastMouseMoveEvent);
lastMouseMoveEvent = null;
}
if (propertyMenu.mouseReleaseEvent(event)) {
return true;
}
if (isActive && selectionManager.hasSelection()) {
tooltip.show(false);
}
if (mouseCapturedByTool) {
return;
}
cameraManager.mouseReleaseEvent(event);
if (!mouseHasMovedSincePress) {
mouseClickEvent(event);
}
}
function wasTabletOrEditHandleClicked(event) {
var rayPick = Camera.computePickRay(event.x, event.y);
var result = Overlays.findRayIntersection(rayPick, true);
if (result.intersects) {
var overlayID = result.overlayID;
var tabletIDs = getMainTabletIDs();
if (tabletIDs.indexOf(overlayID) >= 0) {
return true;
} else if (selectionDisplay.isEditHandle(overlayID)) {
return true;
}
}
return false;
}
function mouseClickEvent(event) {
var wantDebug = false;
var result, properties, tabletClicked;
if (isActive && event.isLeftButton) {
result = findClickedEntity(event);
var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event);
if (tabletOrEditHandleClicked) {
return;
}
if (result === null || result === undefined) {
if (!event.isShifted) {
if (!keepSelectedOnNextClick) {
selectionManager.clearSelections(this);
}
keepSelectedOnNextClick = false;
}
return;
}
toolBar.setActive(true);
var pickRay = result.pickRay;
var foundEntity = result.entityID;
if (HMD.tabletID && foundEntity === HMD.tabletID) {
return;
}
properties = Entities.getEntityProperties(foundEntity);
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
if (wantDebug) {
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
}
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X === A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) *
180 / Math.PI;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) &&
(allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK && selectionManager.editEnabled) {
selectedEntityID = foundEntity;
orientation = MyAvatar.orientation;
intersection = createApp.rayPlaneIntersection(pickRay, P, Quat.getForward(orientation));
if (!event.isShifted) {
selectionManager.setSelections([foundEntity], this);
} else {
selectionManager.addEntity(foundEntity, true, this);
}
selectionManager.saveProperties();
if (wantDebug) {
print("Model selected: " + foundEntity);
}
selectionDisplay.select(selectedEntityID, event);
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
}
} else if (event.isRightButton) {
result = findClickedEntity(event);
if (result) {
if (SHOULD_SHOW_PROPERTY_MENU !== true) {
return;
}
properties = Entities.getEntityProperties(result.entityID);
propertyMenu.setPosition(event.x, event.y);
propertyMenu.show();
} else {
propertyMenu.hide();
}
}
}
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEventBuffered);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var originalLightsArePickable = Entities.getLightsArePickable();
function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Undo",
shortcutKey: 'Ctrl+Z',
position: 0,
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Redo",
shortcutKey: 'Ctrl+Y',
position: 1,
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: MENU_CREATE_SEPARATOR,
isSeparator: true
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: MENU_IMPORT_FROM_FILE,
afterItem: MENU_CREATE_SEPARATOR
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: MENU_IMPORT_FROM_URL,
afterItem: MENU_IMPORT_FROM_FILE
});
Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES);
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_CREATE_ENTITIES_GRABBABLE,
position: 0,
isCheckable: true,
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false)
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_ALLOW_SELECTION_LARGE,
afterItem: MENU_CREATE_ENTITIES_GRABBABLE,
isCheckable: true,
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true)
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_ALLOW_SELECTION_SMALL,
afterItem: MENU_ALLOW_SELECTION_LARGE,
isCheckable: true,
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true)
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_ALLOW_SELECTION_LIGHTS,
afterItem: MENU_ALLOW_SELECTION_SMALL,
isCheckable: true,
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false)
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_AUTO_FOCUS_ON_SELECT,
afterItem: MENU_ALLOW_SELECTION_LIGHTS,
isCheckable: true,
isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true"
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_EASE_ON_FOCUS,
afterItem: MENU_AUTO_FOCUS_ON_SELECT,
isCheckable: true,
isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true"
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE,
afterItem: MENU_EASE_ON_FOCUS,
isCheckable: true,
isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false"
});
Menu.addMenuItem({
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS,
afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE
});
Entities.setLightsArePickable(false);
}
setupModelMenus(); // do this when first running our script.
function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Undo");
Menu.removeMenuItem("Edit", "Redo");
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE);
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS);
Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES);
Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_URL);
Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_FILE);
Menu.removeSeparator("Edit", MENU_CREATE_SEPARATOR);
}
Script.scriptEnding.connect(function () {
toolBar.setActive(false);
Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT));
Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL));
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS));
progressDialog.cleanup();
cleanupModelMenus();
tooltip.cleanup();
selectionDisplay.cleanup();
entityShapeVisualizer.cleanup();
Entities.setLightsArePickable(originalLightsArePickable);
Overlays.deleteOverlay(importingSVOImageOverlay);
Overlays.deleteOverlay(importingSVOTextOverlay);
Controller.keyReleaseEvent.disconnect(keyReleaseEvent);
Controller.keyPressEvent.disconnect(keyPressEvent);
Controller.mousePressEvent.disconnect(mousePressEvent);
Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered);
Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent);
Messages.messageReceived.disconnect(handleMessagesReceived);
Messages.unsubscribe("entityToolUpdates");
createButton = null;
});
var lastOrientation = Camera.orientation;
var lastPosition = Camera.position;
// Do some stuff regularly, like check for placement of various overlays
Script.update.connect(function (deltaTime) {
progressDialog.move();
selectionDisplay.checkControllerMove();
var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1);
var dPosition = Vec3.distance(Camera.position, lastPosition);
if (dOrientation > 0.001 || dPosition > 0.001) {
propertyMenu.hide();
lastOrientation = Camera.orientation;
lastPosition = Camera.position;
}
if (lastMouseMoveEvent) {
mouseMove(lastMouseMoveEvent);
lastMouseMoveEvent = null;
}
});
function insideBox(center, dimensions, point) {
return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) &&
(Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) &&
(Math.abs(point.z - center.z) <= (dimensions.z / 2.0));
}
createApp.selectAllEntitiesInCurrentSelectionBox = function(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);
}
}
createApp.unparentSelectedEntities = function() {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
var parentCheck = false;
if (selectedEntities.length < 1) {
audioFeedback.rejection();
Window.notifyEditError("You must have an entity selected in order to unparent it.");
return;
}
selectedEntities.forEach(function (id, index) {
var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NONE) {
parentCheck = true;
}
Entities.editEntity(id, {parentID: null});
return true;
});
if (parentCheck) {
audioFeedback.confirmation();
if (selectedEntities.length > 1) {
Window.notify("Entities unparented");
} else {
Window.notify("Entity unparented");
}
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
if (selectedEntities.length > 1) {
Window.notify("Selected Entities have no parents");
} else {
Window.notify("Selected Entity does not have a parent");
}
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
createApp.parentSelectedEntities = function() {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
if (selectedEntities.length <= 1) {
audioFeedback.rejection();
Window.notifyEditError("You must have multiple entities selected in order to parent them");
return;
}
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) {
audioFeedback.confirmation();
Window.notify("Entities parented");
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
Window.notify("Entities are already parented to last");
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
createApp.deleteSelectedEntities = function() {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var deletedIDs = [];
SelectionManager.saveProperties();
var savedProperties = [];
var newSortedSelection = sortSelectedEntities(selectionManager.selections);
var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType');
for (var i = 0; i < newSortedSelection.length; ++i) {
var entityID = newSortedSelection[i];
var initialProperties = SelectionManager.savedProperties[entityID];
if (initialProperties.locked ||
(initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) {
continue;
}
var children = Entities.getChildrenIDs(entityID);
var childList = [];
recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType);
savedProperties.push({
entityID: entityID,
properties: initialProperties,
children: childList
});
deletedIDs.push(entityID);
Entities.deleteEntity(entityID);
}
if (savedProperties.length > 0) {
SelectionManager.clearSelections(this);
createApp.pushCommandForSelections([], savedProperties);
entityListTool.deleteEntities(deletedIDs);
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
createApp.toggleSelectedEntitiesLocked = function() {
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);
}
}
createApp.toggleSelectedEntitiesVisible = function() {
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())) {
toolBar.toggle();
}
importSVO(importURL);
}
}
function onPromptTextChanged(prompt) {
Window.promptTextChanged.disconnect(onPromptTextChanged);
if (prompt !== "") {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
toolBar.toggle();
}
importSVO(prompt);
}
}
function onPromptTextChangedDefaultRadiusUserPref(prompt) {
Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref);
if (prompt !== "") {
var radius = parseInt(prompt);
if (radius < 0 || isNaN(radius)){
radius = 100;
}
Settings.setValue(createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius);
}
}
function handleMenuEvent(menuItem) {
if (menuItem === MENU_ALLOW_SELECTION_SMALL) {
allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL);
} else if (menuItem === MENU_ALLOW_SELECTION_LARGE) {
allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE);
} else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) {
Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS));
} else if (menuItem === "Delete") {
createApp.deleteSelectedEntities();
} else if (menuItem === "Undo") {
createApp.undoHistory.undo();
} else if (menuItem === "Redo") {
createApp.undoHistory.redo();
} else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) {
entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE));
} else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) {
Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem));
} else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) {
Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref);
Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100));
} else if (menuItem === MENU_IMPORT_FROM_FILE) {
createApp.importEntitiesFromFile();
} else if (menuItem === MENU_IMPORT_FROM_URL) {
createApp.importEntitiesFromUrl();
}
tooltip.show(false);
}
var HALF_TREE_SCALE = 16384;
createApp.getPositionToCreateEntity = function(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, importEntityHostType) {
importEntityHostType = importEntityHostType || "domain";
if (!Entities.canRez() && !Entities.canRezTmp()) {
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 = createApp.getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
}
if (position !== null && position !== undefined) {
var pastedEntityIDs = Clipboard.pasteEntities(position, importEntityHostType);
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", "ProceduralParticleEffect", "Sound"];
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 = createApp.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);
}
// Hacks to get the menu bar buttons to work
// Copy
if (event.text.toLowerCase() === "c" && event.isControl && !event.isShifted) {
selectionManager.copySelectedEntities();
}
// Paste
if (event.text.toLowerCase() === "v" && event.isControl && !event.isShifted) {
selectionManager.pasteEntities();
}
// Cut
if (event.text.toLowerCase() === "x" && event.isControl && !event.isShifted) {
selectionManager.cutSelectedEntities();
}
// Delete - This uses the physical 'delete' key on a keyboard.
if (event.text.toLowerCase() === "delete" && !event.isControl && !event.isShifted) {
createApp.deleteSelectedEntities();
}
};
var keyReleaseEvent = function (event) {
if (isActive) {
cameraManager.keyReleaseEvent(event);
}
};
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);
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
createApp.setCameraFocusToSelection();
}
}
function gridKey(value) {
if (value === 0) { // on release
createApp.alignGridToSelection();
}
}
function viewGridKey(value) {
if (value === 0) { // on release
createApp.toggleGridVisibility();
}
}
function snapKey(value) {
if (value === 0) { // on release
entityListTool.toggleSnapToGrid();
}
}
function gridToAvatarKey(value) {
if (value === 0) { // on release
createApp.alignGridToAvatar();
}
}
createApp.rotateAsNextClickedSurfaceKey = function(value) {
if (value === 0) { // on release
createApp.rotateAsNextClickedSurface();
}
}
function quickRotate90xKey(value) {
if (value === 0) { // on release
selectionDisplay.rotate90degreeSelection("X");
}
}
function quickRotate90yKey(value) {
if (value === 0) { // on release
selectionDisplay.rotate90degreeSelection("Y");
}
}
function quickRotate90zKey(value) {
if (value === 0) { // on release
selectionDisplay.rotate90degreeSelection("Z");
}
}
function recursiveAdd(newParentID, parentData) {
if (parentData.children !== undefined) {
var children = parentData.children;
for (var i = 0; i < children.length; i++) {
var childProperties = children[i].properties;
childProperties.parentID = newParentID;
var newChildID = Entities.addEntity(childProperties);
recursiveAdd(newChildID, children[i]);
}
}
}
var UndoHistory = function(onUpdate) {
this.history = [];
// The current position is the index of the last executed action in the history array.
//
// -1 0 1 2 3 <- position
// A B C D <- actions in history
//
// If our lastExecutedIndex is 1, the last executed action is B.
// If we undo, we undo B (index 1). If we redo, we redo C (index 2).
this.lastExecutedIndex = -1;
this.enabled = true;
this.onUpdate = onUpdate;
};
UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) {
if (!this.enabled) {
return;
}
// Delete any history following the last executed action.
this.history.splice(this.lastExecutedIndex + 1);
this.history.push({
undoFn: undoFn,
undoArgs: undoArgs,
redoFn: redoFn,
redoArgs: redoArgs
});
this.lastExecutedIndex++;
if (this.onUpdate) {
this.onUpdate();
}
};
UndoHistory.prototype.setEnabled = function(enabled) {
this.enabled = enabled;
if (this.onUpdate) {
this.onUpdate();
}
};
UndoHistory.prototype.canUndo = function() {
return this.enabled && this.lastExecutedIndex >= 0;
};
UndoHistory.prototype.canRedo = function() {
return this.enabled && this.lastExecutedIndex < this.history.length - 1;
};
UndoHistory.prototype.undo = function() {
if (!this.canUndo()) {
console.warn("Cannot undo action");
return;
}
var command = this.history[this.lastExecutedIndex];
command.undoFn(command.undoArgs);
this.lastExecutedIndex--;
if (this.onUpdate) {
this.onUpdate();
}
};
UndoHistory.prototype.redo = function() {
if (!this.canRedo()) {
console.warn("Cannot redo action");
return;
}
var command = this.history[this.lastExecutedIndex + 1];
command.redoFn(command.redoArgs);
this.lastExecutedIndex++;
if (this.onUpdate) {
this.onUpdate();
}
};
function updateUndoRedoMenuItems() {
Menu.setMenuEnabled("Edit > Undo", createApp.undoHistory.canUndo());
Menu.setMenuEnabled("Edit > Redo", createApp.undoHistory.canRedo());
}
createApp.undoHistory = new UndoHistory(updateUndoRedoMenuItems);
updateUndoRedoMenuItems();
// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently
// possible to create an entity with a specific id, earlier undo commands to the deleted entity
// will fail if there isn't a way to find the new entity id.
var DELETED_ENTITY_MAP = {};
function applyEntityProperties(data) {
var editEntities = data.editEntities;
var createEntities = data.createEntities;
var deleteEntities = data.deleteEntities;
var selectedEntityIDs = [];
var selectEdits = createEntities.length === 0 || !data.selectCreated;
var i, entityID, entityProperties;
for (i = 0; i < createEntities.length; i++) {
entityID = createEntities[i].entityID;
entityProperties = createEntities[i].properties;
var newEntityID = Entities.addEntity(entityProperties);
recursiveAdd(newEntityID, createEntities[i]);
DELETED_ENTITY_MAP[entityID] = newEntityID;
if (data.selectCreated) {
selectedEntityIDs.push(newEntityID);
}
}
for (i = 0; i < deleteEntities.length; i++) {
entityID = deleteEntities[i].entityID;
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
entityID = DELETED_ENTITY_MAP[entityID];
}
Entities.deleteEntity(entityID);
var index = selectedEntityIDs.indexOf(entityID);
if (index >= 0) {
selectedEntityIDs.splice(index, 1);
}
}
for (i = 0; i < editEntities.length; i++) {
entityID = editEntities[i].entityID;
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
entityID = DELETED_ENTITY_MAP[entityID];
}
entityProperties = editEntities[i].properties;
if (entityProperties !== null) {
Entities.editEntity(entityID, entityProperties);
}
if (selectEdits) {
selectedEntityIDs.push(entityID);
}
}
// We might be getting an undo while edit.js is disabled. If that is the case, don't set
// our selections, causing the edit widgets to display.
if (isActive) {
selectionManager.setSelections(selectedEntityIDs, this);
selectionManager.saveProperties();
}
}
// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the
// redo command, and the saved properties for the undo command. Also, include create and delete entity data.
createApp.pushCommandForSelections = function (createdEntityData, deletedEntityData, doNotSaveEditProperties) {
doNotSaveEditProperties = false;
var undoData = {
editEntities: [],
createEntities: deletedEntityData || [],
deleteEntities: createdEntityData || [],
selectCreated: true
};
var redoData = {
editEntities: [],
createEntities: createdEntityData || [],
deleteEntities: deletedEntityData || [],
selectCreated: true
};
for (var i = 0; i < SelectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID];
var currentProperties = null;
if (!initialProperties) {
continue;
}
if (doNotSaveEditProperties) {
initialProperties = null;
} else {
currentProperties = Entities.getEntityProperties(entityID);
}
undoData.editEntities.push({
entityID: entityID,
properties: initialProperties
});
redoData.editEntities.push({
entityID: entityID,
properties: currentProperties
});
}
createApp.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 (HMD.active && visible) {
webView.setLandscape(true);
} else {
if (!visible) {
createApp.hmdMultiSelectMode = false;
webView.setLandscape(false);
}
}
if (blockPropertyUpdates) {
return;
}
var data = {
type: 'update',
spaceMode: selectionDisplay.getSpaceMode(),
isPropertiesToolUpdate: caller === this,
};
if (selectionUpdated) {
resetScriptStatus();
if (selectionManager.selections.length !== 1) {
if (statusMonitor !== null) {
statusMonitor.stop();
statusMonitor = null;
}
currentSelectedEntityID = null;
} else if (currentSelectedEntityID !== selectionManager.selections[0]) {
if (statusMonitor !== null) {
statusMonitor.stop();
}
var entityID = selectionManager.selections[0];
currentSelectedEntityID = entityID;
statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus);
}
}
var selections = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entity = {};
entity.id = selectionManager.selections[i];
entity.properties = Entities.getEntityProperties(selectionManager.selections[i]);
if (entity.properties.rotation !== undefined) {
entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation);
}
if (entity.properties.localRotation !== undefined) {
entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation);
}
if (entity.properties.emitOrientation !== undefined) {
entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation);
}
if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) {
entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction);
entity.properties.keyLight.direction.z = 0.0;
}
selections.push(entity);
}
data.selections = selections;
emitScriptEvent(data);
}
selectionManager.addEventListener(updateSelections, this);
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
} catch(e) {
return;
}
var i, properties, dY, diff, newPosition;
if (data.type === "update") {
if (data.properties || data.propertiesMap) {
var propertiesMap = data.propertiesMap;
if (propertiesMap === undefined) {
propertiesMap = [{
entityIDs: data.ids,
properties: data.properties,
}];
}
var sendListUpdate = false;
propertiesMap.forEach(function(propertiesObject) {
var properties = propertiesObject.properties;
var updateEntityIDs = propertiesObject.entityIDs;
if (properties.dynamic === false) {
// this object is leaving dynamic, so we zero its velocities
properties.localVelocity = Vec3.ZERO;
properties.localAngularVelocity = Vec3.ZERO;
}
if (properties.rotation !== undefined) {
properties.rotation = Quat.fromVec3Degrees(properties.rotation);
}
if (properties.localRotation !== undefined) {
properties.localRotation = Quat.fromVec3Degrees(properties.localRotation);
}
if (properties.emitOrientation !== undefined) {
properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation);
}
if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) {
var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction);
if (properties.keyLight.direction.x === undefined) {
properties.keyLight.direction.x = currentKeyLightDirection.x;
}
if (properties.keyLight.direction.y === undefined) {
properties.keyLight.direction.y = currentKeyLightDirection.y;
}
properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y);
}
updateEntityIDs.forEach(function (entityID) {
Entities.editEntity(entityID, properties);
});
if (properties.name !== undefined || properties.modelURL !== undefined || properties.imageURL !== undefined ||
properties.materialURL !== undefined || properties.visible !== undefined || properties.locked !== undefined) {
sendListUpdate = true;
}
});
if (sendListUpdate) {
entityListTool.sendUpdate();
}
}
if (data.onlyUpdateEntities) {
blockPropertyUpdates = true;
} else {
createApp.pushCommandForSelections();
SelectionManager.saveProperties();
}
selectionManager._update(false, this);
blockPropertyUpdates = false;
if (data.snapToGrid !== undefined) {
entityListTool.setListMenuSnapToGrid(data.snapToGrid);
}
} else if (data.type === 'saveUserData' || data.type === 'saveMaterialData' || data.type === 'saveParticleUpdateData' || data.type === 'saveParticleRenderData') {
data.ids.forEach(function(entityID) {
Entities.editEntity(entityID, data.properties);
});
} 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
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
}
} else if (data.action === "moveAllToGrid") {
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
properties = selectionManager.savedProperties[selectionManager.selections[i]];
var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2;
dY = grid.getOrigin().y - bottomY;
diff = {
x: 0,
y: dY,
z: 0
};
newPosition = Vec3.sum(properties.position, diff);
Entities.editEntity(selectionManager.selections[i], {
position: newPosition
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
}
} else if (data.action === "resetToNaturalDimensions") {
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
properties = selectionManager.savedProperties[selectionManager.selections[i]];
var naturalDimensions = properties.naturalDimensions;
// If any of the natural dimensions are not 0, resize
if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 &&
naturalDimensions.z === 0) {
Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" +
" is invalid or the model has not yet been loaded.");
} else {
Entities.editEntity(selectionManager.selections[i], {
dimensions: properties.naturalDimensions
});
}
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
}
} else if (data.action === "previewCamera") {
if (selectionManager.hasSelection()) {
Camera.mode = "entity";
Camera.cameraEntity = selectionManager.selections[0];
}
} else if (data.action === "rescaleDimensions") {
var multiplier = data.percentage / 100.0;
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
properties = selectionManager.savedProperties[selectionManager.selections[i]];
Entities.editEntity(selectionManager.selections[i], {
dimensions: Vec3.multiply(multiplier, properties.dimensions)
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
}
} else if (data.action === "reloadClientScripts") {
if (selectionManager.hasSelection()) {
var timestamp = Date.now();
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], {
scriptTimestamp: timestamp
});
}
}
} else if (data.action === "reloadServerScripts") {
if (selectionManager.hasSelection()) {
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.reloadServerScripts(selectionManager.selections[i]);
}
}
} else if (data.action === "copyPosition") {
if (selectionManager.selections.length === 1) {
selectionManager.saveProperties();
properties = selectionManager.savedProperties[selectionManager.selections[0]];
copiedPosition = properties.position;
Window.copyToClipboard(JSON.stringify(copiedPosition));
}
} else if (data.action === "copyRotation") {
if (selectionManager.selections.length === 1) {
selectionManager.saveProperties();
properties = selectionManager.savedProperties[selectionManager.selections[0]];
copiedRotation = properties.rotation;
Window.copyToClipboard(JSON.stringify(copiedRotation));
}
} else if (data.action === "copyDimensions") {
if (selectionManager.selections.length === 1) {
selectionManager.saveProperties();
properties = selectionManager.savedProperties[selectionManager.selections[0]];
copiedDimensions = properties.dimensions;
Window.copyToClipboard(JSON.stringify(copiedDimensions));
}
} else if (data.action === "pastePosition") {
if (copiedPosition !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], {
position: copiedPosition
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
}
} else if (data.action === "pasteDimensions") {
if (copiedDimensions !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], {
dimensions: copiedDimensions
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
}
} else if (data.action === "pasteRotation") {
if (copiedRotation !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], {
rotation: copiedRotation
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
}
} else if (data.action === "setRotationToZero") {
if (selectionManager.selections.length === 1 && SelectionManager.hasUnlockedSelection()) {
selectionManager.saveProperties();
var parentState = createApp.getParentState(selectionManager.selections[0]);
if ((parentState === "PARENT_CHILDREN" || parentState === "CHILDREN") && selectionDisplay.getSpaceMode() === "local" ) {
Entities.editEntity(selectionManager.selections[0], {
localRotation: Quat.IDENTITY
});
} else {
Entities.editEntity(selectionManager.selections[0], {
rotation: Quat.IDENTITY
});
}
createApp.pushCommandForSelections();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
}
}
} else if (data.type === "propertiesPageReady") {
emitScriptEvent({
type: 'urlPermissionChanged',
canViewAssetURLs: Entities.canViewAssetURLs(),
});
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.NONE) {
var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type;
if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") {
parentModelData = Graphics.getModel(properties.parentID);
} else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") {
parentModelData = {};
parentModelData.numMeshes = 1;
parentModelData.materialNames = [];
}
}
emitScriptEvent({
type: 'materialTargetReply',
entityID: data.entityID,
materialTargetData: parentModelData,
});
} else if (data.type === "zoneListRequest") {
emitScriptEvent({
type: 'zoneListRequest',
zones: getExistingZoneList()
});
} else if (data.type === "importUiBrowse") {
let fileToImport = Window.browse("Select .json to Import", "", "*.json");
if (fileToImport !== null) {
emitScriptEvent({
type: 'importUi_SELECTED_FILE',
file: fileToImport
});
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiImport") {
if ((data.entityHostType === "domain" && Entities.canAdjustLocks() && Entities.canRez()) ||
(data.entityHostType === "avatar" && Entities.canRezAvatarEntities())) {
if (data.positioningMode === "avatar") {
importSVO(data.jsonURL, data.entityHostType);
} else {
if (Clipboard.importEntities(data.jsonURL)) {
let importedPastedEntities = Clipboard.pasteEntities(data.position, data.entityHostType);
if (importedPastedEntities.length === 0) {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "No Entity has been imported."
});
} else {
if (isActive) {
selectionManager.setSelections(importedPastedEntities, this);
}
emitScriptEvent({type: 'importUi_IMPORT_CONFIRMATION'});
}
} else {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "Import Entities has failed."
});
}
}
} else {
emitScriptEvent({
type: 'importUi_IMPORT_ERROR',
reason: "You don't have permission to create in this domain."
});
}
} else if (data.type === "importUiGoBack") {
if (location.canGoBack()) {
location.goBack();
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiGoTutorial") {
Window.location = "file:///~/serverless/tutorial.json";
} else if (data.type === "importUiGetCopiedPosition") {
if (copiedPosition !== undefined) {
emitScriptEvent({
type: 'importUi_POSITION_TO_PASTE',
position: copiedPosition
});
} else {
audioFeedback.rejection();
}
} else if (data.type === "importUiPersistData") {
importUiPersistedData = data.importUiPersistedData;
} else if (data.type === "importUiGetPersistData") {
emitScriptEvent({
type: 'importUi_LOAD_DATA',
importUiPersistedData: importUiPersistedData
});
}
};
HMD.displayModeChanged.connect(function() {
emitScriptEvent({
type: 'hmdActiveChanged',
hmdActive: HMD.active,
});
});
Entities.canViewAssetURLsChanged.connect((value) => {
emitScriptEvent({
type: 'urlPermissionChanged',
canViewAssetURLs: value,
});
});
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() {
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);
}
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);
mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey);
mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey);
mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey);
mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey);
mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey);
mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey);
mapping.from([Controller.Hardware.Keyboard["0"]]).to(createApp.rotateAsNextClickedSurfaceKey);
mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey);
mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey);
mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey);
mapping.from([Controller.Hardware.Keyboard.C])
.when([Controller.Hardware.Keyboard.Control])
.to(whenReleased(function() { selectionManager.copySelectedEntities() }));
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() { createApp.undoHistory.redo() }));
mapping.from([Controller.Hardware.Keyboard.P])
.when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift])
.to(whenReleased(function() { createApp.unparentSelectedEntities(); }));
mapping.from([Controller.Hardware.Keyboard.P])
.when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift])
.to(whenReleased(function() { createApp.parentSelectedEntities(); }));
createApp.keyUpEventFromUIWindow = function(keyUpEvent) {
var WANT_DEBUG_MISSING_SHORTCUTS = false;
var pressedValue = 0.0;
if (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace") {
createApp.deleteSelectedEntities();
} else if (keyUpEvent.keyCodeString === "T") {
toggleKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "F") {
focusKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "J") {
gridKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "G") {
viewGridKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "H") {
snapKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "K") {
gridToAvatarKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "0") {
createApp.rotateAsNextClickedSurfaceKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "7") {
quickRotate90xKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "8") {
quickRotate90yKey(pressedValue);
} else if (keyUpEvent.keyCodeString === "9") {
quickRotate90zKey(pressedValue);
} else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") {
selectionManager.copySelectedEntities();
} else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") {
selectionManager.duplicateSelection();
} else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") {
createApp.undoHistory.undo(); // undo is only handled via handleMenuItem on Mac
} else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") {
createApp.parentSelectedEntities();
} else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") {
createApp.unparentSelectedEntities();
} else if (!isOnMacPlatform &&
((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") ||
(keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) {
createApp.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) {
};
var propertiesTool = new PropertiesTool();
selectionDisplay.onSpaceModeChange = function(spaceMode) {
entityListTool.setSpaceMode(spaceMode);
propertiesTool.setSpaceMode(spaceMode);
};
function getExistingZoneList() {
var center = { "x": 0, "y": 0, "z": 0 };
var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_SCAN_RADIUS);
var listExistingZones = [];
var thisZone = {};
var properties;
for (var k = 0; k < existingZoneIDs.length; k++) {
properties = Entities.getEntityProperties(existingZoneIDs[k], ["name"]);
thisZone = {
"id": existingZoneIDs[k],
"name": properties.name
};
listExistingZones.push(thisZone);
}
listExistingZones.sort(zoneSortOrder);
return listExistingZones;
}
function zoneSortOrder(a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
if (nameA > nameB) {
return 1;
} else if (nameA < nameB) {
return -1;
}
if (a.name > b.name) {
return 1;
} else if (a.name < b.name) {
return -1;
}
return 0;
}
createApp.getParentState = function(id) {
var state = "NONE";
var properties = Entities.getEntityProperties(id, ["parentID"]);
var children = createApp.getDomainOnlyChildrenIDs(id);
if (properties.parentID !== Uuid.NONE) {
if (children.length > 0) {
state = "PARENT_CHILDREN";
} else {
state = "CHILDREN";
}
} else {
if (children.length > 0) {
state = "PARENT";
}
}
return state;
}
createApp.getDomainOnlyChildrenIDs = function(id) {
var allChildren = Entities.getChildrenIDs(id);
var realChildren = [];
var properties;
for (var i = 0; i < allChildren.length; i++) {
properties = Entities.getEntityProperties(allChildren[i], ["name"]);
if (properties.name !== undefined && properties.name !== entityShapeVisualizerSessionName) {
realChildren.push(allChildren[i]);
}
}
return realChildren;
}
createApp.importEntitiesFromFile = function() {
Window.browseChanged.connect(onFileOpenChanged);
Window.browseAsync("Select .json to Import", "", "*.json");
}
createApp.importEntitiesFromUrl = function() {
Window.promptTextChanged.connect(onPromptTextChanged);
Window.promptAsync("URL of a .json to import", "");
}
createApp.setCameraFocusToSelection = function() {
cameraManager.enable();
if (selectionManager.hasSelection()) {
cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
}
createApp.alignGridToSelection = function() {
if (selectionManager.hasSelection()) {
if (!grid.getVisible()) {
grid.setVisible(true, true);
}
grid.moveToSelection();
}
}
createApp.alignGridToAvatar = function() {
if (!grid.getVisible()) {
grid.setVisible(true, true);
}
grid.moveToAvatar();
}
createApp.toggleGridVisibility = function() {
if (!grid.getVisible()) {
grid.setVisible(true, true);
} else {
grid.setVisible(false, true);
}
}
createApp.rotateAsNextClickedSurface = function() {
if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected, or the selection is locked.");
createApp.expectingRotateAsClickedSurface = false;
} else {
createApp.expectingRotateAsClickedSurface = true;
}
}
}()); // END LOCAL_SCOPE