diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 2b7d6873cc..f3c52f5895 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "EntityServer.h" #include "EntityServerConsts.h" @@ -20,7 +21,8 @@ const char* MODEL_SERVER_NAME = "Entity"; const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server"; const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo"; -EntityServer::EntityServer(const QByteArray& packet) : OctreeServer(packet) { +EntityServer::EntityServer(const QByteArray& packet) + : OctreeServer(packet), _entitySimulation(NULL) { // nothing special to do here... } @@ -36,6 +38,12 @@ OctreeQueryNode* EntityServer::createOctreeQueryNode() { Octree* EntityServer::createTree() { EntityTree* tree = new EntityTree(true); tree->addNewlyCreatedHook(this); + if (!_entitySimulation) { + SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation(); + simpleSimulation->setEntityTree(tree); + tree->setSimulation(simpleSimulation); + _entitySimulation = simpleSimulation; + } return tree; } diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 563efed288..d072d18cdf 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -27,7 +27,6 @@ public: // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode(); - virtual Octree* createTree(); virtual char getMyNodeType() const { return NodeType::EntityServer; } virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; } virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; } @@ -46,7 +45,11 @@ public: public slots: void pruneDeletedEntities(); +protected: + virtual Octree* createTree(); + private: + EntitySimulation* _entitySimulation; }; #endif // hifi_EntityServer_h diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 27365c1e9d..4999594a98 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -62,7 +62,6 @@ public: // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode() = 0; - virtual Octree* createTree() = 0; virtual char getMyNodeType() const = 0; virtual PacketType getMyQueryMessageType() const = 0; virtual const char* getMyServerName() const = 0; @@ -132,6 +131,7 @@ public slots: void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); protected: + virtual Octree* createTree() = 0; bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result); bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result); bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result); diff --git a/assignment-client/src/voxels/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h index f4b6bd3a42..4d21695f33 100644 --- a/assignment-client/src/voxels/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -36,7 +36,6 @@ public: // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode(); - virtual Octree* createTree(); virtual char getMyNodeType() const { return NodeType::VoxelServer; } virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; } virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; } @@ -50,6 +49,7 @@ public: virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent); protected: + virtual Octree* createTree(); virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject); private: diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 1a43231f6f..40b5b78a31 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -89,11 +89,19 @@ SelectionManager = (function() { } } - that.addEntity = function(entityID) { + that.addEntity = function(entityID, toggleSelection) { if (entityID.isKnownID) { - var idx = that.selections.indexOf(entityID); + var idx = -1; + for (var i = 0; i < that.selections.length; i++) { + if (entityID.id == that.selections[i].id) { + idx = i; + break; + } + } if (idx == -1) { that.selections.push(entityID); + } else if (toggleSelection) { + that.selections.splice(idx, 1); } } else { var idx = that.pendingSelections.indexOf(entityID); @@ -227,7 +235,6 @@ SelectionDisplay = (function () { var overlayNames = new Array(); var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); - var lastPlaneIntersection; var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; @@ -324,21 +331,23 @@ SelectionDisplay = (function () { solid: false, visible: false, dashed: true, - lineWidth: 1.0, + lineWidth: 2.0, ignoreRayIntersection: true // this never ray intersects }); var selectionBox = Overlays.addOverlay("cube", { position: { x:0, y: 0, z: 0}, size: 1, - color: { red: 60, green: 60, blue: 60}, + color: { red: 255, green: 0, blue: 0}, alpha: 1, solid: false, visible: false, - dashed: true, + dashed: false, lineWidth: 1.0, }); + var selectionBoxes = []; + var rotationDegreesDisplay = Overlays.addOverlay("text3d", { position: { x:0, y: 0, z: 0}, text: "", @@ -684,6 +693,9 @@ SelectionDisplay = (function () { for (var i = 0; i < allOverlays.length; i++) { Overlays.deleteOverlay(allOverlays[i]); } + for (var i = 0; i < selectionBoxes.length; i++) { + Overlays.deleteOverlay(selectionBoxes[i]); + } }; that.highlightSelectable = function(entityID) { @@ -708,13 +720,11 @@ SelectionDisplay = (function () { if (event !== false) { pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastCameraOrientation)); var wantDebug = false; if (wantDebug) { print("select() with EVENT...... "); print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" current position:", properties.position); } @@ -933,23 +943,6 @@ SelectionDisplay = (function () { var dimensions = selectionManager.worldDimensions; var position = selectionManager.worldPosition; - Overlays.editOverlay(baseOfEntityProjectionOverlay, - { - visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", - solid: true, - // lineWidth: 2.0, - position: { - x: position.x, - y: grid.getOrigin().y, - z: position.z - }, - dimensions: { - x: dimensions.x, - y: dimensions.z - }, - rotation: rotation, - }); - Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); @@ -986,7 +979,6 @@ SelectionDisplay = (function () { return; } - that.updateRotationHandles(); that.highlightSelectable(); @@ -1126,6 +1118,41 @@ SelectionDisplay = (function () { visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), }); + // Create more selection box overlays if we don't have enough + var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length; + for (var i = 0; i < overlaysNeeded; i++) { + selectionBoxes.push( + Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0 }, + size: 1, + color: { red: 255, green: 153, blue: 0 }, + alpha: 1, + solid: false, + visible: false, + dashed: false, + lineWidth: 1.0, + ignoreRayIntersection: true, + })); + } + + var i = 0; + // Only show individual selections boxes if there is more than 1 selection + if (selectionManager.selections.length > 1) { + for (; i < selectionManager.selections.length; i++) { + var properties = Entities.getEntityProperties(selectionManager.selections[i]); + Overlays.editOverlay(selectionBoxes[i], { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions, + visible: true, + }); + } + } + // Hide any remaining selection boxes + for (; i < selectionBoxes.length; i++) { + Overlays.editOverlay(selectionBoxes[i], { visible: false }); + } + Overlays.editOverlay(grabberEdgeTR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTR }); Overlays.editOverlay(grabberEdgeTL, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTL }); Overlays.editOverlay(grabberEdgeTF, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTF }); @@ -1142,6 +1169,23 @@ SelectionDisplay = (function () { var grabberMoveUpOffset = 0.1; grabberMoveUpPosition = { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z } Overlays.editOverlay(grabberMoveUp, { visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" }); + + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", + solid: true, + position: { + x: selectionManager.worldPosition.x, + y: grid.getOrigin().y, + z: selectionManager.worldPosition.z + }, + dimensions: { + x: selectionManager.worldDimensions.x, + y: selectionManager.worldDimensions.z + }, + rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), + }); + + }; that.setOverlaysVisible = function(isVisible) { @@ -1149,6 +1193,10 @@ SelectionDisplay = (function () { for (var i = 0; i < length; i++) { Overlays.editOverlay(allOverlays[i], { visible: isVisible }); } + length = selectionBoxes.length; + for (var i = 0; i < length; i++) { + Overlays.editOverlay(selectionBoxes[i], { visible: isVisible }); + } }; that.unselect = function (entityID) { @@ -1242,7 +1290,6 @@ SelectionDisplay = (function () { if (wantDebug) { print("translateXZ... "); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" vector:", vector); Vec3.print(" newPosition:", properties.position); Vec3.print(" newPosition:", newPosition); @@ -1254,10 +1301,17 @@ SelectionDisplay = (function () { }; var lastXYPick = null + var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { - lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, Quat.getFront(lastCameraOrientation)); + pickRay = Camera.computePickRay(event.x, event.y); + + upDownPickNormal = Quat.getFront(lastCameraOrientation); + // Remove y component so the y-axis lies along the plane we picking on - this will + // give movements that follow the mouse. + upDownPickNormal.y = 0; + lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); SelectionManager.saveProperties(); @@ -1285,11 +1339,9 @@ SelectionDisplay = (function () { pickRay = Camera.computePickRay(event.x, event.y); // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, - SelectionManager.worldPosition, - Quat.getFront(lastCameraOrientation)); + var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); - var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); + var vector = Vec3.subtract(newIntersection, lastXYPick); vector = grid.snapToGrid(vector); // we only care about the Y axis @@ -1300,7 +1352,6 @@ SelectionDisplay = (function () { if (wantDebug) { print("translateUpDown... "); print(" event.y:" + event.y); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); Vec3.print(" newPosition:", newPosition); @@ -1513,7 +1564,6 @@ SelectionDisplay = (function () { var wantDebug = false; if (wantDebug) { print(stretchMode); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); Vec3.print(" oldPOS:", oldPOS); @@ -2010,7 +2060,7 @@ SelectionDisplay = (function () { that.checkMove = function() { if (SelectionManager.hasSelection() && (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation))){ - that.select(selectionManager.selections[0], false, false); + that.updateRotationHandles(); } }; @@ -2262,11 +2312,8 @@ SelectionDisplay = (function () { if (somethingClicked) { pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectionManager.worldPosition, - Quat.getFront(lastCameraOrientation)); if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); + print("mousePressEvent()...... " + overlayNames[result.overlayID]); } } diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js index de4fd7b8d4..aa412b1a76 100644 --- a/examples/libraries/gridTool.js +++ b/examples/libraries/gridTool.js @@ -177,6 +177,8 @@ Grid = function(opts) { color: gridColor, alpha: gridAlpha, }); + + that.emitUpdate(); } function cleanup() { @@ -207,6 +209,7 @@ GridTool = function(opts) { horizontalGrid.addListener(function(data) { webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + selectionDisplay.updateHandles(); }); webView.eventBridge.webEventReceived.connect(function(data) { diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index bc2d87259e..92db9daab5 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -51,8 +51,8 @@ var toolWidth = 50; var MIN_ANGULAR_SIZE = 2; var MAX_ANGULAR_SIZE = 45; -var allowLargeModels = false; -var allowSmallModels = false; +var allowLargeModels = true; +var allowSmallModels = true; var wantEntityGlow = false; var SPAWN_DISTANCE = 1; @@ -476,7 +476,6 @@ function findClickedEntity(event) { var identify = Entities.identifyEntity(foundEntity); if (!identify.isKnownID) { print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")"); - selectionManager.clearSelections(); return null; } foundEntity = identify; @@ -485,74 +484,18 @@ function findClickedEntity(event) { return { pickRay: pickRay, entityID: foundEntity }; } +var mouseHasMovedSincePress = false; function mousePressEvent(event) { + mouseHasMovedSincePress = false; + if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { return; } if (isActive) { - var entitySelected = false; if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { // Event handled; do nothing. return; - } else { - var result = findClickedEntity(event); - if (result === null) { - selectionManager.clearSelections(); - return; - } - var pickRay = result.pickRay; - var foundEntity = result.entityID; - - var properties = Entities.getEntityProperties(foundEntity); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - print("Checking properties: " + properties.id + " " + properties.isKnownID + " - 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 X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - entitySelected = true; - selectedEntityID = foundEntity; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - - if (!event.isShifted) { - selectionManager.clearSelections(); - } - selectionManager.addEntity(foundEntity); - - print("Model selected: " + foundEntity.id); - } - } - } - if (entitySelected) { - selectionDisplay.select(selectedEntityID, event); } } else if (Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)) { var result = findClickedEntity(event); @@ -572,6 +515,7 @@ function mousePressEvent(event) { var highlightedEntityID = { isKnownID: false }; function mouseMoveEvent(event) { + mouseHasMovedSincePress = true; if (isActive) { // 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) || cameraManager.mouseMoveEvent(event)) { @@ -615,6 +559,72 @@ function mouseReleaseEvent(event) { } cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + mouseClickEvent(event); + } +} + +function mouseClickEvent(event) { + var result = findClickedEntity(event); + if (result === null) { + if (!event.isShifted) { + selectionManager.clearSelections(); + } + return; + } + var pickRay = result.pickRay; + var foundEntity = result.entityID; + + var properties = Entities.getEntityProperties(foundEntity); + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + print("Checking properties: " + properties.id + " " + properties.isKnownID + " - 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 X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + entitySelected = true; + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + + if (!event.isShifted) { + selectionManager.clearSelections(); + } + + var toggle = event.isShifted; + selectionManager.addEntity(foundEntity, toggle); + + print("Model selected: " + foundEntity.id); + selectionDisplay.select(selectedEntityID, event); + } + } } Controller.mousePressEvent.connect(mousePressEvent); @@ -644,9 +654,9 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Paste Models", isCheckable: true }); + afterItem: "Paste Models", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", - afterItem: "Allow Select Large Models", isCheckable: true }); + afterItem: "Allow Select Large Models", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9c3eb48993..9ab87fdc30 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -442,6 +442,7 @@ void Application::aboutToQuit() { } Application::~Application() { + _entities.getTree()->setSimulation(NULL); qInstallMessageHandler(NULL); saveSettings(); @@ -1976,7 +1977,9 @@ void Application::init() { _entities.init(); _entities.setViewFrustum(getViewFrustum()); - _entityCollisionSystem.init(&_entityEditSender, _entities.getTree(), _voxels.getTree(), &_audio, &_avatarManager); + EntityTree* entityTree = _entities.getTree(); + _entityCollisionSystem.init(&_entityEditSender, entityTree, _voxels.getTree(), &_audio, &_avatarManager); + entityTree->setSimulation(&_entityCollisionSystem); // connect the _entityCollisionSystem to our script engine's EntityScriptingInterface connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithVoxel, @@ -2327,11 +2330,12 @@ void Application::update(float deltaTime) { if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); + // NOTE: the _entities.update() call below will wait for lock + // and will simulate entity motion (the EntityTree has been given an EntitySimulation). _entities.update(); // update the models... - { - PerformanceTimer perfTimer("collisions"); - _entityCollisionSystem.update(); // collide the entities... - } + // The _entityCollisionSystem.updateCollisions() call below merely tries for lock, + // and on failure it skips collision detection. + _entityCollisionSystem.updateCollisions(); // collide the entities... } { diff --git a/interface/src/Application.h b/interface/src/Application.h index 92ef00e8a5..e432f7fdf0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -30,8 +30,8 @@ #include #include -#include #include +#include #include #include #include diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 9f06011d30..0042dd495f 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -40,7 +40,6 @@ public: EntityTreeRenderer(bool wantScripts); virtual ~EntityTreeRenderer(); - virtual Octree* createTree() { return new EntityTree(true); } virtual char getMyNodeType() const { return NodeType::EntityServer; } virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; } virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; } @@ -108,6 +107,9 @@ public slots: void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void entitySciptChanging(const EntityItemID& entityID); +protected: + virtual Octree* createTree() { return new EntityTree(true); } + private: void checkAndCallPreload(const EntityItemID& entityID); void checkAndCallUnload(const EntityItemID& entityID); diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index 5b0ada4ec1..12441e5427 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -48,9 +48,6 @@ void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEnt details.cube = details.containingElement->getAACube(); _entitiesToDelete << details; _lookingCount++; - _tree->trackDeletedEntity(searchEntityID); - // before deleting any entity make sure to remove it from our Mortal, Changing, and Moving lists - _tree->removeEntityFromSimulationLists(searchEntityID); } } } @@ -78,13 +75,9 @@ bool DeleteEntityOperator::preRecursion(OctreeElement* element) { EntityTreeElement* entityTreeElement = static_cast(element); // In Pre-recursion, we're generally deciding whether or not we want to recurse this - // path of the tree. For this operation, we want to recurse the branch of the tree if - // and of the following are true: - // * We have not yet found the old entity, and this branch contains our old entity - // * We have not yet found the new entity, and this branch contains our new entity - // - // Note: it's often the case that the branch in question contains both the old entity - // and the new entity. + // path of the tree. For this operation, we want to recurse the branch of the tree if: + // * We have not yet found the all entities, and + // * this branch contains our some of the entities we're looking for. bool keepSearching = false; // assume we don't need to search any more @@ -100,6 +93,8 @@ bool DeleteEntityOperator::preRecursion(OctreeElement* element) { if (entityTreeElement == details.containingElement) { EntityItemID entityItemID = details.entity->getEntityItemID(); EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity + assert(theEntity); + _tree->trackDeletedEntity(theEntity); entityTreeElement->removeEntityItem(theEntity); // remove it from the element _tree->setContainingElement(entityItemID, NULL); // update or id to element lookup delete theEntity; // now actually delete the entity! diff --git a/libraries/entities/src/EntityCollisionSystem.cpp b/libraries/entities/src/EntityCollisionSystem.cpp index b080212479..2ac8ea596d 100644 --- a/libraries/entities/src/EntityCollisionSystem.cpp +++ b/libraries/entities/src/EntityCollisionSystem.cpp @@ -16,27 +16,32 @@ #include #include #include +#include #include -#include "EntityItem.h" #include "EntityCollisionSystem.h" #include "EntityEditPacketSender.h" -#include "EntityTree.h" +#include "EntityItem.h" #include "EntityTreeElement.h" +#include "EntityTree.h" const int MAX_COLLISIONS_PER_Entity = 16; -EntityCollisionSystem::EntityCollisionSystem(EntityEditPacketSender* packetSender, - EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio, - AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_Entity) { - init(packetSender, Entities, voxels, audio, avatars); +EntityCollisionSystem::EntityCollisionSystem() + : SimpleEntitySimulation(), + _packetSender(NULL), + _voxels(NULL), + _audio(NULL), + _avatars(NULL), + _collisions(MAX_COLLISIONS_PER_Entity) { } void EntityCollisionSystem::init(EntityEditPacketSender* packetSender, - EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio, - AvatarHashMap* avatars) { + EntityTree* entities, VoxelTree* voxels, AbstractAudioInterface* audio, + AvatarHashMap* avatars) { + assert(entities); + setEntityTree(entities); _packetSender = packetSender; - _entities = Entities; _voxels = voxels; _audio = audio; _avatars = avatars; @@ -45,14 +50,15 @@ void EntityCollisionSystem::init(EntityEditPacketSender* packetSender, EntityCollisionSystem::~EntityCollisionSystem() { } -void EntityCollisionSystem::update() { +void EntityCollisionSystem::updateCollisions() { + PerformanceTimer perfTimer("collisions"); + assert(_entityTree); // update all Entities - if (_entities->tryLockForRead()) { - QList& movingEntities = _entities->getMovingEntities(); - foreach (EntityItem* entity, movingEntities) { + if (_entityTree->tryLockForRead()) { + foreach (EntityItem* entity, _movingEntities) { checkEntity(entity); } - _entities->unlock(); + _entityTree->unlock(); } } @@ -127,9 +133,8 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { CollisionList collisions(MAX_COLLISIONS_PER_ENTITY); bool shapeCollisionsAccurate = false; - bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(), + bool shapeCollisions = _entityTree->findShapeCollisions(&entityA->getCollisionShapeInMeters(), collisions, Octree::NoLock, &shapeCollisionsAccurate); - if (shapeCollisions) { for(int i = 0; i < collisions.size(); i++) { @@ -203,7 +208,7 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { propertiesA.setPosition(newPositionA * (float)TREE_SCALE); propertiesA.setLastEdited(now); - _entities->updateEntity(idA, propertiesA); + _entityTree->updateEntity(idA, propertiesA); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA); } @@ -220,7 +225,7 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { propertiesB.setPosition(newPositionB * (float)TREE_SCALE); propertiesB.setLastEdited(now); - _entities->updateEntity(idB, propertiesB); + _entityTree->updateEntity(idB, propertiesB); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB); } } @@ -326,6 +331,6 @@ void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const Collisi properties.setVelocity(velocity * (float)TREE_SCALE); properties.setLastEdited(usecTimestampNow()); - _entities->updateEntity(entityItemID, properties); + _entityTree->updateEntity(entityItemID, properties); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties); } diff --git a/libraries/entities/src/EntityCollisionSystem.h b/libraries/entities/src/EntityCollisionSystem.h index 4b483122fe..b4421ffc72 100644 --- a/libraries/entities/src/EntityCollisionSystem.h +++ b/libraries/entities/src/EntityCollisionSystem.h @@ -20,11 +20,12 @@ #include #include -#include #include +#include #include #include "EntityItem.h" +#include "SimpleEntitySimulation.h" class AbstractAudioInterface; class AvatarData; @@ -32,19 +33,17 @@ class EntityEditPacketSender; class EntityTree; class VoxelTree; -class EntityCollisionSystem : public QObject { +class EntityCollisionSystem : public QObject, public SimpleEntitySimulation { Q_OBJECT public: - EntityCollisionSystem(EntityEditPacketSender* packetSender = NULL, EntityTree* Entitys = NULL, - VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL, - AvatarHashMap* avatars = NULL); + EntityCollisionSystem(); - void init(EntityEditPacketSender* packetSender, EntityTree* Entitys, VoxelTree* voxels, + void init(EntityEditPacketSender* packetSender, EntityTree* entities, VoxelTree* voxels, AbstractAudioInterface* audio = NULL, AvatarHashMap* _avatars = NULL); ~EntityCollisionSystem(); - void update(); + void updateCollisions(); void checkEntity(EntityItem* Entity); void updateCollisionWithVoxels(EntityItem* Entity); @@ -65,7 +64,6 @@ private: void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const CollisionInfo& penetration); EntityEditPacketSender* _packetSender; - EntityTree* _entities; VoxelTree* _voxels; AbstractAudioInterface* _audio; AvatarHashMap* _avatars; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3f63c96c4e..c1b1f243ef 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -292,16 +292,10 @@ public: uint32_t getUpdateFlags() const { return _updateFlags; } void clearUpdateFlags() { _updateFlags = 0; } -#ifdef USE_BULLET_PHYSICS - EntityMotionState* getMotionState() const { return _motionState; } - virtual EntityMotionState* createMotionState() { return NULL; } - void destroyMotionState(); -#endif // USE_BULLET_PHYSICS SimulationState getSimulationState() const { return _simulationState; } -protected: - friend class EntityTree; void setSimulationState(SimulationState state) { _simulationState = state; } +protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init virtual void recalculateCollisionShape(); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp new file mode 100644 index 0000000000..8058c2f24e --- /dev/null +++ b/libraries/entities/src/EntitySimulation.cpp @@ -0,0 +1,20 @@ +// +// EntitySimulation.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.24 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntitySimulation.h" + +void EntitySimulation::setEntityTree(EntityTree* tree) { + if (_entityTree && _entityTree != tree) { + clearEntities(); + } + _entityTree = tree; +} + diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h new file mode 100644 index 0000000000..770d6ebdb0 --- /dev/null +++ b/libraries/entities/src/EntitySimulation.h @@ -0,0 +1,49 @@ +// +// EntitySimulation.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.24 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EntitySimulation_h +#define hifi_EntitySimulation_h + +#include + +#include "EntityTree.h" + +class EntitySimulation { +public: + EntitySimulation() : _entityTree(NULL) { } + virtual ~EntitySimulation() {} + + /// \param tree pointer to EntityTree which is stored internally + virtual void setEntityTree(EntityTree* tree); + + /// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted. + virtual void update(QSet& entitiesToDelete) = 0; + + /// \param entity pointer to EntityItem to add to the simulation + /// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list + virtual void addEntity(EntityItem* entity) = 0; + + /// \param entity pointer to EntityItem to removed from the simulation + /// \sideeffect the EntityItem::_simulationState member may be updated to indicate non-membership to internal list + virtual void removeEntity(EntityItem* entity) = 0; + + /// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation + virtual void entityChanged(EntityItem* entity) = 0; + + virtual void clearEntities() = 0; + + EntityTree* getEntityTree() { return _entityTree; } + +protected: + EntityTree* _entityTree; +}; + +#endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bb201b6f86..6ac3c31939 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -12,13 +12,14 @@ #include #include "EntityTree.h" +#include "EntitySimulation.h" #include "AddEntityOperator.h" #include "DeleteEntityOperator.h" #include "MovingEntitiesOperator.h" #include "UpdateEntityOperator.h" -EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) { +EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _simulation(NULL) { _rootElement = createNewElement(); } @@ -34,14 +35,14 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) { // this would be a good place to clean up our entities... + if (_simulation) { + _simulation->clearEntities(); + } foreach (EntityTreeElement* element, _entityToElementMap) { element->cleanupEntities(); } _entityToElementMap.clear(); Octree::eraseAllOctreeElements(createNewRoot); - _movingEntities.clear(); - _mortalEntities.clear(); - _changedEntities.clear(); } bool EntityTree::handlesEditPacketType(PacketType packetType) const { @@ -75,27 +76,17 @@ EntityItem* EntityTree::getOrCreateEntityItem(const EntityItemID& entityID, cons } /// Adds a new entity item to the tree -void EntityTree::addEntityItem(EntityItem* entityItem) { - // You should not call this on existing entities that are already part of the tree! Call updateEntity() - EntityItemID entityID = entityItem->getEntityItemID(); - EntityTreeElement* containingElement = getContainingElement(entityID); - if (containingElement) { - qDebug() << "UNEXPECTED!!!! don't call addEntityItem() on existing EntityItems. entityID=" << entityID; - return; - } - - // Recurse the tree and store the entity in the correct tree element - AddEntityOperator theOperator(this, entityItem); - recurseTreeWithOperator(&theOperator); - +void EntityTree::postAddEntity(EntityItem* entity) { + assert(entity); // check to see if we need to simulate this entity.. - updateEntityState(entityItem); - + if (_simulation) { + _simulation->addEntity(entity); + } _isDirty = true; + emit addingEntity(entity->getEntityItemID()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { - // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; @@ -119,6 +110,9 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp UpdateEntityOperator theOperator(this, containingElement, existingEntity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; + if (_simulation && existingEntity->getUpdateFlags() != 0) { + _simulation->entityChanged(existingEntity); + } } } } else { @@ -129,13 +123,14 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp recurseTreeWithOperator(&theOperator); _isDirty = true; - updateEntityState(existingEntity); + if (_simulation && existingEntity->getUpdateFlags() != 0) { + _simulation->entityChanged(existingEntity); + } QString entityScriptAfter = existingEntity->getScript(); if (entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entityID); // the entity script has changed } - } containingElement = getContainingElement(entityID); @@ -171,33 +166,47 @@ EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItem result = EntityTypes::constructEntityItem(type, entityID, properties); if (result) { - // this does the actual adding of the entity - addEntityItem(result); - emitAddingEntity(entityID); + // Recurse the tree and store the entity in the correct tree element + AddEntityOperator theOperator(this, result); + recurseTreeWithOperator(&theOperator); + + postAddEntity(result); } return result; } -void EntityTree::trackDeletedEntity(const EntityItemID& entityID) { +void EntityTree::trackDeletedEntity(EntityItem* entity) { + if (_simulation) { + _simulation->removeEntity(entity); + } // this is only needed on the server to send delete messages for recently deleted entities to the viewers if (getIsServer()) { // set up the deleted entities ID quint64 deletedAt = usecTimestampNow(); _recentlyDeletedEntitiesLock.lockForWrite(); - _recentlyDeletedEntityItemIDs.insert(deletedAt, entityID.id); + _recentlyDeletedEntityItemIDs.insert(deletedAt, entity->getEntityItemID().id); _recentlyDeletedEntitiesLock.unlock(); } } -void EntityTree::emitAddingEntity(const EntityItemID& entityItemID) { - emit addingEntity(entityItemID); -} - void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) { emit entityScriptChanging(entityItemID); } +void EntityTree::setSimulation(EntitySimulation* simulation) { + if (simulation) { + // assert that the simulation's backpointer has already been properly connected + assert(simulation->getEntityTree() == this); + } + if (_simulation && _simulation != simulation) { + // It's important to clearEntities() on the simulation since taht will update each + // EntityItem::_simulationState correctly so as to not confuse the next _simulation. + _simulation->clearEntities(); + } + _simulation = simulation; +} + void EntityTree::deleteEntity(const EntityItemID& entityID) { emit deletingEntity(entityID); @@ -220,29 +229,6 @@ void EntityTree::deleteEntities(QSet entityIDs) { _isDirty = true; } -void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) { - EntityItem* theEntity = findEntityByEntityItemID(entityID); - - if (theEntity) { - // make sure to remove it from any of our simulation lists - EntityItem::SimulationState theState = theEntity->getSimulationState(); - switch (theState) { - case EntityItem::Moving: - _movingEntities.removeAll(theEntity); - break; - - case EntityItem::Mortal: - _mortalEntities.removeAll(theEntity); - break; - - default: - break; - } - _changedEntities.remove(theEntity); - } -} - - /// This method is used to find and fix entity IDs that are shifting from creator token based to known ID based entity IDs. /// This should only be used on a client side (viewing) tree. The typical usage is that a local editor has been creating /// entities in the local tree, those entities have creatorToken based entity IDs. But those entity edits are also sent up to @@ -575,183 +561,29 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod extraEncodeData->clear(); } -void EntityTree::updateEntityState(EntityItem* entity) { - EntityItem::SimulationState oldState = entity->getSimulationState(); - EntityItem::SimulationState newState = entity->computeSimulationState(); - if (newState != oldState) { - switch (oldState) { - case EntityItem::Moving: - _movingEntities.removeAll(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.removeAll(entity); - break; - - default: - break; - } - - switch (newState) { - case EntityItem::Moving: - _movingEntities.push_back(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.push_back(entity); - break; - - default: - break; - } - entity->setSimulationState(newState); - } -} - -void EntityTree::clearEntityState(EntityItem* entity) { - EntityItem::SimulationState oldState = entity->getSimulationState(); - switch (oldState) { - case EntityItem::Moving: - _movingEntities.removeAll(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.removeAll(entity); - break; - - default: - break; - } - entity->setSimulationState(EntityItem::Static); -} - void EntityTree::entityChanged(EntityItem* entity) { - _changedEntities.insert(entity); + if (_simulation) { + _simulation->entityChanged(entity); + } } void EntityTree::update() { - // our new strategy should be to segregate entities into three classes: - // 1) stationary things that are not changing - most models - // 2) mortal things - these are stationary but have a lifetime - then need to be checked, - // can be touched linearly, and won't change the tree - // 2) changing things - like things animating they can be touched linearly and they don't change the tree - // 3) moving things - these need to scan the tree and update accordingly - // finally - all things that need to be deleted, can be handled on a single delete pass. - // - // TODO: theoretically we could combine the move and delete tree passes... - lockForWrite(); - quint64 now = usecTimestampNow(); - QSet entitiesToDelete; - updateChangedEntities(now, entitiesToDelete); - updateMovingEntities(now, entitiesToDelete); - updateMortalEntities(now, entitiesToDelete); - - if (entitiesToDelete.size() > 0) { - deleteEntities(entitiesToDelete); - } - unlock(); -} - -void EntityTree::updateChangedEntities(quint64 now, QSet& entitiesToDelete) { - foreach (EntityItem* thisEntity, _changedEntities) { - // check to see if the lifetime has expired, for immortal entities this is always false - if (thisEntity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); - entitiesToDelete << thisEntity->getEntityItemID(); - clearEntityState(thisEntity); - } else { - updateEntityState(thisEntity); - } - thisEntity->clearUpdateFlags(); - } - _changedEntities.clear(); -} - -void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesToDelete) { - PerformanceTimer perfTimer("updateMovingEntities"); - if (_movingEntities.size() > 0) { - MovingEntitiesOperator moveOperator(this); - { - PerformanceTimer perfTimer("_movingEntities"); - - QList::iterator item_itr = _movingEntities.begin(); - while (item_itr != _movingEntities.end()) { - EntityItem* thisEntity = *item_itr; - - // always check to see if the lifetime has expired, for immortal entities this is always false - if (thisEntity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); - entitiesToDelete << thisEntity->getEntityItemID(); - // remove thisEntity from the list - item_itr = _movingEntities.erase(item_itr); - thisEntity->setSimulationState(EntityItem::Static); - } else { - AACube oldCube = thisEntity->getMaximumAACube(); - thisEntity->update(now); - AACube newCube = thisEntity->getMaximumAACube(); - - // check to see if this movement has sent the entity outside of the domain. - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); - if (!domainBounds.touches(newCube)) { - qDebug() << "Entity " << thisEntity->getEntityItemID() << " moved out of domain bounds."; - entitiesToDelete << thisEntity->getEntityItemID(); - // remove thisEntity from the list - item_itr = _movingEntities.erase(item_itr); - thisEntity->setSimulationState(EntityItem::Static); - } else { - moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube); - EntityItem::SimulationState newState = thisEntity->computeSimulationState(); - if (newState != EntityItem::Moving) { - if (newState == EntityItem::Mortal) { - _mortalEntities.push_back(thisEntity); - } - // remove thisEntity from the list - item_itr = _movingEntities.erase(item_itr); - thisEntity->setSimulationState(newState); - } else { - ++item_itr; - } - } - } + if (_simulation) { + lockForWrite(); + QSet entitiesToDelete; + _simulation->update(entitiesToDelete); + if (entitiesToDelete.size() > 0) { + // translate into list of ID's + QSet idsToDelete; + foreach (EntityItem* entity, entitiesToDelete) { + idsToDelete.insert(entity->getEntityItemID()); } + deleteEntities(idsToDelete); } - if (moveOperator.hasMovingEntities()) { - PerformanceTimer perfTimer("recurseTreeWithOperator"); - recurseTreeWithOperator(&moveOperator); - } + unlock(); } } -void EntityTree::updateMortalEntities(quint64 now, QSet& entitiesToDelete) { - QList::iterator item_itr = _mortalEntities.begin(); - while (item_itr != _mortalEntities.end()) { - EntityItem* thisEntity = *item_itr; - thisEntity->update(now); - // always check to see if the lifetime has expired, for immortal entities this is always false - if (thisEntity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); - entitiesToDelete << thisEntity->getEntityItemID(); - // remove thisEntity from the list - item_itr = _mortalEntities.erase(item_itr); - thisEntity->setSimulationState(EntityItem::Static); - } else { - // check to see if this entity is no longer moving - EntityItem::SimulationState newState = thisEntity->computeSimulationState(); - if (newState != EntityItem::Mortal) { - if (newState == EntityItem::Moving) { - _movingEntities.push_back(thisEntity); - } - // remove thisEntity from the list - item_itr = _mortalEntities.erase(item_itr); - thisEntity->setSimulationState(newState); - } else { - ++item_itr; - } - } - } -} - - bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { // we can probably leverage the ordered nature of QMultiMap to do this quickly... bool hasSomethingNewer = false; @@ -966,19 +798,16 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityItemID) /*const*/ { // TODO: do we need to make this thread safe? Or is it acceptable as is - if (_entityToElementMap.contains(entityItemID)) { - return _entityToElementMap.value(entityItemID); - } else if (entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN){ + EntityTreeElement* element = _entityToElementMap.value(entityItemID); + if (!element && entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN){ // check the creator token version too... EntityItemID creatorTokenOnly; creatorTokenOnly.id = UNKNOWN_ENTITY_ID; creatorTokenOnly.creatorTokenID = entityItemID.creatorTokenID; creatorTokenOnly.isKnownID = false; - if (_entityToElementMap.contains(creatorTokenOnly)) { - return _entityToElementMap.value(creatorTokenOnly); - } + element = _entityToElementMap.value(creatorTokenOnly); } - return NULL; + return element; } // TODO: do we need to make this thread safe? Or is it acceptable as is diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 7370ebadc6..5dccfd7709 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -19,6 +19,7 @@ class Model; +class EntitySimulation; class NewlyCreatedEntityHook { public: @@ -78,13 +79,13 @@ public: // The newer API... EntityItem* getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties); - void addEntityItem(EntityItem* entityItem); + void postAddEntity(EntityItem* entityItem); EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties); bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties); void deleteEntity(const EntityItemID& entityID); void deleteEntities(QSet entityIDs); - void removeEntityFromSimulationLists(const EntityItemID& entityID); + void removeEntityFromSimulation(EntityItem* entity); const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius); EntityItem* findEntityByID(const QUuid& id); @@ -137,18 +138,14 @@ public: void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z); - void updateEntityState(EntityItem* entity); - void clearEntityState(EntityItem* entity); - void entityChanged(EntityItem* entity); - void trackDeletedEntity(const EntityItemID& entityID); + void trackDeletedEntity(EntityItem* entity); - void emitAddingEntity(const EntityItemID& entityItemID); void emitEntityScriptChanging(const EntityItemID& entityItemID); - QList& getMovingEntities() { return _movingEntities; } - + void setSimulation(EntitySimulation* simulation); + signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); @@ -157,10 +154,6 @@ signals: private: - void updateChangedEntities(quint64 now, QSet& entitiesToDelete); - void updateMovingEntities(quint64 now, QSet& entitiesToDelete); - void updateMortalEntities(quint64 now, QSet& entitiesToDelete); - static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData); @@ -176,11 +169,7 @@ private: EntityItemFBXService* _fbxService; QHash _entityToElementMap; - - QList _movingEntities; // entities that need to be updated - QList _mortalEntities; // entities that need to be checked for expiry - - QSet _changedEntities; // entities that have changed in the last frame + EntitySimulation* _simulation; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 2646cc0dfd..f0eeb40ede 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -768,8 +768,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int addEntityItem(entityItem); // add this new entity to this elements entities entityItemID = entityItem->getEntityItemID(); _myTree->setContainingElement(entityItemID, this); - _myTree->updateEntityState(entityItem); - _myTree->emitAddingEntity(entityItemID); // we just added an entity + _myTree->postAddEntity(entityItem); } } // Move the buffer forward to read more entities diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.cpp b/libraries/entities/src/EntityTreeHeadlessViewer.cpp index df00d302e3..45d21c0987 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.cpp +++ b/libraries/entities/src/EntityTreeHeadlessViewer.cpp @@ -10,9 +10,10 @@ // #include "EntityTreeHeadlessViewer.h" +#include "SimpleEntitySimulation.h" -EntityTreeHeadlessViewer::EntityTreeHeadlessViewer() : - OctreeHeadlessViewer() { +EntityTreeHeadlessViewer::EntityTreeHeadlessViewer() + : OctreeHeadlessViewer(), _simulation(NULL) { } EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() { @@ -20,9 +21,15 @@ EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() { void EntityTreeHeadlessViewer::init() { OctreeHeadlessViewer::init(); + if (!_simulation) { + SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation(); + EntityTree* entityTree = static_cast(_tree); + simpleSimulation->setEntityTree(entityTree); + entityTree->setSimulation(simpleSimulation); + _simulation = simpleSimulation; + } } - void EntityTreeHeadlessViewer::update() { if (_tree) { EntityTree* tree = static_cast(_tree); diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.h b/libraries/entities/src/EntityTreeHeadlessViewer.h index b6cc04fb12..3989582c2b 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.h +++ b/libraries/entities/src/EntityTreeHeadlessViewer.h @@ -21,6 +21,8 @@ #include "EntityTree.h" +class EntitySimulation; + // Generic client side Octree renderer class. class EntityTreeHeadlessViewer : public OctreeHeadlessViewer { Q_OBJECT @@ -28,7 +30,6 @@ public: EntityTreeHeadlessViewer(); virtual ~EntityTreeHeadlessViewer(); - virtual Octree* createTree() { return new EntityTree(true); } virtual char getMyNodeType() const { return NodeType::EntityServer; } virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; } virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; } @@ -40,6 +41,11 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); virtual void init(); + +protected: + virtual Octree* createTree() { return new EntityTree(true); } + + EntitySimulation* _simulation; }; #endif // hifi_EntityTreeHeadlessViewer_h diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp new file mode 100644 index 0000000000..b3316978a9 --- /dev/null +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -0,0 +1,224 @@ +// +// SimpleEntitySimulation.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.24 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "EntityItem.h" +#include "MovingEntitiesOperator.h" +#include "SimpleEntitySimulation.h" + +void SimpleEntitySimulation::update(QSet& entitiesToDelete) { + quint64 now = usecTimestampNow(); + updateChangedEntities(now, entitiesToDelete); + updateMovingEntities(now, entitiesToDelete); + updateMortalEntities(now, entitiesToDelete); +} + +void SimpleEntitySimulation::addEntity(EntityItem* entity) { + assert(entity && entity->getSimulationState() == EntityItem::Static); + EntityItem::SimulationState state = entity->computeSimulationState(); + switch(state) { + case EntityItem::Moving: + _movingEntities.push_back(entity); + entity->setSimulationState(state); + break; + case EntityItem::Mortal: + _mortalEntities.push_back(entity); + entity->setSimulationState(state); + break; + case EntityItem::Static: + default: + break; + } +} + +void SimpleEntitySimulation::removeEntity(EntityItem* entity) { + assert(entity); + // make sure to remove it from any of our simulation lists + EntityItem::SimulationState state = entity->getSimulationState(); + switch (state) { + case EntityItem::Moving: + _movingEntities.removeAll(entity); + break; + case EntityItem::Mortal: + _mortalEntities.removeAll(entity); + break; + + default: + break; + } + entity->setSimulationState(EntityItem::Static); + _changedEntities.remove(entity); +} + +void SimpleEntitySimulation::entityChanged(EntityItem* entity) { + assert(entity); + // we batch all changes and deal with them in updateChangedEntities() + _changedEntities.insert(entity); +} + +void SimpleEntitySimulation::clearEntities() { + foreach (EntityItem* entity, _changedEntities) { + entity->clearUpdateFlags(); + entity->setSimulationState(EntityItem::Static); + } + _changedEntities.clear(); + _movingEntities.clear(); + _mortalEntities.clear(); +} + +void SimpleEntitySimulation::updateChangedEntities(quint64 now, QSet& entitiesToDelete) { + foreach (EntityItem* entity, _changedEntities) { + // check to see if the lifetime has expired, for immortal entities this is always false + if (entity->lifetimeHasExpired()) { + qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); + entitiesToDelete.insert(entity); + clearEntityState(entity); + } else { + updateEntityState(entity); + } + entity->clearUpdateFlags(); + } + _changedEntities.clear(); +} + +void SimpleEntitySimulation::updateMovingEntities(quint64 now, QSet& entitiesToDelete) { + if (_entityTree && _movingEntities.size() > 0) { + PerformanceTimer perfTimer("_movingEntities"); + MovingEntitiesOperator moveOperator(_entityTree); + QList::iterator item_itr = _movingEntities.begin(); + while (item_itr != _movingEntities.end()) { + EntityItem* entity = *item_itr; + + // always check to see if the lifetime has expired, for immortal entities this is always false + if (entity->lifetimeHasExpired()) { + qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); + entitiesToDelete.insert(entity); + // remove entity from the list + item_itr = _movingEntities.erase(item_itr); + entity->setSimulationState(EntityItem::Static); + } else { + AACube oldCube = entity->getMaximumAACube(); + entity->update(now); + AACube newCube = entity->getMaximumAACube(); + + // check to see if this movement has sent the entity outside of the domain. + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + if (!domainBounds.touches(newCube)) { + qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; + entitiesToDelete.insert(entity); + // remove entity from the list + item_itr = _movingEntities.erase(item_itr); + entity->setSimulationState(EntityItem::Static); + } else { + moveOperator.addEntityToMoveList(entity, oldCube, newCube); + EntityItem::SimulationState newState = entity->computeSimulationState(); + if (newState != EntityItem::Moving) { + if (newState == EntityItem::Mortal) { + _mortalEntities.push_back(entity); + } + // remove entity from the list + item_itr = _movingEntities.erase(item_itr); + entity->setSimulationState(newState); + } else { + ++item_itr; + } + } + } + } + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + _entityTree->recurseTreeWithOperator(&moveOperator); + } + } +} + +void SimpleEntitySimulation::updateMortalEntities(quint64 now, QSet& entitiesToDelete) { + QList::iterator item_itr = _mortalEntities.begin(); + while (item_itr != _mortalEntities.end()) { + EntityItem* entity = *item_itr; + // always check to see if the lifetime has expired, for immortal entities this is always false + if (entity->lifetimeHasExpired()) { + qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); + entitiesToDelete.insert(entity); + // remove entity from the list + item_itr = _mortalEntities.erase(item_itr); + entity->setSimulationState(EntityItem::Static); + } else { + // check to see if this entity is no longer moving + EntityItem::SimulationState newState = entity->computeSimulationState(); + if (newState != EntityItem::Mortal) { + if (newState == EntityItem::Moving) { + entity->update(now); + _movingEntities.push_back(entity); + } + // remove entity from the list + item_itr = _mortalEntities.erase(item_itr); + entity->setSimulationState(newState); + } else { + ++item_itr; + } + } + } +} + +void SimpleEntitySimulation::updateEntityState(EntityItem* entity) { + EntityItem::SimulationState oldState = entity->getSimulationState(); + EntityItem::SimulationState newState = entity->computeSimulationState(); + if (newState != oldState) { + switch (oldState) { + case EntityItem::Moving: + _movingEntities.removeAll(entity); + break; + + case EntityItem::Mortal: + _mortalEntities.removeAll(entity); + break; + + default: + break; + } + + switch (newState) { + case EntityItem::Moving: + _movingEntities.push_back(entity); + break; + + case EntityItem::Mortal: + _mortalEntities.push_back(entity); + break; + + default: + break; + } + entity->setSimulationState(newState); + } +} + +void SimpleEntitySimulation::clearEntityState(EntityItem* entity) { + EntityItem::SimulationState oldState = entity->getSimulationState(); + switch (oldState) { + case EntityItem::Moving: + _movingEntities.removeAll(entity); + break; + + case EntityItem::Mortal: + _mortalEntities.removeAll(entity); + break; + + default: + break; + } + entity->setSimulationState(EntityItem::Static); +} + + diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h new file mode 100644 index 0000000000..7d0e8f0113 --- /dev/null +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -0,0 +1,47 @@ +// +// SimpleEntitySimulation.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.24 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SimpleEntitySimulation_h +#define hifi_SimpleEntitySimulation_h + +#include "EntitySimulation.h" + +/// provides simple velocity + gravity extrapolation of EntityItem's + +class SimpleEntitySimulation : public EntitySimulation { +public: + SimpleEntitySimulation() : EntitySimulation() { } + virtual ~SimpleEntitySimulation() { setEntityTree(NULL); } + + virtual void update(QSet& entitiesToDelete); + + virtual void addEntity(EntityItem* entity); + virtual void removeEntity(EntityItem* entity); + virtual void entityChanged(EntityItem* entity); + + virtual void clearEntities(); + +protected: + void updateEntityState(EntityItem* entity); + void clearEntityState(EntityItem* entity); + + QList& getMovingEntities() { return _movingEntities; } + + void updateChangedEntities(quint64 now, QSet& entitiesToDelete); + void updateMovingEntities(quint64 now, QSet& entitiesToDelete); + void updateMortalEntities(quint64 now, QSet& entitiesToDelete); + + QSet _changedEntities; // entities that have changed in the last frame + QList _movingEntities; // entities that need to be updated + QList _mortalEntities; // non-moving entities that need to be checked for expiry +}; + +#endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 2999f34fb6..4ee0865243 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -35,7 +35,6 @@ public: OctreeRenderer(); virtual ~OctreeRenderer(); - virtual Octree* createTree() = 0; virtual char getMyNodeType() const = 0; virtual PacketType getMyQueryMessageType() const = 0; virtual PacketType getExpectedPacketType() const = 0; @@ -81,6 +80,8 @@ public: int getOpaqueMeshPartsRendered() const { return _opaqueMeshPartsRendered; } protected: + virtual Octree* createTree() = 0; + Octree* _tree; bool _managedTree; ViewFrustum* _viewFrustum; diff --git a/libraries/voxels/src/VoxelTreeHeadlessViewer.h b/libraries/voxels/src/VoxelTreeHeadlessViewer.h index 4acd5aa52d..291ad7f813 100644 --- a/libraries/voxels/src/VoxelTreeHeadlessViewer.h +++ b/libraries/voxels/src/VoxelTreeHeadlessViewer.h @@ -27,7 +27,6 @@ public: VoxelTreeHeadlessViewer(); virtual ~VoxelTreeHeadlessViewer(); - virtual Octree* createTree() { return new VoxelTree(true); } virtual char getMyNodeType() const { return NodeType::VoxelServer; } virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; } virtual PacketType getExpectedPacketType() const { return PacketTypeVoxelData; } @@ -35,6 +34,9 @@ public: VoxelTree* getTree() { return (VoxelTree*)_tree; } virtual void init(); + +protected: + virtual Octree* createTree() { return new VoxelTree(true); } }; #endif // hifi_VoxelTreeHeadlessViewer_h