diff --git a/examples/edit.js b/examples/edit.js index e7c8c897ac..c85ec26298 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -29,12 +29,15 @@ Script.include([ "libraries/entityCameraTool.js", "libraries/gridTool.js", "libraries/entityList.js", + "libraries/lightOverlayManager.js", ]); var selectionDisplay = SelectionDisplay; var selectionManager = SelectionManager; var entityPropertyDialogBox = EntityPropertyDialogBox; +var lightOverlayManager = new LightOverlayManager(); + var cameraManager = new CameraManager(); var grid = Grid(); @@ -45,6 +48,7 @@ var entityListTool = EntityListTool(); selectionManager.addEventListener(function() { selectionDisplay.updateHandles(); + lightOverlayManager.updatePositions(); }); var windowDimensions = Controller.getViewportDimensions(); @@ -70,13 +74,17 @@ var DEFAULT_DIMENSIONS = { z: DEFAULT_DIMENSION }; +var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool"; var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode"; var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled"; var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; +var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode"; var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain." @@ -186,6 +194,8 @@ var toolBar = (function () { alpha: 0.9, visible: false }); + + that.setActive(false); } that.setActive = function(active) { @@ -214,6 +224,7 @@ var toolBar = (function () { } } toolBar.selectTool(activeButton, isActive); + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); }; // Sets visibility of tool buttons, excluding the power button @@ -354,8 +365,8 @@ var toolBar = (function () { if (position.x > 0 && position.y > 0 && position.z > 0) { placingEntityID = Entities.addEntity({ type: "Light", - position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS), - dimensions: DEFAULT_DIMENSIONS, + position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS), + dimensions: DEFAULT_LIGHT_DIMENSIONS, isSpotlight: false, diffuseColor: { red: 255, green: 255, blue: 255 }, ambientColor: { red: 255, green: 255, blue: 255 }, @@ -469,12 +480,31 @@ function rayPlaneIntersection(pickRay, point, normal) { function findClickedEntity(event) { var pickRay = Camera.computePickRay(event.x, event.y); - var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var lightResult = lightOverlayManager.findRayIntersection(pickRay); + lightResult.accurate = true; - if (!foundIntersection.accurate) { + var result; + + if (!entityResult.intersects && !lightResult.intersects) { + return null; + } else if (entityResult.intersects && !lightResult.intersects) { + result = entityResult; + } else if (!entityResult.intersects && lightResult.intersects) { + result = lightResult; + } else { + if (entityResult.distance < lightResult.distance) { + result = entityResult; + } else { + result = lightResult; + } + } + + if (!result.accurate) { return null; } - var foundEntity = foundIntersection.entityID; + + var foundEntity = result.entityID; if (!foundEntity.isKnownID) { var identify = Entities.identifyEntity(foundEntity); @@ -730,6 +760,8 @@ function setupModelMenus() { isCheckable: true, isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) == "true" }); Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_AUTO_FOCUS_ON_SELECT, isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" }); + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_LIGHTS_IN_EDIT_MODE, afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE) == "true" }); Entities.setLightsArePickable(false); } @@ -756,11 +788,13 @@ function cleanupModelMenus() { Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED); Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT); Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("View", MENU_SHOW_LIGHTS_IN_EDIT_MODE); } Script.scriptEnding.connect(function() { 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_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); progressDialog.cleanup(); toolBar.cleanup(); @@ -837,6 +871,8 @@ function handeMenuEvent(menuItem) { } } else if (menuItem == "Entity List...") { entityListTool.toggleVisible(); + } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); } tooltip.show(false); } diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js new file mode 100644 index 0000000000..8032d77c49 --- /dev/null +++ b/examples/libraries/lightOverlayManager.js @@ -0,0 +1,135 @@ +var POINT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/point-light.svg"; +var SPOT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/spot-light.svg"; + +LightOverlayManager = function() { + var self = this; + + var visible = false; + + // List of all created overlays + var allOverlays = []; + + // List of overlays not currently being used + var unusedOverlays = []; + + // Map from EntityItemID.id to overlay id + var entityOverlays = {}; + + // Map from EntityItemID.id to EntityItemID object + var entityIDs = {}; + + this.updatePositions = function(ids) { + for (var id in entityIDs) { + var entityID = entityIDs[id]; + var properties = Entities.getEntityProperties(entityID); + Overlays.editOverlay(entityOverlays[entityID.id], { + position: properties.position + }); + } + }; + + this.findRayIntersection = function(pickRay) { + var result = Overlays.findRayIntersection(pickRay); + var found = false; + + if (result.intersects) { + for (var id in entityOverlays) { + if (result.overlayID == entityOverlays[id]) { + result.entityID = entityIDs[id]; + found = true; + break; + } + } + + if (!found) { + result.intersects = false; + } + } + + return result; + }; + + this.setVisible = function(isVisible) { + if (visible != isVisible) { + visible = isVisible; + for (var id in entityOverlays) { + Overlays.editOverlay(entityOverlays[id], { visible: visible }); + } + } + }; + + // Allocate or get an unused overlay + function getOverlay() { + if (unusedOverlays.length == 0) { + var overlay = Overlays.addOverlay("billboard", { + }); + allOverlays.push(overlay); + } else { + var overlay = unusedOverlays.pop(); + }; + return overlay; + } + + function releaseOverlay(overlay) { + unusedOverlays.push(overlay); + Overlays.editOverlay(overlay, { visible: false }); + } + + function addEntity(entityID) { + var properties = Entities.getEntityProperties(entityID); + if (properties.type == "Light" && !(entityID.id in entityOverlays)) { + var overlay = getOverlay(); + entityOverlays[entityID.id] = overlay; + entityIDs[entityID.id] = entityID; + Overlays.editOverlay(overlay, { + position: properties.position, + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + rotation: Quat.fromPitchYawRollDegrees(0, 0, 270), + visible: visible, + alpha: 0.9, + scale: 0.5, + color: { red: 255, green: 255, blue: 255 } + }); + } + } + + function deleteEntity(entityID) { + if (entityID.id in entityOverlays) { + releaseOverlay(entityOverlays[entityID.id]); + delete entityOverlays[entityID.id]; + } + } + + function changeEntityID(oldEntityID, newEntityID) { + entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id]; + entityIDs[newEntityID.id] = newEntityID; + + delete entityOverlays[oldEntityID.id]; + delete entityIDs[oldEntityID.id]; + } + + function clearEntities() { + for (var id in entityOverlays) { + releaseOverlay(entityOverlays[id]); + } + entityOverlays = {}; + entityIDs = {}; + } + + Entities.addingEntity.connect(addEntity); + Entities.changingEntityID.connect(changeEntityID); + Entities.deletingEntity.connect(deleteEntity); + Entities.clearingEntities.connect(clearEntities); + + // Add existing entities + var ids = Entities.findEntities(MyAvatar.position, 100); + for (var i = 0; i < ids.length; i++) { + addEntity(ids[i]); + } + + Script.scriptEnding.connect(function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + }); +}; diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 88c097575b..3e3e823737 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -58,6 +58,7 @@ void BillboardOverlay::render(RenderArgs* args) { // rotate about vertical to face the camera rotation = Application::getInstance()->getCamera()->getRotation(); rotation *= glm::angleAxis(glm::pi(), glm::vec3(0.0f, 1.0f, 0.0f)); + rotation *= getRotation(); } else { rotation = getRotation(); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 5ef0db57ec..639798527a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -35,6 +35,25 @@ bool EntityScriptingInterface::canAdjustLocks() { } +void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { + if (_entityTree) { + disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); + disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); + disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); + } + + _entityTree = modelTree; + + if (_entityTree) { + connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); + connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); + connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); + } +} + + EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { // The application will keep track of creatorTokenID diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index bac018f2ae..9300149a98 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -58,7 +58,7 @@ public: virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); } - void setEntityTree(EntityTree* modelTree) { _entityTree = modelTree; } + void setEntityTree(EntityTree* modelTree); EntityTree* getEntityTree(EntityTree*) { return _entityTree; } public slots: @@ -129,6 +129,11 @@ signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); + void deletingEntity(const EntityItemID& entityID); + void addingEntity(const EntityItemID& entityID); + void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); + void clearingEntities(); + private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e952618c9f..e5a7fbee2f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -37,6 +37,8 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { } void EntityTree::eraseAllOctreeElements(bool createNewRoot) { + emit clearingEntities(); + // this would be a good place to clean up our entities... if (_simulation) { _simulation->lock(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 226bfa873a..5126682a99 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -163,6 +163,7 @@ signals: void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID); void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); + void clearingEntities(); private: