diff --git a/examples/edit.js b/examples/edit.js index 75105d4912..72938e5ed4 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -766,6 +766,10 @@ function setupModelMenus() { afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L", afterItem: "Allow Selecting of Small Models", isCheckable: true }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities In Box", shortcutKey: "CTRL+SHIFT+META+A", + afterItem: "Allow Selecting of Lights" }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities Touching Box", shortcutKey: "CTRL+SHIFT+META+T", + afterItem: "Select All Entities In Box" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Entities", shortcutKey: "CTRL+META+E", afterItem: "Models" }); @@ -795,6 +799,8 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem("Edit", "Select All Entities In Box"); + Menu.removeMenuItem("Edit", "Select All Entities Touching Box"); Menu.removeSeparator("File", "Models"); Menu.removeMenuItem("File", "Export Entities"); @@ -830,6 +836,45 @@ Script.update.connect(function (deltaTime) { selectionDisplay.checkMove(); }); +function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) + && (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) + && (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); +} + +function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); + + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null) { + 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({ x: 0, y: 0, z: 0 }, selectionManager.localDimensions, localPosition); + } + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities); + } +} + function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { print(" Delete Entities"); @@ -888,6 +933,10 @@ function handeMenuEvent(menuItem) { } } else if (menuItem == "Entity List...") { entityListTool.toggleVisible(); + } else if (menuItem == "Select All Entities In Box") { + selectAllEtitiesInCurrentSelectionBox(false); + } else if (menuItem == "Select All Entities Touching Box") { + selectAllEtitiesInCurrentSelectionBox(true); } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 639798527a..2585b5d33e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -204,8 +204,7 @@ EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center const EntityItem* closestEntity = _entityTree->findClosestEntity(center, radius); _entityTree->unlock(); if (closestEntity) { - result.id = closestEntity->getID(); - result.isKnownID = true; + result = closestEntity->getEntityItemID(); } } return result; @@ -227,10 +226,25 @@ QVector EntityScriptingInterface::findEntities(const glm::vec3& ce QVector entities; _entityTree->findEntities(center, radius, entities); _entityTree->unlock(); - + foreach (const EntityItem* entity, entities) { - EntityItemID thisEntityItemID(entity->getID(), UNKNOWN_ENTITY_TOKEN, true); - result << thisEntityItemID; + result << entity->getEntityItemID(); + } + } + return result; +} + +QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const { + QVector result; + if (_entityTree) { + _entityTree->lockForRead(); + AABox box(corner, dimensions); + QVector entities; + _entityTree->findEntities(box, entities); + _entityTree->unlock(); + + foreach (const EntityItem* entity, entities) { + result << entity->getEntityItemID(); } } return result; diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 9300149a98..5e75e2ae0d 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -87,10 +87,14 @@ public slots: /// will return a EntityItemID.isKnownID = false if no models are in the radius /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE EntityItemID findClosestEntity(const glm::vec3& center, float radius) const; - + /// finds models within the search sphere specified by the center point and radius /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; + + /// finds models within the search sphere specified by the center point and radius + /// this function will not find any models in script engine contexts which don't have access to models + Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e5a7fbee2f..4c39e70a00 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -528,6 +528,35 @@ void EntityTree::findEntities(const AACube& cube, QVector& foundEnt foundEntities.swap(args._foundEntities); } +class FindEntitiesInBoxArgs { +public: + FindEntitiesInBoxArgs(const AABox& box) + : _box(box), _foundEntities() { + } + + AABox _box; + QVector _foundEntities; +}; + +bool EntityTree::findInBoxOperation(OctreeElement* element, void* extraData) { + FindEntitiesInBoxArgs* args = static_cast(extraData); + if (element->getAACube().touches(args->_box)) { + EntityTreeElement* entityTreeElement = static_cast(element); + entityTreeElement->getEntities(args->_box, args->_foundEntities); + return true; + } + return false; +} + +// NOTE: assumes caller has handled locking +void EntityTree::findEntities(const AABox& box, QVector& foundEntities) { + FindEntitiesInBoxArgs args(box); + // NOTE: This should use recursion, since this is a spatial operation + recurseTreeWithOperation(findInBoxOperation, &args); + // swap the two lists of entity pointers instead of copy + foundEntities.swap(args._foundEntities); +} + EntityItem* EntityTree::findEntityByID(const QUuid& id) { EntityItemID entityID(id); return findEntityByEntityItemID(entityID); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5126682a99..41872dec28 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -111,12 +111,18 @@ public: /// \param foundEntities[out] vector of const EntityItem* /// \remark Side effect: any initial contents in foundEntities will be lost void findEntities(const glm::vec3& center, float radius, QVector& foundEntities); - + /// finds all entities that touch a cube /// \param cube the query cube in world-frame (meters) /// \param foundEntities[out] vector of non-const EntityItem* /// \remark Side effect: any initial contents in entities will be lost void findEntities(const AACube& cube, QVector& foundEntities); + + /// finds all entities that touch a box + /// \param box the query box in world-frame (meters) + /// \param foundEntities[out] vector of non-const EntityItem* + /// \remark Side effect: any initial contents in entities will be lost + void findEntities(const AABox& box, QVector& foundEntities); void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); @@ -173,6 +179,7 @@ private: static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData); + static bool findInBoxOperation(OctreeElement* element, void* extraData); static bool sendEntitiesOperation(OctreeElement* element, void* extraData); void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);