Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

This commit is contained in:
Andrzej Kapolka 2014-12-03 16:49:10 -08:00
commit 6764f29cd2
25 changed files with 655 additions and 413 deletions

View file

@ -11,6 +11,7 @@
#include <QTimer>
#include <EntityTree.h>
#include <SimpleEntitySimulation.h>
#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;
}

View file

@ -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

View file

@ -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);

View file

@ -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:

View file

@ -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]);
}
}

View file

@ -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) {

View file

@ -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" });

View file

@ -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...
}
{

View file

@ -30,8 +30,8 @@
#include <QUndoStack>
#include <QSystemTrayIcon>
#include <EntityEditPacketSender.h>
#include <EntityCollisionSystem.h>
#include <EntityEditPacketSender.h>
#include <NetworkPacket.h>
#include <NodeList.h>
#include <PacketHeaders.h>

View file

@ -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);

View file

@ -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<EntityTreeElement*>(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!

View file

@ -16,27 +16,32 @@
#include <CollisionInfo.h>
#include <HeadData.h>
#include <HandData.h>
#include <PerfStat.h>
#include <SphereShape.h>
#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<EntityItem*>& 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);
}

View file

@ -20,11 +20,12 @@
#include <AvatarHashMap.h>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
#include <SharedUtil.h>
#include <VoxelDetail.h>
#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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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 <QSet>
#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<EntityItem*>& 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

View file

@ -12,13 +12,14 @@
#include <PerfStat.h>
#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<EntityItemID> 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<EntityItemID> entitiesToDelete;
updateChangedEntities(now, entitiesToDelete);
updateMovingEntities(now, entitiesToDelete);
updateMortalEntities(now, entitiesToDelete);
if (entitiesToDelete.size() > 0) {
deleteEntities(entitiesToDelete);
}
unlock();
}
void EntityTree::updateChangedEntities(quint64 now, QSet<EntityItemID>& 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<EntityItemID>& entitiesToDelete) {
PerformanceTimer perfTimer("updateMovingEntities");
if (_movingEntities.size() > 0) {
MovingEntitiesOperator moveOperator(this);
{
PerformanceTimer perfTimer("_movingEntities");
QList<EntityItem*>::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<EntityItem*> entitiesToDelete;
_simulation->update(entitiesToDelete);
if (entitiesToDelete.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> 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<EntityItemID>& entitiesToDelete) {
QList<EntityItem*>::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

View file

@ -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<EntityItemID> 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<EntityItem*>& 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<EntityItemID>& entitiesToDelete);
void updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
void updateMortalEntities(quint64 now, QSet<EntityItemID>& 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<EntityItemID, EntityTreeElement*> _entityToElementMap;
QList<EntityItem*> _movingEntities; // entities that need to be updated
QList<EntityItem*> _mortalEntities; // entities that need to be checked for expiry
QSet<EntityItem*> _changedEntities; // entities that have changed in the last frame
EntitySimulation* _simulation;
};
#endif // hifi_EntityTree_h

View file

@ -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

View file

@ -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<EntityTree*>(_tree);
simpleSimulation->setEntityTree(entityTree);
entityTree->setSimulation(simpleSimulation);
_simulation = simpleSimulation;
}
}
void EntityTreeHeadlessViewer::update() {
if (_tree) {
EntityTree* tree = static_cast<EntityTree*>(_tree);

View file

@ -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

View file

@ -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 <AACube.h>
#include <PerfStat.h>
#include "EntityItem.h"
#include "MovingEntitiesOperator.h"
#include "SimpleEntitySimulation.h"
void SimpleEntitySimulation::update(QSet<EntityItem*>& 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<EntityItem*>& 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<EntityItem*>& entitiesToDelete) {
if (_entityTree && _movingEntities.size() > 0) {
PerformanceTimer perfTimer("_movingEntities");
MovingEntitiesOperator moveOperator(_entityTree);
QList<EntityItem*>::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<EntityItem*>& entitiesToDelete) {
QList<EntityItem*>::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);
}

View file

@ -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<EntityItem*>& 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<EntityItem*>& getMovingEntities() { return _movingEntities; }
void updateChangedEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
void updateMovingEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
void updateMortalEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
QSet<EntityItem*> _changedEntities; // entities that have changed in the last frame
QList<EntityItem*> _movingEntities; // entities that need to be updated
QList<EntityItem*> _mortalEntities; // non-moving entities that need to be checked for expiry
};
#endif // hifi_SimpleEntitySimulation_h

View file

@ -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;

View file

@ -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