Merge pull request #4772 from AndrewMeadows/nova

PhysicsEngine cleanup in preparation for collidable avatars
This commit is contained in:
Philip Rosedale 2015-05-07 14:33:35 -07:00
commit b7b62c8d42
33 changed files with 1719 additions and 1321 deletions

View file

@ -71,6 +71,7 @@
#include <NetworkingConstants.h>
#include <OctalCode.h>
#include <OctreeSceneStats.h>
#include <ObjectMotionState.h>
#include <PacketHeaders.h>
#include <PathUtils.h>
#include <PerfStat.h>
@ -432,6 +433,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList.data(), &NodeList::uuidChanged, _myAvatar, &MyAvatar::setSessionUUID);
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
@ -2119,19 +2121,20 @@ void Application::init() {
_entities.init();
_entities.setViewFrustum(getViewFrustum());
EntityTree* tree = _entities.getTree();
_physicsEngine.setEntityTree(tree);
tree->setSimulation(&_physicsEngine);
_physicsEngine.init(&_entityEditSender);
ObjectMotionState::setShapeManager(&_shapeManager);
_physicsEngine.init();
EntityTree* tree = _entities.getTree();
_entitySimulation.init(tree, &_physicsEngine, &_shapeManager, &_entityEditSender);
tree->setSimulation(&_entitySimulation);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity,
entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity);
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity,
&_entities, &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
@ -2415,8 +2418,22 @@ void Application::update(float deltaTime) {
{
PerformanceTimer perfTimer("physics");
_myAvatar->relayDriveKeysToCharacterController();
_entitySimulation.lock();
_physicsEngine.deleteObjects(_entitySimulation.getObjectsToDelete());
_physicsEngine.addObjects(_entitySimulation.getObjectsToAdd());
_physicsEngine.changeObjects(_entitySimulation.getObjectsToChange());
_entitySimulation.unlock();
_physicsEngine.stepSimulation();
_physicsEngine.dumpStatsIfNecessary();
if (_physicsEngine.hasOutgoingChanges()) {
_entitySimulation.lock();
_entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges());
_entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents());
_entitySimulation.unlock();
_physicsEngine.dumpStatsIfNecessary();
}
}
if (!_aboutToQuit) {
@ -3948,6 +3965,9 @@ bool Application::acceptURL(const QString& urlString) {
return false;
}
void Application::setSessionUUID(const QUuid& sessionUUID) {
_physicsEngine.setSessionUUID(sessionUUID);
}
bool Application::askToSetAvatarUrl(const QString& url) {
QUrl realUrl(url);

View file

@ -33,8 +33,10 @@
#include <OctreeQuery.h>
#include <OffscreenUi.h>
#include <PacketHeaders.h>
#include <PhysicalEntitySimulation.h>
#include <PhysicsEngine.h>
#include <ScriptEngine.h>
#include <ShapeManager.h>
#include <StDev.h>
#include <TextureCache.h>
#include <ViewFrustum.h>
@ -371,6 +373,7 @@ signals:
void beforeAboutToQuit();
public slots:
void setSessionUUID(const QUuid& sessionUUID);
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
void nodeAdded(SharedNodePointer node);
@ -525,6 +528,8 @@ private:
bool _justStarted;
Stars _stars;
ShapeManager _shapeManager;
PhysicalEntitySimulation _entitySimulation;
PhysicsEngine _physicsEngine;
EntityTreeRenderer _entities;

View file

@ -7,4 +7,9 @@ add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
link_hifi_libraries(shared gpu script-engine render-utils)
add_dependency_external_projects(bullet)
find_package(Bullet REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BULLET_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
link_hifi_libraries(shared gpu script-engine render-utils)

View file

@ -14,16 +14,17 @@
#include <glm/gtx/quaternion.hpp>
#include <gpu/GPUConfig.h>
#include <DeferredLightingEffect.h>
#include <PhysicsEngine.h>
#include "RenderableDebugableEntityItem.h"
void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut) {
void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args,
float puffedOut, glm::vec4& color) {
glm::vec3 position = entity->getPosition();
glm::vec3 center = entity->getCenter();
glm::vec3 dimensions = entity->getDimensions();
glm::quat rotation = entity->getRotation();
glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
@ -33,15 +34,36 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, dimensions.z);
if (puffedOut) {
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.2f, greenColor);
} else {
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.0f, greenColor);
}
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(1.0f + puffedOut, color);
glPopMatrix();
glPopMatrix();
}
void RenderableDebugableEntityItem::renderHoverDot(EntityItem* entity, RenderArgs* args) {
glm::vec3 position = entity->getPosition();
glm::vec3 center = entity->getCenter();
glm::vec3 dimensions = entity->getDimensions();
glm::quat rotation = entity->getRotation();
glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f);
float radius = 0.05f;
glPushMatrix();
glTranslatef(position.x, position.y + dimensions.y + radius, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(radius, radius, radius);
const int SLICES = 8;
const int STACKS = 8;
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(0.5f, SLICES, STACKS, blueColor);
glPopMatrix();
glPopMatrix();
}
void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) {
bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP;
@ -49,7 +71,17 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args)
if (debugSimulationOwnership) {
quint64 now = usecTimestampNow();
if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) {
renderBoundingBox(entity, args, true);
glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f);
renderBoundingBox(entity, args, 0.2f, redColor);
}
if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) {
glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f);
renderBoundingBox(entity, args, 0.3f, yellowColor);
}
if (PhysicsEngine::physicsInfoIsActive(entity->getPhysicsInfo())) {
renderHoverDot(entity, args);
}
}
}

View file

@ -16,7 +16,8 @@
class RenderableDebugableEntityItem {
public:
static void renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut);
static void renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color);
static void renderHoverDot(EntityItem* entity, RenderArgs* args);
static void render(EntityItem* entity, RenderArgs* args);
};

View file

@ -192,7 +192,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
}
if (!didDraw) {
RenderableDebugableEntityItem::renderBoundingBox(this, args, false);
glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f);
RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor);
}
RenderableDebugableEntityItem::render(this, args);
@ -277,6 +278,11 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
return false; // hmm...
}
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
return false;
}
assert(!_model->getCollisionURL().isEmpty());
if (_model->getURL().isEmpty()) {

View file

@ -64,9 +64,10 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
_simulatorIDChangedTime(0),
_marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID),
_name(ENTITY_ITEM_DEFAULT_NAME),
_physicsInfo(NULL),
_dirtyFlags(0),
_element(NULL)
_element(nullptr),
_physicsInfo(nullptr),
_simulated(false)
{
quint64 now = usecTimestampNow();
_lastSimulated = now;
@ -79,9 +80,11 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert
}
EntityItem::~EntityItem() {
// be sure to clean up _physicsInfo before calling this dtor
assert(_physicsInfo == NULL);
assert(_element == NULL);
// these pointers MUST be NULL at delete, else we probably have a dangling backpointer
// to this EntityItem in the corresponding data structure.
assert(!_simulated);
assert(!_element);
assert(!_physicsInfo);
}
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
@ -310,10 +313,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates.
glm::vec3 savePosition = _position;
glm::quat saveRotation = _rotation;
glm::vec3 saveVelocity = _velocity;
glm::vec3 saveAngularVelocity = _angularVelocity;
glm::vec3 saveGravity = _gravity;
glm::vec3 saveAcceleration = _acceleration;
// glm::vec3 saveVelocity = _velocity;
// glm::vec3 saveAngularVelocity = _angularVelocity;
// glm::vec3 saveGravity = _gravity;
// glm::vec3 saveAcceleration = _acceleration;
// Header bytes
@ -398,7 +401,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (lastEditedFromBufferAdjusted > now) {
lastEditedFromBufferAdjusted = now;
}
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
#ifdef WANT_DEBUG
@ -542,7 +545,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits);
}
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) {
READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, updateAcceleration);
READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, setAcceleration);
}
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
@ -584,7 +587,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
}
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) {
// NOTE: This code is attempting to "repair" the old data we just got from the server to make it more
// closely match where the entities should be if they'd stepped forward in time to "now". The server
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
@ -615,10 +618,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// this node, so our version has to be newer than what the packet contained.
_position = savePosition;
_rotation = saveRotation;
_velocity = saveVelocity;
_angularVelocity = saveAngularVelocity;
_gravity = saveGravity;
_acceleration = saveAcceleration;
// _velocity = saveVelocity;
// _angularVelocity = saveAngularVelocity;
// _gravity = saveGravity;
// _acceleration = saveAcceleration;
}
return bytesRead;
@ -659,12 +662,17 @@ void EntityItem::setDensity(float density) {
_density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
const float ACTIVATION_RELATIVE_DENSITY_DELTA = 0.01f; // 1 percent
void EntityItem::updateDensity(float density) {
const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent
float newDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) {
_density = newDensity;
_dirtyFlags |= EntityItem::DIRTY_MASS;
float clampedDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
if (_density != clampedDensity) {
_density = clampedDensity;
if (fabsf(_density - clampedDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) {
// the density has changed enough that we should update the physics simulation
_dirtyFlags |= EntityItem::DIRTY_MASS;
}
}
}
@ -918,7 +926,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, updateAcceleration);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
@ -947,7 +955,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
if (_created != UNKNOWN_CREATED_TIME) {
setLastEdited(now);
}
if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) {
// TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases.
_lastSimulated = now;
}
@ -1097,14 +1105,22 @@ void EntityItem::computeShapeInfo(ShapeInfo& info) {
info.setParams(getShapeType(), 0.5f * getDimensions());
}
const float MIN_POSITION_DELTA = 0.0001f;
const float MIN_DIMENSIONS_DELTA = 0.0005f;
const float MIN_ALIGNMENT_DOT = 0.999999f;
const float MIN_VELOCITY_DELTA = 0.01f;
const float MIN_DAMPING_DELTA = 0.001f;
const float MIN_GRAVITY_DELTA = 0.001f;
const float MIN_ACCELERATION_DELTA = 0.001f;
const float MIN_SPIN_DELTA = 0.0003f;
// these thesholds determine what updates will be ignored (client and server)
const float IGNORE_POSITION_DELTA = 0.0001f;
const float IGNORE_DIMENSIONS_DELTA = 0.0005f;
const float IGNORE_ALIGNMENT_DOT = 0.99997f;
const float IGNORE_LINEAR_VELOCITY_DELTA = 0.001f;
const float IGNORE_DAMPING_DELTA = 0.001f;
const float IGNORE_GRAVITY_DELTA = 0.001f;
const float IGNORE_ANGULAR_VELOCITY_DELTA = 0.0002f;
// these thresholds determine what updates will activate the physical object
const float ACTIVATION_POSITION_DELTA = 0.005f;
const float ACTIVATION_DIMENSIONS_DELTA = 0.005f;
const float ACTIVATION_ALIGNMENT_DOT = 0.99990f;
const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f;
const float ACTIVATION_GRAVITY_DELTA = 0.1f;
const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f;
void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) {
glm::vec3 position = value * (float)TREE_SCALE;
@ -1112,10 +1128,13 @@ void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) {
}
void EntityItem::updatePosition(const glm::vec3& value) {
if (value != _position) {
auto distance = glm::distance(_position, value);
_dirtyFlags |= (distance > MIN_POSITION_DELTA) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE;
auto delta = glm::distance(_position, value);
if (delta > IGNORE_POSITION_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_POSITION;
_position = value;
if (delta > ACTIVATION_POSITION_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
}
}
@ -1125,17 +1144,27 @@ void EntityItem::updateDimensionsInDomainUnits(const glm::vec3& value) {
}
void EntityItem::updateDimensions(const glm::vec3& value) {
if (glm::distance(_dimensions, value) > MIN_DIMENSIONS_DELTA) {
auto delta = glm::distance(_dimensions, value);
if (delta > IGNORE_DIMENSIONS_DELTA) {
_dimensions = value;
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
if (delta > ACTIVATION_DIMENSIONS_DELTA) {
// rebuilding the shape will always activate
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
}
void EntityItem::updateRotation(const glm::quat& rotation) {
if (rotation != _rotation) {
auto alignmentDot = glm::abs(glm::dot(_rotation, rotation));
_dirtyFlags |= (alignmentDot < MIN_ALIGNMENT_DOT) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE;
if (_rotation != rotation) {
_rotation = rotation;
auto alignmentDot = glm::abs(glm::dot(_rotation, rotation));
if (alignmentDot < IGNORE_ALIGNMENT_DOT) {
_dirtyFlags |= EntityItem::DIRTY_ROTATION;
}
if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
}
}
@ -1156,9 +1185,11 @@ void EntityItem::updateMass(float mass) {
newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent
if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) {
_density = newDensity;
float oldDensity = _density;
_density = newDensity;
if (fabsf(_density - oldDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) {
// the density has changed enough that we should update the physics simulation
_dirtyFlags |= EntityItem::DIRTY_MASS;
}
}
@ -1169,19 +1200,26 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) {
}
void EntityItem::updateVelocity(const glm::vec3& value) {
if (glm::distance(_velocity, value) > MIN_VELOCITY_DELTA) {
if (glm::length(value) < MIN_VELOCITY_DELTA) {
auto delta = glm::distance(_velocity, value);
if (delta > IGNORE_LINEAR_VELOCITY_DELTA) {
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
_velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_velocity = value;
}
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
}
}
void EntityItem::updateDamping(float value) {
if (fabsf(_damping - value) > MIN_DAMPING_DELTA) {
_damping = glm::clamp(value, 0.0f, 1.0f);
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
if (fabsf(_damping - clampedDamping) > IGNORE_DAMPING_DELTA) {
_damping = clampedDamping;
_dirtyFlags |= EntityItem::DIRTY_MATERIAL;
}
}
@ -1192,34 +1230,36 @@ void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) {
}
void EntityItem::updateGravity(const glm::vec3& value) {
if (glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) {
auto delta = glm::distance(_gravity, value);
if (delta > IGNORE_GRAVITY_DELTA) {
_gravity = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
}
void EntityItem::updateAcceleration(const glm::vec3& value) {
if (glm::distance(_acceleration, value) > MIN_ACCELERATION_DELTA) {
_acceleration = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
if (delta > ACTIVATION_GRAVITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
}
}
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
auto distance = glm::distance(_angularVelocity, value);
if (distance > MIN_SPIN_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
if (glm::length(value) < MIN_SPIN_DELTA) {
auto delta = glm::distance(_angularVelocity, value);
if (delta > IGNORE_ANGULAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_ANGULAR_VELOCITY;
const float MIN_ANGULAR_SPEED = 0.0002f;
if (glm::length(value) < MIN_ANGULAR_SPEED) {
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_angularVelocity = value;
}
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
}
}
void EntityItem::updateAngularDamping(float value) {
if (fabsf(_angularDamping - value) > MIN_DAMPING_DELTA) {
_angularDamping = glm::clamp(value, 0.0f, 1.0f);
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
if (fabsf(_angularDamping - clampedDamping) > IGNORE_DAMPING_DELTA) {
_angularDamping = clampedDamping;
_dirtyFlags |= EntityItem::DIRTY_MATERIAL;
}
}

View file

@ -28,7 +28,7 @@
#include "EntityItemPropertiesDefaults.h"
#include "EntityTypes.h"
class EntityTree;
class EntitySimulation;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
@ -44,19 +44,28 @@ class EntityTreeElementExtraEncodeData;
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features.
class EntityItem {
// These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted.
// To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by
// the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to
// do cleanup.
friend class EntityTreeElement;
friend class EntitySimulation;
public:
enum EntityDirtyFlags {
DIRTY_POSITION = 0x0001,
DIRTY_VELOCITY = 0x0002,
DIRTY_MASS = 0x0004,
DIRTY_COLLISION_GROUP = 0x0008,
DIRTY_MOTION_TYPE = 0x0010,
DIRTY_SHAPE = 0x0020,
DIRTY_LIFETIME = 0x0040,
DIRTY_UPDATEABLE = 0x0080,
DIRTY_MATERIAL = 0x00100,
DIRTY_PHYSICS_NO_WAKE = 0x0200 // we want to update values in physics engine without "waking" the object up
DIRTY_ROTATION = 0x0002,
DIRTY_LINEAR_VELOCITY = 0x0004,
DIRTY_ANGULAR_VELOCITY = 0x0008,
DIRTY_MASS = 0x0010,
DIRTY_COLLISION_GROUP = 0x0020,
DIRTY_MOTION_TYPE = 0x0040,
DIRTY_SHAPE = 0x0080,
DIRTY_LIFETIME = 0x0100,
DIRTY_UPDATEABLE = 0x0200,
DIRTY_MATERIAL = 0x00400,
DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object
DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION,
DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY
};
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
@ -66,7 +75,7 @@ public:
virtual ~EntityItem();
// ID and EntityItemID related methods
QUuid getID() const { return _id; }
const QUuid& getID() const { return _id; }
void setID(const QUuid& id) { _id = id; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
@ -95,6 +104,10 @@ public:
float getEditedAgo() const /// Elapsed seconds since this entity was last edited
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
/// Last time we sent out an edit packet for this entity
quint64 getLastBroadcast() const { return _lastBroadcast; }
void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
quint64 getLastChangedOnServer() const { return _changedOnServer; }
@ -243,6 +256,8 @@ public:
bool getCollisionsWillMove() const { return _collisionsWillMove; }
void setCollisionsWillMove(bool value) { _collisionsWillMove = value; }
virtual bool shouldBePhysical() const { return !_ignoreForCollisions; }
bool getLocked() const { return _locked; }
void setLocked(bool value) { _locked = value; }
@ -281,7 +296,6 @@ public:
void updateDamping(float value);
void updateGravityInDomainUnits(const glm::vec3& value);
void updateGravity(const glm::vec3& value);
void updateAcceleration(const glm::vec3& value);
void updateAngularVelocity(const glm::vec3& value);
void updateAngularVelocityInDegrees(const glm::vec3& value) { updateAngularVelocity(glm::radians(value)); }
void updateAngularDamping(float value);
@ -296,8 +310,8 @@ public:
bool isMoving() const;
void* getPhysicsInfo() const { return _physicsInfo; }
void setPhysicsInfo(void* data) { _physicsInfo = data; }
void setPhysicsInfo(void* data) { _physicsInfo = data; }
EntityTreeElement* getElement() const { return _element; }
static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; }
@ -320,6 +334,7 @@ protected:
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes
quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes
quint64 _lastEdited; // last official local or remote edit time
quint64 _lastBroadcast; // the last time we sent an edit packet about this entity
quint64 _lastEditedFromRemote; // last time we received and edit from the server
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received an edit from the server (in server-time-frame)
@ -371,16 +386,23 @@ protected:
/// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis
void setRadius(float value);
// _physicsInfo is a hook reserved for use by the EntitySimulation, which is guaranteed to set _physicsInfo
// to a non-NULL value when the EntityItem has a representation in the physics engine.
void* _physicsInfo = NULL; // only set by EntitySimulation
// DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about.
uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation
EntityTreeElement* _element; // back pointer to containing Element
// these backpointers are only ever set/cleared by friends:
EntityTreeElement* _element = nullptr; // set by EntityTreeElement
void* _physicsInfo = nullptr; // set by EntitySimulation
bool _simulated; // set by EntitySimulation
};
extern const float IGNORE_POSITION_DELTA;
extern const float IGNORE_DIMENSIONS_DELTA;
extern const float IGNORE_ALIGNMENT_DOT;
extern const float IGNORE_LINEAR_VELOCITY_DELTA;
extern const float IGNORE_DAMPING_DELTA;
extern const float IGNORE_GRAVITY_DELTA;
extern const float IGNORE_ANGULAR_VELOCITY_DELTA;
#endif // hifi_EntityItem_h

View file

@ -86,6 +86,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro
if (_entityTree) {
_entityTree->lockForWrite();
EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID);
entity->setLastBroadcast(usecTimestampNow());
if (entity) {
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
setSimId(propertiesWithSimID, entity);
@ -178,6 +179,7 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E
if (propertiesWithSimID.getType() == EntityTypes::Unknown) {
EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
entity->setLastBroadcast(usecTimestampNow());
propertiesWithSimID.setType(entity->getType());
setSimId(propertiesWithSimID, entity);
}

View file

@ -19,41 +19,67 @@ void EntitySimulation::setEntityTree(EntityTree* tree) {
if (_entityTree && _entityTree != tree) {
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_updateableEntities.clear();
_entitiesToBeSorted.clear();
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
}
_entityTree = tree;
}
void EntitySimulation::updateEntities(QSet<EntityItem*>& entitiesToDelete) {
void EntitySimulation::updateEntities() {
quint64 now = usecTimestampNow();
// these methods may accumulate entries in _entitiesToBeDeleted
expireMortalEntities(now);
callUpdateOnEntitiesThatNeedIt(now);
moveSimpleKinematics(now);
updateEntitiesInternal(now);
sortEntitiesThatMoved();
}
// at this point we harvest _entitiesToBeDeleted
entitiesToDelete.unite(_entitiesToDelete);
void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
for (auto entityItr : _entitiesToDelete) {
EntityItem* entity = &(*entityItr);
// this entity is still in its tree, so we insert into the external list
entitiesToDelete.push_back(entity);
++entityItr;
}
_entitiesToDelete.clear();
}
// private
void EntitySimulation::addEntityInternal(EntityItem* entity) {
if (entity->isMoving() && !entity->getPhysicsInfo()) {
_simpleKinematicEntities.insert(entity);
}
}
void EntitySimulation::changeEntityInternal(EntityItem* entity) {
if (entity->isMoving() && !entity->getPhysicsInfo()) {
_simpleKinematicEntities.insert(entity);
} else {
_simpleKinematicEntities.remove(entity);
}
}
// protected
void EntitySimulation::expireMortalEntities(const quint64& now) {
if (now > _nextExpiry) {
// only search for expired entities if we expect to find one
_nextExpiry = quint64(-1);
QSet<EntityItem*>::iterator itemItr = _mortalEntities.begin();
SetOfEntities::iterator itemItr = _mortalEntities.begin();
while (itemItr != _mortalEntities.end()) {
EntityItem* entity = *itemItr;
quint64 expiry = entity->getExpiry();
if (expiry < now) {
_entitiesToDelete.insert(entity);
itemItr = _mortalEntities.erase(itemItr);
_updateableEntities.remove(entity);
_entitiesToBeSorted.remove(entity);
_entitiesToUpdate.remove(entity);
_entitiesToSort.remove(entity);
_simpleKinematicEntities.remove(entity);
removeEntityInternal(entity);
_allEntities.remove(entity);
entity->_simulated = false;
} else {
if (expiry < _nextExpiry) {
// remeber the smallest _nextExpiry so we know when to start the next search
@ -65,16 +91,16 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
}
}
// private
// protected
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
PerformanceTimer perfTimer("updatingEntities");
QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin();
while (itemItr != _updateableEntities.end()) {
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
while (itemItr != _entitiesToUpdate.end()) {
EntityItem* entity = *itemItr;
// TODO: catch transition from needing update to not as a "change"
// so we don't have to scan for it here.
if (!entity->needsToCallUpdate()) {
itemItr = _updateableEntities.erase(itemItr);
itemItr = _entitiesToUpdate.erase(itemItr);
} else {
entity->update(now);
++itemItr;
@ -82,15 +108,15 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
}
}
// private
// protected
void EntitySimulation::sortEntitiesThatMoved() {
// NOTE: this is only for entities that have been moved by THIS EntitySimulation.
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
PerformanceTimer perfTimer("sortingEntities");
MovingEntitiesOperator moveOperator(_entityTree);
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
QSet<EntityItem*>::iterator itemItr = _entitiesToBeSorted.begin();
while (itemItr != _entitiesToBeSorted.end()) {
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
while (itemItr != _entitiesToSort.end()) {
EntityItem* entity = *itemItr;
// check to see if this movement has sent the entity outside of the domain.
AACube newCube = entity->getMaximumAACube();
@ -98,9 +124,14 @@ void EntitySimulation::sortEntitiesThatMoved() {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
_entitiesToDelete.insert(entity);
_mortalEntities.remove(entity);
_updateableEntities.remove(entity);
_entitiesToUpdate.remove(entity);
_simpleKinematicEntities.remove(entity);
removeEntityInternal(entity);
itemItr = _entitiesToBeSorted.erase(itemItr);
_allEntities.remove(entity);
entity->_simulated = false;
itemItr = _entitiesToSort.erase(itemItr);
} else {
moveOperator.addEntityToMoveList(entity, newCube);
++itemItr;
@ -111,8 +142,7 @@ void EntitySimulation::sortEntitiesThatMoved() {
_entityTree->recurseTreeWithOperator(&moveOperator);
}
sortEntitiesThatMovedInternal();
_entitiesToBeSorted.clear();
_entitiesToSort.clear();
}
void EntitySimulation::addEntity(EntityItem* entity) {
@ -125,10 +155,13 @@ void EntitySimulation::addEntity(EntityItem* entity) {
}
}
if (entity->needsToCallUpdate()) {
_updateableEntities.insert(entity);
_entitiesToUpdate.insert(entity);
}
addEntityInternal(entity);
_allEntities.insert(entity);
entity->_simulated = true;
// DirtyFlags are used to signal changes to entities that have already been added,
// so we can clear them for this entity which has just been added.
entity->clearDirtyFlags();
@ -136,15 +169,25 @@ void EntitySimulation::addEntity(EntityItem* entity) {
void EntitySimulation::removeEntity(EntityItem* entity) {
assert(entity);
_updateableEntities.remove(entity);
_entitiesToUpdate.remove(entity);
_mortalEntities.remove(entity);
_entitiesToBeSorted.remove(entity);
_entitiesToSort.remove(entity);
_simpleKinematicEntities.remove(entity);
_entitiesToDelete.remove(entity);
removeEntityInternal(entity);
_allEntities.remove(entity);
entity->_simulated = false;
}
void EntitySimulation::entityChanged(EntityItem* entity) {
void EntitySimulation::changeEntity(EntityItem* entity) {
assert(entity);
if (!entity->_simulated) {
// This entity was either never added to the simulation or has been removed
// (probably for pending delete), so we don't want to keep a pointer to it
// on any internal lists.
return;
}
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
@ -158,8 +201,11 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
_entitiesToDelete.insert(entity);
_mortalEntities.remove(entity);
_updateableEntities.remove(entity);
_entitiesToUpdate.remove(entity);
_entitiesToSort.remove(entity);
_simpleKinematicEntities.remove(entity);
removeEntityInternal(entity);
entity->_simulated = false;
wasRemoved = true;
}
}
@ -177,18 +223,41 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
entity->clearDirtyFlags(EntityItem::DIRTY_LIFETIME);
}
if (entity->needsToCallUpdate()) {
_updateableEntities.insert(entity);
_entitiesToUpdate.insert(entity);
} else {
_updateableEntities.remove(entity);
_entitiesToUpdate.remove(entity);
}
entityChangedInternal(entity);
changeEntityInternal(entity);
}
}
void EntitySimulation::clearEntities() {
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_updateableEntities.clear();
_entitiesToBeSorted.clear();
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
_entitiesToDelete.clear();
clearEntitiesInternal();
for (auto entityItr : _allEntities) {
entityItr->_simulated = false;
}
_allEntities.clear();
}
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
while (itemItr != _simpleKinematicEntities.end()) {
EntityItem* entity = *itemItr;
if (entity->isMoving() && !entity->getPhysicsInfo()) {
entity->simulate(now);
_entitiesToSort.insert(entity);
++itemItr;
} else {
// the entity is no longer non-physical-kinematic
itemItr = _simpleKinematicEntities.erase(itemItr);
}
}
}

View file

@ -14,23 +14,30 @@
#include <QtCore/QObject>
#include <QSet>
#include <QVector>
#include <PerfStat.h>
#include "EntityItem.h"
#include "EntityTree.h"
typedef QSet<EntityItem*> SetOfEntities;
typedef QVector<EntityItem*> VectorOfEntities;
// the EntitySimulation needs to know when these things change on an entity,
// so it can sort EntityItem or relay its state to the PhysicsEngine.
const int DIRTY_SIMULATION_FLAGS =
EntityItem::DIRTY_POSITION |
EntityItem::DIRTY_VELOCITY |
EntityItem::DIRTY_ROTATION |
EntityItem::DIRTY_LINEAR_VELOCITY |
EntityItem::DIRTY_ANGULAR_VELOCITY |
EntityItem::DIRTY_MASS |
EntityItem::DIRTY_COLLISION_GROUP |
EntityItem::DIRTY_MOTION_TYPE |
EntityItem::DIRTY_SHAPE |
EntityItem::DIRTY_LIFETIME |
EntityItem::DIRTY_UPDATEABLE;
EntityItem::DIRTY_UPDATEABLE |
EntityItem::DIRTY_MATERIAL;
class EntitySimulation : public QObject {
Q_OBJECT
@ -44,25 +51,34 @@ public:
/// \param tree pointer to EntityTree which is stored internally
void setEntityTree(EntityTree* tree);
/// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted.
void updateEntities(QSet<EntityItem*>& entitiesToDelete);
void updateEntities();
/// \param entity pointer to EntityItem to add to the simulation
/// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list
friend class EntityTree;
protected: // these only called by the EntityTree?
/// \param entity pointer to EntityItem to be added
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
void addEntity(EntityItem* entity);
/// \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
/// \param entity pointer to EntityItem to be removed
/// \brief the actual removal may happen later when appropriate data structures are locked
/// \sideeffect nulls relevant backpointers in entity
void removeEntity(EntityItem* entity);
/// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation
/// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself)
void entityChanged(EntityItem* entity);
void changeEntity(EntityItem* entity);
void clearEntities();
void moveSimpleKinematics(const quint64& now);
public:
EntityTree* getEntityTree() { return _entityTree; }
void getEntitiesToDelete(VectorOfEntities& entitiesToDelete);
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
@ -70,18 +86,10 @@ protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places.
// NOTE: updateEntitiesInternal() should clear all dirty flags on each changed entity as side effect
virtual void updateEntitiesInternal(const quint64& now) = 0;
virtual void addEntityInternal(EntityItem* entity) = 0;
virtual void addEntityInternal(EntityItem* entity);
virtual void removeEntityInternal(EntityItem* entity) = 0;
virtual void entityChangedInternal(EntityItem* entity) = 0;
virtual void sortEntitiesThatMovedInternal() {}
virtual void changeEntityInternal(EntityItem* entity);
virtual void clearEntitiesInternal() = 0;
void expireMortalEntities(const quint64& now);
@ -95,11 +103,16 @@ protected:
// We maintain multiple lists, each for its distinct purpose.
// An entity may be in more than one list.
QSet<EntityItem*> _mortalEntities; // entities that have an expiry
SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry
quint64 _nextExpiry;
QSet<EntityItem*> _updateableEntities; // entities that need update() called
QSet<EntityItem*> _entitiesToBeSorted; // entities that were moved by THIS simulation and might need to be resorted in the tree
QSet<EntityItem*> _entitiesToDelete;
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
private:
void moveSimpleKinematics();
};
#endif // hifi_EntitySimulation_h

View file

@ -145,8 +145,7 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
// squash the physics-related changes.
properties.setSimulatorIDChanged(false);
properties.setPositionChanged(false);
properties.setVelocityChanged(false);
properties.setAccelerationChanged(false);
properties.setRotationChanged(false);
} else {
qCDebug(entities) << "allowing simulatorID change";
}
@ -160,10 +159,10 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
if (newFlags) {
if (_simulation) {
if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->lock();
_simulation->entityChanged(entity);
_simulation->changeEntity(entity);
_simulation->unlock();
}
} else {
@ -351,8 +350,8 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
if (_simulation) {
_simulation->removeEntity(theEntity);
}
delete theEntity; // now actually delete the entity!
}
delete theEntity; // we can delete the entity immediately
}
if (_simulation) {
_simulation->unlock();
@ -742,7 +741,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
void EntityTree::entityChanged(EntityItem* entity) {
if (_simulation) {
_simulation->lock();
_simulation->entityChanged(entity);
_simulation->changeEntity(entity);
_simulation->unlock();
}
}
@ -750,16 +749,21 @@ void EntityTree::entityChanged(EntityItem* entity) {
void EntityTree::update() {
if (_simulation) {
lockForWrite();
QSet<EntityItem*> entitiesToDelete;
_simulation->lock();
_simulation->updateEntities(entitiesToDelete);
_simulation->updateEntities();
VectorOfEntities pendingDeletes;
_simulation->getEntitiesToDelete(pendingDeletes);
_simulation->unlock();
if (entitiesToDelete.size() > 0) {
if (pendingDeletes.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;
foreach (EntityItem* entity, entitiesToDelete) {
for (auto entityItr : pendingDeletes) {
EntityItem* entity = &(*entityItr);
assert(!entity->getPhysicsInfo()); // TODO: Andrew to remove this after testing
idsToDelete.insert(entity->getEntityItemID());
}
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
}
unlock();

View file

@ -12,13 +12,13 @@
#ifndef hifi_EntityTree_h
#define hifi_EntityTree_h
#include <QSet>
#include <QVector>
#include <Octree.h>
#include "EntityTreeElement.h"
#include "DeleteEntityOperator.h"
class Model;
class EntitySimulation;

View file

@ -33,8 +33,8 @@ EntityItem* ModelEntityItem::factory(const EntityItemID& entityID, const EntityI
}
ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
{
EntityItem(entityItemID, properties)
{
_type = EntityTypes::Model;
setProperties(properties);
_lastAnimated = usecTimestampNow();
@ -84,17 +84,17 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
}
setLastEdited(properties._lastEdited);
}
return somethingChanged;
}
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL);
if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) {
@ -105,7 +105,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL);
}
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL);
// Because we're using AnimationLoop which will reset the frame index if you change it's running state
// we want to read these values in the order they appear in the buffer, but call our setters in an
// order that allows AnimationLoop to preserve the correct frame rate.
@ -115,7 +115,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex);
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying);
if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
if (animationIsPlaying != getAnimationIsPlaying()) {
setAnimationIsPlaying(animationIsPlaying);
@ -148,12 +148,12 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_ANIMATION_SETTINGS;
requestedProperties += PROP_TEXTURES;
requestedProperties += PROP_SHAPE_TYPE;
return requestedProperties;
}
void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
@ -186,7 +186,7 @@ void ModelEntityItem::cleanupLoadedAnimations() {
Animation* ModelEntityItem::getAnimation(const QString& url) {
AnimationPointer animation;
// if we don't already have this model then create it and initialize it
if (_loadedAnimations.find(url) == _loadedAnimations.end()) {
animation = DependencyManager::get<AnimationCache>()->getAnimation(url);
@ -204,7 +204,7 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) {
}
Animation* myAnimation = getAnimation(_animationURL);
if (!_jointMappingCompleted) {
QStringList animationJointNames = myAnimation->getJointNames();
@ -229,7 +229,7 @@ QVector<glm::quat> ModelEntityItem::getAnimationFrame() {
if (animationFrameIndex < 0 || animationFrameIndex > frameCount) {
animationFrameIndex = 0;
}
QVector<glm::quat> rotations = frames[animationFrameIndex].rotations;
frameData.resize(_jointMapping.size());
@ -244,8 +244,8 @@ QVector<glm::quat> ModelEntityItem::getAnimationFrame() {
return frameData;
}
bool ModelEntityItem::isAnimatingSomething() const {
return getAnimationIsPlaying() &&
bool ModelEntityItem::isAnimatingSomething() const {
return getAnimationIsPlaying() &&
getAnimationFPS() != 0.0f &&
!getAnimationURL().isEmpty();
}
@ -278,7 +278,7 @@ void ModelEntityItem::debugDump() const {
void ModelEntityItem::updateShapeType(ShapeType type) {
// BEGIN_TEMPORARY_WORKAROUND
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
// but we are now enforcing the entity properties to be consistent. To make the possible we're
// but we are now enforcing the entity properties to be consistent. To make the possible we're
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
// _compoundShapeURL.
if (hasCompoundShapeURL()) {
@ -292,7 +292,7 @@ void ModelEntityItem::updateShapeType(ShapeType type) {
}
}
// virtual
// virtual
ShapeType ModelEntityItem::getShapeType() const {
if (_shapeType == SHAPE_TYPE_COMPOUND) {
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
@ -309,9 +309,9 @@ void ModelEntityItem::setCompoundShapeURL(const QString& url) {
}
}
void ModelEntityItem::setAnimationURL(const QString& url) {
void ModelEntityItem::setAnimationURL(const QString& url) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationURL = url;
_animationURL = url;
}
void ModelEntityItem::setAnimationFrameIndex(float value) {
@ -327,7 +327,7 @@ void ModelEntityItem::setAnimationFrameIndex(float value) {
_animationLoop.setFrameIndex(value);
}
void ModelEntityItem::setAnimationSettings(const QString& value) {
void ModelEntityItem::setAnimationSettings(const QString& value) {
// the animations setting is a JSON string that may contain various animation settings.
// if it includes fps, frameIndex, or running, those values will be parsed out and
// will over ride the regular animation settings
@ -388,21 +388,21 @@ void ModelEntityItem::setAnimationSettings(const QString& value) {
setAnimationStartAutomatically(startAutomatically);
}
_animationSettings = value;
_animationSettings = value;
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
}
void ModelEntityItem::setAnimationIsPlaying(bool value) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationLoop.setRunning(value);
_animationLoop.setRunning(value);
}
void ModelEntityItem::setAnimationFPS(float value) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationLoop.setFPS(value);
_animationLoop.setFPS(value);
}
QString ModelEntityItem::getAnimationSettings() const {
QString ModelEntityItem::getAnimationSettings() const {
// the animations setting is a JSON string that may contain various animation settings.
// if it includes fps, frameIndex, or running, those values will be parsed out and
// will over ride the regular animation settings
@ -411,7 +411,7 @@ QString ModelEntityItem::getAnimationSettings() const {
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
QJsonObject settingsAsJsonObject = settingsAsJson.object();
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
QVariant fpsValue(getAnimationFPS());
settingsMap["fps"] = fpsValue;
@ -435,10 +435,15 @@ QString ModelEntityItem::getAnimationSettings() const {
QVariant startAutomaticallyValue(getAnimationStartAutomatically());
settingsMap["startAutomatically"] = startAutomaticallyValue;
settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap);
QJsonDocument newDocument(settingsAsJsonObject);
QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact);
QString jsonByteString(jsonByteArray);
return jsonByteString;
}
// virtual
bool ModelEntityItem::shouldBePhysical() const {
return EntityItem::shouldBePhysical() && getShapeType() != SHAPE_TYPE_NONE;
}

View file

@ -117,6 +117,8 @@ public:
static const QString DEFAULT_TEXTURES;
const QString& getTextures() const { return _textures; }
void setTextures(const QString& textures) { _textures = textures; }
virtual bool shouldBePhysical() const;
static void cleanupLoadedAnimations();

View file

@ -18,32 +18,25 @@
const quint64 AUTO_REMOVE_SIMULATION_OWNER_USEC = 2 * USECS_PER_SECOND;
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
// now is usecTimestampNow()
QSet<EntityItem*>::iterator itemItr = _movingEntities.begin();
while (itemItr != _movingEntities.end()) {
EntityItem* entity = *itemItr;
if (!entity->isMoving()) {
itemItr = _movingEntities.erase(itemItr);
_movableButStoppedEntities.insert(entity);
} else {
entity->simulate(now);
_entitiesToBeSorted.insert(entity);
++itemItr;
}
}
// If an Entity has a simulation owner and we don't get an update for some amount of time,
// clear the owner. This guards against an interface failing to release the Entity when it
// has finished simulating it.
itemItr = _hasSimulationOwnerEntities.begin();
auto nodeList = DependencyManager::get<LimitedNodeList>();
SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin();
while (itemItr != _hasSimulationOwnerEntities.end()) {
EntityItem* entity = *itemItr;
if (entity->getSimulatorID().isNull()) {
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
} else if (usecTimestampNow() - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
entity->setSimulatorID(QUuid());
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
} else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
if (ownerNode.isNull() || !ownerNode->isAlive()) {
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
entity->setSimulatorID(QUuid());
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
} else {
++itemItr;
}
} else {
++itemItr;
}
@ -51,45 +44,27 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
}
void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) {
if (entity->isMoving()) {
_movingEntities.insert(entity);
} else if (entity->getCollisionsWillMove()) {
_movableButStoppedEntities.insert(entity);
}
EntitySimulation::addEntityInternal(entity);
if (!entity->getSimulatorID().isNull()) {
_hasSimulationOwnerEntities.insert(entity);
}
}
void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) {
_movingEntities.remove(entity);
_movableButStoppedEntities.remove(entity);
_hasSimulationOwnerEntities.remove(entity);
}
const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_MOTION_TYPE;
const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITIES | EntityItem::DIRTY_MOTION_TYPE;
void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) {
int dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & SIMPLE_SIMULATION_DIRTY_FLAGS) {
if (entity->isMoving()) {
_movingEntities.insert(entity);
} else if (entity->getCollisionsWillMove()) {
_movableButStoppedEntities.remove(entity);
} else {
_movingEntities.remove(entity);
_movableButStoppedEntities.remove(entity);
}
if (!entity->getSimulatorID().isNull()) {
_hasSimulationOwnerEntities.insert(entity);
}
void SimpleEntitySimulation::changeEntityInternal(EntityItem* entity) {
EntitySimulation::changeEntityInternal(entity);
if (!entity->getSimulatorID().isNull()) {
_hasSimulationOwnerEntities.insert(entity);
}
entity->clearDirtyFlags();
}
void SimpleEntitySimulation::clearEntitiesInternal() {
_movingEntities.clear();
_movableButStoppedEntities.clear();
_hasSimulationOwnerEntities.clear();
}

View file

@ -25,12 +25,10 @@ protected:
virtual void updateEntitiesInternal(const quint64& now);
virtual void addEntityInternal(EntityItem* entity);
virtual void removeEntityInternal(EntityItem* entity);
virtual void entityChangedInternal(EntityItem* entity);
virtual void changeEntityInternal(EntityItem* entity);
virtual void clearEntitiesInternal();
QSet<EntityItem*> _movingEntities;
QSet<EntityItem*> _movableButStoppedEntities;
QSet<EntityItem*> _hasSimulationOwnerEntities;
SetOfEntities _hasSimulationOwnerEntities;
};
#endif // hifi_SimpleEntitySimulation_h

View file

@ -484,7 +484,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
meshPart._material->setShininess(material->shininess);
meshPart._material->setOpacity(material->opacity);
}
qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count();
// qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count();
foreach(OBJFace face, faceGroup) {
glm::vec3 v0 = vertices[face.vertexIndices[0]];
glm::vec3 v1 = vertices[face.vertexIndices[1]];
@ -526,7 +526,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
mesh.meshExtents.addPoint(vertex);
geometry.meshExtents.addPoint(vertex);
}
fbxDebugDump(geometry);
// fbxDebugDump(geometry);
} catch(const std::exception& e) {
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
}

View file

@ -9,16 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "BulletUtil.h"
#include "ContactInfo.h"
void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) {
void ContactInfo::update(uint32_t currentStep, const btManifoldPoint& p) {
_lastStep = currentStep;
++_numSteps;
contactPoint = bulletToGLM(p.m_positionWorldOnB) + worldOffset;
penetration = bulletToGLM(p.m_distance1 * p.m_normalWorldOnB);
// TODO: also report normal
//_normal = bulletToGLM(p.m_normalWorldOnB);
positionWorldOnB = p.m_positionWorldOnB;
normalWorldOnB = p.m_normalWorldOnB;
distance = p.m_distance1;
}
ContactEventType ContactInfo::computeType(uint32_t thisStep) {

View file

@ -17,17 +17,18 @@
#include "RegisteredMetaTypes.h"
enum ContactEventType {
CONTACT_EVENT_TYPE_START,
CONTACT_EVENT_TYPE_CONTINUE,
CONTACT_EVENT_TYPE_END
};
class ContactInfo : public Collision
{
class ContactInfo {
public:
void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset);
void update(uint32_t currentStep, const btManifoldPoint& p);
ContactEventType computeType(uint32_t thisStep);
const btVector3& getPositionWorldOnB() const { return positionWorldOnB; }
btVector3 getPositionWorldOnA() const { return positionWorldOnB + normalWorldOnB * distance; }
btVector3 positionWorldOnB;
btVector3 normalWorldOnB;
btScalar distance;
private:
uint32_t _lastStep = 0;
uint32_t _numSteps = 0;

View file

@ -21,60 +21,87 @@
static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f;
static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
QSet<EntityItem*>* _outgoingEntityList;
// static
void EntityMotionState::setOutgoingEntityList(QSet<EntityItem*>* list) {
assert(list);
_outgoingEntityList = list;
}
// static
void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
assert(_outgoingEntityList);
_outgoingEntityList->insert(entity);
}
EntityMotionState::EntityMotionState(EntityItem* entity) :
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) :
ObjectMotionState(shape),
_entity(entity),
_sentMoving(false),
_numNonMovingUpdates(0),
_sentStep(0),
_sentPosition(0.0f),
_sentRotation(),
_sentVelocity(0.0f),
_sentAngularVelocity(0.0f),
_sentGravity(0.0f),
_sentAcceleration(0.0f),
_accelerationNearlyGravityCount(0),
_shouldClaimSimulationOwnership(false),
_movingStepsWithoutSimulationOwner(0)
{
_type = MOTION_STATE_TYPE_ENTITY;
assert(entity != NULL);
assert(entity != nullptr);
}
EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity->setPhysicsInfo(NULL);
_entity = NULL;
// be sure to clear _entity before calling the destructor
assert(!_entity);
}
void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) {
if (flags & EntityItem::DIRTY_POSITION) {
_sentPosition = _entity->getPosition();
}
if (flags & EntityItem::DIRTY_ROTATION) {
_sentRotation = _entity->getRotation();
}
if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) {
_sentVelocity = _entity->getVelocity();
}
if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) {
_sentAngularVelocity = _entity->getAngularVelocity();
}
}
// virtual
void EntityMotionState::handleEasyChanges(uint32_t flags) {
updateServerPhysicsVariables(flags);
ObjectMotionState::handleEasyChanges(flags);
}
// virtual
void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
updateServerPhysicsVariables(flags);
ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
void EntityMotionState::clearEntity() {
_entity = nullptr;
// set the type to INVALID so that external logic that pivots on the type won't try to access _entity
_type = MOTION_STATE_TYPE_INVALID;
}
MotionType EntityMotionState::computeObjectMotionType() const {
if (!_entity) {
return MOTION_TYPE_STATIC;
}
if (_entity->getCollisionsWillMove()) {
return MOTION_TYPE_DYNAMIC;
}
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
}
void EntityMotionState::updateKinematicState(uint32_t substep) {
setKinematic(_entity->isMoving(), substep);
}
void EntityMotionState::stepKinematicSimulation(quint64 now) {
assert(_isKinematic);
// NOTE: this is non-physical kinematic motion which steps to real run-time (now)
// which is different from physical kinematic motion (inside getWorldTransform())
// which steps in physics simulation time.
_entity->simulate(now);
// TODO: we can't use measureBodyAcceleration() here because the entity
// has no RigidBody and the timestep is a little bit out of sync with the physics simulation anyway.
// Hence we must manually measure kinematic velocity and acceleration.
}
bool EntityMotionState::isMoving() const {
return _entity->isMoving();
return _entity && _entity->isMoving();
}
bool EntityMotionState::isMovingVsServer() const {
auto alignmentDot = glm::abs(glm::dot(_sentRotation, _entity->getRotation()));
if (glm::distance(_sentPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA ||
alignmentDot < IGNORE_ALIGNMENT_DOT) {
return true;
}
return false;
}
// This callback is invoked by the physics simulation in two cases:
@ -83,52 +110,54 @@ bool EntityMotionState::isMoving() const {
// (2) at the beginning of each simulation step for KINEMATIC RigidBody's --
// it is an opportunity for outside code to update the object's simulation position
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
if (_isKinematic) {
if (!_entity) {
return;
}
if (_motionType == MOTION_TYPE_KINEMATIC) {
// This is physical kinematic motion which steps strictly by the subframe count
// of the physics simulation.
uint32_t substep = PhysicsEngine::getNumSubsteps();
float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_entity->simulateKinematicMotion(dt);
_entity->setLastSimulated(usecTimestampNow());
// bypass const-ness so we can remember the substep
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
// bypass const-ness so we can remember the step
const_cast<EntityMotionState*>(this)->_lastKinematicStep = thisStep;
}
worldTrans.setOrigin(glmToBullet(_entity->getPosition() - ObjectMotionState::getWorldOffset()));
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
}
// This callback is invoked by the physics simulation at the end of each simulation step...
// iff the corresponding RigidBody is DYNAMIC and has moved.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
if (!_entity) {
return;
}
measureBodyAcceleration();
_entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset());
_entity->setRotation(bulletToGLM(worldTrans.getRotation()));
glm::vec3 v;
getVelocity(v);
_entity->setVelocity(v);
glm::vec3 av;
getAngularVelocity(av);
_entity->setAngularVelocity(av);
_entity->setVelocity(getBodyLinearVelocity());
_entity->setAngularVelocity(getBodyAngularVelocity());
_entity->setLastSimulated(usecTimestampNow());
if (_entity->getSimulatorID().isNull() && isMoving()) {
// object is moving and has no owner. attempt to claim simulation ownership.
// if (_entity->getSimulatorID().isNull() && isMoving()) {
if (_entity->getSimulatorID().isNull() && isMovingVsServer()) {
// if object is moving and has no owner, attempt to claim simulation ownership.
_movingStepsWithoutSimulationOwner++;
} else {
_movingStepsWithoutSimulationOwner = 0;
}
if (_movingStepsWithoutSimulationOwner > 4) { // XXX maybe meters from our characterController ?
int ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController?
if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) {
qDebug() << "Warning -- claiming something I saw moving." << getName();
setShouldClaimSimulationOwnership(true);
}
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
EntityMotionState::enqueueOutgoingEntity(_entity);
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
@ -138,76 +167,125 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
#endif
}
void EntityMotionState::updateBodyEasy(uint32_t flags, uint32_t step) {
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_PHYSICS_NO_WAKE)) {
if (flags & EntityItem::DIRTY_POSITION) {
_sentPosition = getObjectPosition() - ObjectMotionState::getWorldOffset();
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_sentPosition));
_sentRotation = getObjectRotation();
worldTrans.setRotation(glmToBullet(_sentRotation));
_body->setWorldTransform(worldTrans);
}
if (flags & EntityItem::DIRTY_VELOCITY) {
updateBodyVelocities();
}
_sentStep = step;
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
_body->activate();
}
}
if (flags & EntityItem::DIRTY_MATERIAL) {
updateBodyMaterialProperties();
}
if (flags & EntityItem::DIRTY_MASS) {
ShapeInfo shapeInfo;
_entity->computeShapeInfo(shapeInfo);
float mass = computeObjectMass(shapeInfo);
btVector3 inertia(0.0f, 0.0f, 0.0f);
_body->getCollisionShape()->calculateLocalInertia(mass, inertia);
_body->setMassProps(mass, inertia);
_body->updateInertiaTensor();
}
}
void EntityMotionState::updateBodyMaterialProperties() {
_body->setRestitution(getObjectRestitution());
_body->setFriction(getObjectFriction());
_body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f)));
}
void EntityMotionState::updateBodyVelocities() {
if (_body) {
_sentVelocity = getObjectLinearVelocity();
setBodyVelocity(_sentVelocity);
_sentAngularVelocity = getObjectAngularVelocity();
setBodyAngularVelocity(_sentAngularVelocity);
_sentGravity = getObjectGravity();
setBodyGravity(_sentGravity);
_body->setActivationState(ACTIVE_TAG);
}
}
void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) {
if (_entity->isReadyToComputeShape()) {
if (_entity) {
_entity->computeShapeInfo(shapeInfo);
}
}
float EntityMotionState::computeObjectMass(const ShapeInfo& shapeInfo) const {
return _entity->computeMass();
// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates
// we alwasy resend packets for objects that have stopped moving up to some max limit.
const int MAX_NUM_NON_MOVING_UPDATES = 5;
bool EntityMotionState::doesNotNeedToSendUpdate() const {
return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES;
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
assert(_body);
// if we've never checked before, our _sentStep will be 0, and we need to initialize our state
if (_sentStep == 0) {
btTransform xform = _body->getWorldTransform();
_sentPosition = bulletToGLM(xform.getOrigin());
_sentRotation = bulletToGLM(xform.getRotation());
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_sentStep = simulationStep;
return false;
}
#ifdef WANT_DEBUG
glm::vec3 wasPosition = _sentPosition;
glm::quat wasRotation = _sentRotation;
glm::vec3 wasAngularVelocity = _sentAngularVelocity;
#endif
int numSteps = simulationStep - _sentStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_sentStep = simulationStep;
bool isActive = _body->isActive();
if (!isActive) {
if (_sentMoving) {
// this object just went inactive so send an update immediately
return true;
} else {
const float NON_MOVING_UPDATE_PERIOD = 1.0f;
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
// at a faster rate than the MAX period above, and only send a limited number of them.
return true;
}
}
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
// of remote EntitySimulation) and return true if the error is significant.
// NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
// due to _worldOffset.
// compute position error
if (glm::length2(_sentVelocity) > 0.0f) {
_sentVelocity += _sentAcceleration * dt;
_sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
_sentPosition += dt * _sentVelocity;
}
btTransform worldTrans = _body->getWorldTransform();
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
float dx2 = glm::distance2(position, _sentPosition);
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
#ifdef WANT_DEBUG
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
qCDebug(physics) << "wasPosition:" << wasPosition;
qCDebug(physics) << "bullet position:" << position;
qCDebug(physics) << "_sentPosition:" << _sentPosition;
qCDebug(physics) << "dx2:" << dx2;
#endif
return true;
}
if (glm::length2(_sentAngularVelocity) > 0.0f) {
// compute rotation error
float attenuation = powf(1.0f - _body->getAngularDamping(), dt);
_sentAngularVelocity *= attenuation;
// Bullet caps the effective rotation velocity inside its rotation integration step, therefore
// we must integrate with the same algorithm and timestep in order achieve similar results.
for (int i = 0; i < numSteps; ++i) {
_sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation);
}
}
const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
#ifdef WANT_DEBUG
if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) {
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ....";
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
qCDebug(physics) << "_sentAngularVelocity:" << _sentAngularVelocity;
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
qCDebug(physics) << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity);
qCDebug(physics) << "wasRotation:" << wasRotation;
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
qCDebug(physics) << "_sentRotation:" << _sentRotation;
}
#endif
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
}
bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) {
if (!ObjectMotionState::shouldSendUpdate(simulationFrame)) {
if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) {
return false;
}
@ -228,144 +306,138 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) {
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
if (!_entity->isKnownID()) {
if (!_entity || !_entity->isKnownID()) {
return; // never update entities that are unknown
}
if (_outgoingPacketFlags) {
EntityItemProperties properties = _entity->getProperties();
float gravityLength = glm::length(_entity->getGravity());
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) {
// acceleration measured during the most recent simulation step was close to gravity.
if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) {
// only increment this if we haven't reached the threshold yet. this is to avoid
// overflowing the counter.
incrementAccelerationNearlyGravityCount();
}
} else {
// acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter
resetAccelerationNearlyGravityCount();
EntityItemProperties properties = _entity->getProperties();
float gravityLength = glm::length(_entity->getGravity());
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) {
// acceleration measured during the most recent simulation step was close to gravity.
if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) {
// only increment this if we haven't reached the threshold yet. this is to avoid
// overflowing the counter.
incrementAccelerationNearlyGravityCount();
}
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let
// the entity server's estimates include gravity.
if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) {
_entity->setAcceleration(_entity->getGravity());
} else {
_entity->setAcceleration(glm::vec3(0.0f));
}
if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) {
btTransform worldTrans = _body->getWorldTransform();
_sentPosition = bulletToGLM(worldTrans.getOrigin());
properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset());
_sentRotation = bulletToGLM(worldTrans.getRotation());
properties.setRotation(_sentRotation);
}
bool zeroSpeed = true;
bool zeroSpin = true;
if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) {
if (_body->isActive()) {
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
// if the speeds are very small we zero them out
const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec
zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED);
if (zeroSpeed) {
_sentVelocity = glm::vec3(0.0f);
}
const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec
zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED;
if (zeroSpin) {
_sentAngularVelocity = glm::vec3(0.0f);
}
_sentMoving = ! (zeroSpeed && zeroSpin);
} else {
_sentVelocity = _sentAngularVelocity = glm::vec3(0.0f);
_sentMoving = false;
}
properties.setVelocity(_sentVelocity);
_sentGravity = _entity->getGravity();
properties.setGravity(_entity->getGravity());
_sentAcceleration = _entity->getAcceleration();
properties.setAcceleration(_sentAcceleration);
properties.setAngularVelocity(_sentAngularVelocity);
}
auto nodeList = DependencyManager::get<NodeList>();
QUuid myNodeID = nodeList->getSessionUUID();
QUuid simulatorID = _entity->getSimulatorID();
if (getShouldClaimSimulationOwnership()) {
properties.setSimulatorID(myNodeID);
setShouldClaimSimulationOwnership(false);
} else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) {
// we are the simulator and the entity has stopped. give up "simulator" status
_entity->setSimulatorID(QUuid());
properties.setSimulatorID(QUuid());
} else if (simulatorID == myNodeID && !_body->isActive()) {
// it's not active. don't keep simulation ownership.
_entity->setSimulatorID(QUuid());
properties.setSimulatorID(QUuid());
}
// RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit.
if (_sentMoving) {
_numNonMovingUpdates = 0;
} else {
_numNonMovingUpdates++;
}
if (_numNonMovingUpdates <= 1) {
// we only update lastEdited when we're sending new physics data
// (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data)
// NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly
quint64 lastSimulated = _entity->getLastSimulated();
_entity->setLastEdited(lastSimulated);
properties.setLastEdited(lastSimulated);
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qCDebug(physics) << "EntityMotionState::sendUpdate()";
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
<< "---------------------------------------------";
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
} else {
properties.setLastEdited(_entity->getLastEdited());
}
if (EntityItem::getSendPhysicsUpdates()) {
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
} else {
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested.";
#endif
}
// The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them
// to the full set. These flags may be momentarily cleared by incoming external changes.
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
_sentStep = step;
} else {
// acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter
resetAccelerationNearlyGravityCount();
}
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let
// the entity server's estimates include gravity.
if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) {
_entity->setAcceleration(_entity->getGravity());
} else {
_entity->setAcceleration(glm::vec3(0.0f));
}
btTransform worldTrans = _body->getWorldTransform();
_sentPosition = bulletToGLM(worldTrans.getOrigin());
properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset());
_sentRotation = bulletToGLM(worldTrans.getRotation());
properties.setRotation(_sentRotation);
bool zeroSpeed = true;
bool zeroSpin = true;
if (_body->isActive()) {
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
// if the speeds are very small we zero them out
const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec
zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED);
if (zeroSpeed) {
_sentVelocity = glm::vec3(0.0f);
}
const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec
zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED;
if (zeroSpin) {
_sentAngularVelocity = glm::vec3(0.0f);
}
_sentMoving = ! (zeroSpeed && zeroSpin);
} else {
_sentVelocity = _sentAngularVelocity = glm::vec3(0.0f);
_sentMoving = false;
}
properties.setVelocity(_sentVelocity);
_sentGravity = _entity->getGravity();
properties.setGravity(_entity->getGravity());
_sentAcceleration = _entity->getAcceleration();
properties.setAcceleration(_sentAcceleration);
properties.setAngularVelocity(_sentAngularVelocity);
auto nodeList = DependencyManager::get<NodeList>();
QUuid myNodeID = nodeList->getSessionUUID();
QUuid simulatorID = _entity->getSimulatorID();
if (getShouldClaimSimulationOwnership()) {
properties.setSimulatorID(myNodeID);
setShouldClaimSimulationOwnership(false);
} else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) {
// we are the simulator and the entity has stopped. give up "simulator" status
_entity->setSimulatorID(QUuid());
properties.setSimulatorID(QUuid());
} else if (simulatorID == myNodeID && !_body->isActive()) {
// it's not active. don't keep simulation ownership.
_entity->setSimulatorID(QUuid());
properties.setSimulatorID(QUuid());
}
// RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit.
if (_sentMoving) {
_numNonMovingUpdates = 0;
} else {
_numNonMovingUpdates++;
}
if (_numNonMovingUpdates <= 1) {
// we only update lastEdited when we're sending new physics data
// (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data)
// NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly
quint64 lastSimulated = _entity->getLastSimulated();
_entity->setLastEdited(lastSimulated);
properties.setLastEdited(lastSimulated);
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qCDebug(physics) << "EntityMotionState::sendUpdate()";
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
<< "---------------------------------------------";
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
} else {
properties.setLastEdited(_entity->getLastEdited());
}
if (EntityItem::getSendPhysicsUpdates()) {
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
_entity->setLastBroadcast(usecTimestampNow());
} else {
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested.";
#endif
}
_sentStep = step;
}
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
uint32_t dirtyFlags = _entity->getDirtyFlags();
if (_body) {
uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() const {
uint32_t dirtyFlags = 0;
if (_body && _entity) {
dirtyFlags = _entity->getDirtyFlags();
_entity->clearDirtyFlags();
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
int bodyFlags = _body->getCollisionFlags();
bool isMoving = _entity->isMoving();
@ -376,3 +448,60 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() const {
}
return dirtyFlags;
}
// virtual
QUuid EntityMotionState::getSimulatorID() const {
if (_entity) {
return _entity->getSimulatorID();
}
return QUuid();
}
// virtual
void EntityMotionState::bump() {
setShouldClaimSimulationOwnership(true);
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
_lastMeasureStep = ObjectMotionState::getWorldSimulationStep();
if (_body) {
_lastVelocity = bulletToGLM(_body->getLinearVelocity());
} else {
_lastVelocity = glm::vec3(0.0f);
}
_measuredAcceleration = glm::vec3(0.0f);
}
void EntityMotionState::measureBodyAcceleration() {
// try to manually measure the true acceleration of the object
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
uint32_t numSubsteps = thisStep - _lastMeasureStep;
if (numSubsteps > 0) {
float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP);
float invDt = 1.0f / dt;
_lastMeasureStep = thisStep;
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity());
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
_lastVelocity = velocity;
}
}
// virtual
void EntityMotionState::setMotionType(MotionType motionType) {
ObjectMotionState::setMotionType(motionType);
resetMeasuredBodyAcceleration();
}
// virtual
QString EntityMotionState::getName() {
if (_entity) {
return _entity->getName();
}
return "";
}

View file

@ -25,22 +25,18 @@ class EntityItem;
class EntityMotionState : public ObjectMotionState {
public:
// The OutgoingEntityQueue is a pointer to a QSet (owned by an EntitySimulation) of EntityItem*'s
// that have been changed by the physics simulation.
// All ObjectMotionState's with outgoing changes put themselves on the list.
static void setOutgoingEntityList(QSet<EntityItem*>* list);
static void enqueueOutgoingEntity(EntityItem* entity);
EntityMotionState(EntityItem* item);
EntityMotionState(btCollisionShape* shape, EntityItem* item);
virtual ~EntityMotionState();
void updateServerPhysicsVariables(uint32_t flags);
virtual void handleEasyChanges(uint32_t flags);
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
virtual MotionType computeObjectMotionType() const;
virtual void updateKinematicState(uint32_t substep);
virtual void stepKinematicSimulation(quint64 now);
virtual bool isMoving() const;
virtual bool isMovingVsServer() const;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const;
@ -48,41 +44,70 @@ public:
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans);
// these relay incoming values to the RigidBody
virtual void updateBodyEasy(uint32_t flags, uint32_t step);
virtual void updateBodyMaterialProperties();
virtual void updateBodyVelocities();
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo);
virtual float computeObjectMass(const ShapeInfo& shapeInfo) const;
virtual bool shouldSendUpdate(uint32_t simulationFrame);
virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
bool doesNotNeedToSendUpdate() const;
bool remoteSimulationOutOfSync(uint32_t simulationStep);
bool shouldSendUpdate(uint32_t simulationFrame);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
virtual uint32_t getIncomingDirtyFlags() const;
virtual void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
virtual uint32_t getAndClearIncomingDirtyFlags() const;
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; }
virtual EntityItem* getEntity() const { return _entity; }
virtual void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
virtual bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
virtual float getObjectRestitution() const { return _entity->getRestitution(); }
virtual float getObjectFriction() const { return _entity->getFriction(); }
virtual float getObjectLinearDamping() const { return _entity->getDamping(); }
virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); }
virtual const glm::vec3& getObjectPosition() const { return _entity->getPosition(); }
virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); }
virtual const glm::quat& getObjectRotation() const { return _entity->getRotation(); }
virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); }
virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); }
virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); }
virtual const QUuid& getObjectID() const { return _entity->getID(); }
virtual QUuid getSimulatorID() const;
virtual void bump();
EntityItem* getEntity() const { return _entity; }
void resetMeasuredBodyAcceleration();
void measureBodyAcceleration();
virtual QString getName();
friend class PhysicalEntitySimulation;
protected:
void clearEntity();
virtual void setMotionType(MotionType motionType);
EntityItem* _entity;
bool _sentMoving; // true if last update was moving
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
// TODO XXX rename _sent* to _server*
uint32_t _sentStep;
glm::vec3 _sentPosition; // in simulation-frame (not world-frame)
glm::quat _sentRotation;;
glm::vec3 _sentVelocity;
glm::vec3 _sentAngularVelocity; // radians per second
glm::vec3 _sentGravity;
glm::vec3 _sentAcceleration;
uint32_t _lastMeasureStep;
glm::vec3 _lastVelocity;
glm::vec3 _measuredAcceleration;
quint8 _accelerationNearlyGravityCount;
bool _shouldClaimSimulationOwnership;
quint32 _movingStepsWithoutSimulationOwner;

View file

@ -31,57 +31,43 @@ const glm::vec3& ObjectMotionState::getWorldOffset() {
}
// static
uint32_t _worldSimulationStep = 0;
uint32_t worldSimulationStep = 0;
void ObjectMotionState::setWorldSimulationStep(uint32_t step) {
assert(step > _worldSimulationStep);
_worldSimulationStep = step;
assert(step > worldSimulationStep);
worldSimulationStep = step;
}
ObjectMotionState::ObjectMotionState() :
uint32_t ObjectMotionState::getWorldSimulationStep() {
return worldSimulationStep;
}
// static
ShapeManager* shapeManager = nullptr;
void ObjectMotionState::setShapeManager(ShapeManager* manager) {
assert(manager);
shapeManager = manager;
}
ShapeManager* ObjectMotionState::getShapeManager() {
assert(shapeManager); // you must properly set shapeManager before calling getShapeManager()
return shapeManager;
}
ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
_motionType(MOTION_TYPE_STATIC),
_body(NULL),
_sentMoving(false),
_numNonMovingUpdates(0),
_outgoingPacketFlags(DIRTY_PHYSICS_FLAGS),
_sentStep(0),
_sentPosition(0.0f),
_sentRotation(),
_sentVelocity(0.0f),
_sentAngularVelocity(0.0f),
_sentGravity(0.0f),
_sentAcceleration(0.0f),
_lastSimulationStep(0),
_lastVelocity(0.0f),
_measuredAcceleration(0.0f) {
_shape(shape),
_body(nullptr),
_mass(0.0f),
_lastKinematicStep(worldSimulationStep)
{
}
ObjectMotionState::~ObjectMotionState() {
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
assert(_body == NULL);
assert(!_body);
assert(!_shape);
}
void ObjectMotionState::measureBodyAcceleration() {
// try to manually measure the true acceleration of the object
uint32_t numSubsteps = _worldSimulationStep - _lastSimulationStep;
if (numSubsteps > 0) {
float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP);
float invDt = 1.0f / dt;
_lastSimulationStep = _worldSimulationStep;
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity());
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
_lastVelocity = velocity;
}
}
void ObjectMotionState::resetMeasuredBodyAcceleration() {
_lastSimulationStep = _worldSimulationStep;
_lastVelocity = bulletToGLM(_body->getLinearVelocity());
}
void ObjectMotionState::setBodyVelocity(const glm::vec3& velocity) const {
void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const {
_body->setLinearVelocity(glmToBullet(velocity));
}
@ -93,130 +79,30 @@ void ObjectMotionState::setBodyGravity(const glm::vec3& gravity) const {
_body->setGravity(glmToBullet(gravity));
}
void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const {
velocityOut = bulletToGLM(_body->getLinearVelocity());
glm::vec3 ObjectMotionState::getBodyLinearVelocity() const {
return bulletToGLM(_body->getLinearVelocity());
}
void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const {
angularVelocityOut = bulletToGLM(_body->getAngularVelocity());
glm::vec3 ObjectMotionState::getBodyAngularVelocity() const {
return bulletToGLM(_body->getAngularVelocity());
}
// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates
// we alwasy resend packets for objects that have stopped moving up to some max limit.
const int MAX_NUM_NON_MOVING_UPDATES = 5;
bool ObjectMotionState::doesNotNeedToSendUpdate() const {
return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES;
void ObjectMotionState::releaseShape() {
if (_shape) {
shapeManager->releaseShape(_shape);
_shape = nullptr;
}
}
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationStep) {
assert(_body);
// if we've never checked before, our _sentStep will be 0, and we need to initialize our state
if (_sentStep == 0) {
btTransform xform = _body->getWorldTransform();
_sentPosition = bulletToGLM(xform.getOrigin());
_sentRotation = bulletToGLM(xform.getRotation());
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_sentStep = simulationStep;
return false;
}
#ifdef WANT_DEBUG
glm::vec3 wasPosition = _sentPosition;
glm::quat wasRotation = _sentRotation;
glm::vec3 wasAngularVelocity = _sentAngularVelocity;
#endif
int numSteps = simulationStep - _sentStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_sentStep = simulationStep;
bool isActive = _body->isActive();
if (!isActive) {
if (_sentMoving) {
// this object just went inactive so send an update immediately
return true;
} else {
const float NON_MOVING_UPDATE_PERIOD = 1.0f;
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
// at a faster rate than the MAX period above, and only send a limited number of them.
return true;
}
}
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
// of remote EntitySimulation) and return true if the error is significant.
// NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
// due to _worldOffset.
// compute position error
if (glm::length2(_sentVelocity) > 0.0f) {
_sentVelocity += _sentAcceleration * dt;
_sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
_sentPosition += dt * _sentVelocity;
}
btTransform worldTrans = _body->getWorldTransform();
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
float dx2 = glm::distance2(position, _sentPosition);
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
#ifdef WANT_DEBUG
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
qCDebug(physics) << "wasPosition:" << wasPosition;
qCDebug(physics) << "bullet position:" << position;
qCDebug(physics) << "_sentPosition:" << _sentPosition;
qCDebug(physics) << "dx2:" << dx2;
#endif
return true;
}
if (glm::length2(_sentAngularVelocity) > 0.0f) {
// compute rotation error
float attenuation = powf(1.0f - _body->getAngularDamping(), dt);
_sentAngularVelocity *= attenuation;
// Bullet caps the effective rotation velocity inside its rotation integration step, therefore
// we must integrate with the same algorithm and timestep in order achieve similar results.
for (int i = 0; i < numSteps; ++i) {
_sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation);
}
}
const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
#ifdef WANT_DEBUG
if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) {
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ....";
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
qCDebug(physics) << "_sentAngularVelocity:" << _sentAngularVelocity;
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
qCDebug(physics) << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity);
qCDebug(physics) << "wasRotation:" << wasRotation;
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
qCDebug(physics) << "_sentRotation:" << _sentRotation;
}
#endif
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
void ObjectMotionState::setMotionType(MotionType motionType) {
_motionType = motionType;
}
void ObjectMotionState::setRigidBody(btRigidBody* body) {
// give the body a (void*) back-pointer to this ObjectMotionState
if (_body != body) {
if (_body) {
_body->setUserPointer(NULL);
_body->setUserPointer(nullptr);
}
_body = body;
if (_body) {
@ -225,7 +111,89 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
}
}
void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) {
_isKinematic = kinematic;
_lastKinematicSubstep = substep;
void ObjectMotionState::handleEasyChanges(uint32_t flags) {
if (flags & EntityItem::DIRTY_POSITION) {
btTransform worldTrans;
if (flags & EntityItem::DIRTY_ROTATION) {
worldTrans.setRotation(glmToBullet(getObjectRotation()));
} else {
worldTrans = _body->getWorldTransform();
}
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
_body->setWorldTransform(worldTrans);
} else if (flags & EntityItem::DIRTY_ROTATION) {
btTransform worldTrans = _body->getWorldTransform();
worldTrans.setRotation(glmToBullet(getObjectRotation()));
_body->setWorldTransform(worldTrans);
}
if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) {
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
_body->setGravity(glmToBullet(getObjectGravity()));
}
if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) {
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
}
if (flags & EntityItem::DIRTY_MATERIAL) {
updateBodyMaterialProperties();
}
if (flags & EntityItem::DIRTY_MASS) {
float mass = getMass();
btVector3 inertia(0.0f, 0.0f, 0.0f);
_body->getCollisionShape()->calculateLocalInertia(mass, inertia);
_body->setMassProps(mass, inertia);
_body->updateInertiaTensor();
}
if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) {
_body->activate();
}
}
void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
if (flags & EntityItem::DIRTY_SHAPE) {
// make sure the new shape is valid
ShapeInfo shapeInfo;
computeObjectShapeInfo(shapeInfo);
btCollisionShape* newShape = getShapeManager()->getShape(shapeInfo);
if (!newShape) {
qCDebug(physics) << "Warning: failed to generate new shape!";
// failed to generate new shape! --> keep old shape and remove shape-change flag
flags &= ~EntityItem::DIRTY_SHAPE;
// TODO: force this object out of PhysicsEngine rather than just use the old shape
if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) {
// no HARD flags remain, so do any EASY changes
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
handleEasyChanges(flags);
}
return;
}
}
getShapeManager()->releaseShape(_shape);
_shape = newShape;
_body->setCollisionShape(_shape);
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
handleEasyChanges(flags);
}
} else {
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
handleEasyChanges(flags);
}
}
engine->reinsertObject(this);
}
void ObjectMotionState::updateBodyMaterialProperties() {
_body->setRestitution(getObjectRestitution());
_body->setFriction(getObjectFriction());
_body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f)));
}
void ObjectMotionState::updateBodyVelocities() {
setBodyLinearVelocity(getObjectLinearVelocity());
setBodyAngularVelocity(getObjectAngularVelocity());
setBodyGravity(getObjectGravity());
_body->setActivationState(ACTIVE_TAG);
}

View file

@ -18,7 +18,7 @@
#include <EntityItem.h>
#include "ContactInfo.h"
#include "ShapeInfo.h"
#include "ShapeManager.h"
enum MotionType {
MOTION_TYPE_STATIC, // no motion
@ -27,7 +27,7 @@ enum MotionType {
};
enum MotionStateType {
MOTION_STATE_TYPE_UNKNOWN,
MOTION_STATE_TYPE_INVALID,
MOTION_STATE_TYPE_ENTITY,
MOTION_STATE_TYPE_AVATAR
};
@ -35,86 +35,71 @@ enum MotionStateType {
// The update flags trigger two varieties of updates: "hard" which require the body to be pulled
// and re-added to the physics engine and "easy" which just updates the body properties.
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY |
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MATERIAL);
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES |
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP |
EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION);
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS;
// These are the outgoing flags that the PhysicsEngine can affect:
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY;
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES;
class OctreeEditPacketSender;
class PhysicsEngine;
extern const int MAX_NUM_NON_MOVING_UPDATES;
class ObjectMotionState : public btMotionState {
public:
// The WorldOffset is used to keep the positions of objects in the simulation near the origin, to
// reduce numerical error when computing vector differences. In other words: The EntityMotionState
// class translates between the simulation-frame and the world-frame as known by the render pipeline,
// various object trees, etc. The EntityMotionState class uses a static "worldOffset" to help in
// the translations.
// These poroperties of the PhysicsEngine are "global" within the context of all ObjectMotionStates
// (assuming just one PhysicsEngine). They are cached as statics for fast calculations in the
// ObjectMotionState context.
static void setWorldOffset(const glm::vec3& offset);
static const glm::vec3& getWorldOffset();
// The WorldSimulationStep is a cached copy of number of SubSteps of the simulation, used for local time measurements.
static void setWorldSimulationStep(uint32_t step);
static uint32_t getWorldSimulationStep();
ObjectMotionState();
static void setShapeManager(ShapeManager* manager);
static ShapeManager* getShapeManager();
ObjectMotionState(btCollisionShape* shape);
~ObjectMotionState();
void measureBodyAcceleration();
void resetMeasuredBodyAcceleration();
virtual void handleEasyChanges(uint32_t flags);
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
// An EASY update does not require the object to be removed and then reinserted into the PhysicsEngine
virtual void updateBodyEasy(uint32_t flags, uint32_t frame) = 0;
virtual void updateBodyMaterialProperties() = 0;
virtual void updateBodyVelocities() = 0;
virtual void updateBodyMaterialProperties();
virtual void updateBodyVelocities();
MotionStateType getType() const { return _type; }
virtual MotionType getMotionType() const { return _motionType; }
virtual void computeObjectShapeInfo(ShapeInfo& info) = 0;
virtual float computeObjectMass(const ShapeInfo& shapeInfo) const = 0;
void setMass(float mass) { _mass = fabsf(mass); }
float getMass() { return _mass; }
void setBodyVelocity(const glm::vec3& velocity) const;
void setBodyLinearVelocity(const glm::vec3& velocity) const;
void setBodyAngularVelocity(const glm::vec3& velocity) const;
void setBodyGravity(const glm::vec3& gravity) const;
void getVelocity(glm::vec3& velocityOut) const;
void getAngularVelocity(glm::vec3& angularVelocityOut) const;
glm::vec3 getBodyLinearVelocity() const;
glm::vec3 getBodyAngularVelocity() const;
virtual uint32_t getIncomingDirtyFlags() const = 0;
virtual void clearIncomingDirtyFlags(uint32_t flags) = 0;
void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; }
bool doesNotNeedToSendUpdate() const;
virtual bool shouldSendUpdate(uint32_t simulationStep);
virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0;
virtual uint32_t getAndClearIncomingDirtyFlags() const = 0;
virtual MotionType computeObjectMotionType() const = 0;
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo) = 0;
virtual void updateKinematicState(uint32_t substep) = 0;
btCollisionShape* getShape() const { return _shape; }
btRigidBody* getRigidBody() const { return _body; }
bool isKinematic() const { return _isKinematic; }
void setKinematic(bool kinematic, uint32_t substep);
virtual void stepKinematicSimulation(quint64 now) = 0;
void releaseShape();
virtual bool isMoving() const = 0;
friend class PhysicsEngine;
// these are here so we can call into EntityMotionObject with a base-class pointer
virtual EntityItem* getEntity() const { return NULL; }
virtual void setShouldClaimSimulationOwnership(bool value) { }
virtual bool getShouldClaimSimulationOwnership() { return false; }
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
@ -123,39 +108,33 @@ public:
virtual float getObjectLinearDamping() const = 0;
virtual float getObjectAngularDamping() const = 0;
virtual const glm::vec3& getObjectPosition() const = 0;
virtual glm::vec3 getObjectPosition() const = 0;
virtual const glm::quat& getObjectRotation() const = 0;
virtual const glm::vec3& getObjectLinearVelocity() const = 0;
virtual const glm::vec3& getObjectAngularVelocity() const = 0;
virtual const glm::vec3& getObjectGravity() const = 0;
virtual const QUuid& getObjectID() const = 0;
virtual QUuid getSimulatorID() const = 0;
virtual void bump() = 0;
virtual QString getName() { return ""; }
friend class PhysicsEngine;
protected:
virtual void setMotionType(MotionType motionType);
void setRigidBody(btRigidBody* body);
MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN; // type of MotionState
MotionStateType _type = MOTION_STATE_TYPE_INVALID; // type of MotionState
MotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
btCollisionShape* _shape;
btRigidBody* _body;
float _mass;
bool _isKinematic = false;
uint32_t _lastKinematicSubstep = 0;
bool _sentMoving; // true if last update was moving
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
uint32_t _outgoingPacketFlags;
uint32_t _sentStep;
glm::vec3 _sentPosition; // in simulation-frame (not world-frame)
glm::quat _sentRotation;;
glm::vec3 _sentVelocity;
glm::vec3 _sentAngularVelocity; // radians per second
glm::vec3 _sentGravity;
glm::vec3 _sentAcceleration;
uint32_t _lastSimulationStep;
glm::vec3 _lastVelocity;
glm::vec3 _measuredAcceleration;
uint32_t _lastKinematicStep;
};
#endif // hifi_ObjectMotionState_h

View file

@ -0,0 +1,238 @@
//
// PhysicalEntitySimulation.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.04.27
// Copyright 2015 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 "EntityMotionState.h"
#include "PhysicalEntitySimulation.h"
#include "PhysicsHelpers.h"
#include "PhysicsLogging.h"
#include "ShapeInfoUtil.h"
#include "ShapeManager.h"
PhysicalEntitySimulation::PhysicalEntitySimulation() {
}
PhysicalEntitySimulation::~PhysicalEntitySimulation() {
}
void PhysicalEntitySimulation::init(
EntityTree* tree,
PhysicsEngine* physicsEngine,
ShapeManager* shapeManager,
EntityEditPacketSender* packetSender) {
assert(tree);
setEntityTree(tree);
assert(physicsEngine);
_physicsEngine = physicsEngine;
assert(shapeManager);
_shapeManager = shapeManager;
assert(packetSender);
_entityPacketSender = packetSender;
}
// begin EntitySimulation overrides
void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
// TODO: add back non-physical kinematic objects and step them forward here
}
void PhysicalEntitySimulation::addEntityInternal(EntityItem* entity) {
assert(entity);
if (entity->shouldBePhysical()) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (!motionState) {
_pendingAdds.insert(entity);
}
} else if (entity->isMoving()) {
_simpleKinematicEntities.insert(entity);
}
}
void PhysicalEntitySimulation::removeEntityInternal(EntityItem* entity) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
motionState->clearEntity();
entity->setPhysicsInfo(nullptr);
// NOTE: we must remove entity from _pendingAdds immediately because we've disconnected the backpointers between
// motionState and entity and they can't be used to look up each other. However we don't need to remove
// motionState from _pendingChanges at this time becuase it will be removed during getObjectsToDelete().
_pendingAdds.remove(entity);
_pendingRemoves.insert(motionState);
_outgoingChanges.remove(motionState);
}
}
void PhysicalEntitySimulation::changeEntityInternal(EntityItem* entity) {
// queue incoming changes: from external sources (script, EntityServer, etc) to physics engine
assert(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
if (!entity->shouldBePhysical()) {
// the entity should be removed from the physical simulation
_pendingChanges.remove(motionState);
_physicalObjects.remove(motionState);
_pendingRemoves.insert(motionState);
_outgoingChanges.remove(motionState);
if (entity->isMoving()) {
_simpleKinematicEntities.insert(entity);
}
} else {
_pendingChanges.insert(motionState);
}
} else if (entity->shouldBePhysical()) {
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
// Perhaps it's shape has changed and it can now be added?
_pendingAdds.insert(entity);
_simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic
} else if (entity->isMoving()) {
_simpleKinematicEntities.insert(entity);
} else {
_simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic
}
}
void PhysicalEntitySimulation::clearEntitiesInternal() {
// TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures
// while it is in the middle of a simulation step. As it is, we're probably in shutdown mode
// anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
// first disconnect each MotionStates from its Entity
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
EntityItem* entity = motionState->getEntity();
if (entity) {
entity->setPhysicsInfo(nullptr);
}
motionState->clearEntity();
}
// then delete the objects (aka MotionStates)
_physicsEngine->deleteObjects(_physicalObjects);
// finally clear all lists (which now have only dangling pointers)
_physicalObjects.clear();
_pendingRemoves.clear();
_pendingAdds.clear();
_pendingChanges.clear();
}
// end EntitySimulation overrides
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToDelete() {
_tempVector.clear();
for (auto stateItr : _pendingRemoves) {
EntityMotionState* motionState = &(*stateItr);
_pendingChanges.remove(motionState);
_physicalObjects.remove(motionState);
EntityItem* entity = motionState->getEntity();
if (entity) {
_pendingAdds.remove(entity);
entity->setPhysicsInfo(nullptr);
motionState->clearEntity();
}
_tempVector.push_back(motionState);
}
_pendingRemoves.clear();
return _tempVector;
}
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() {
_tempVector.clear();
SetOfEntities::iterator entityItr = _pendingAdds.begin();
while (entityItr != _pendingAdds.end()) {
EntityItem* entity = *entityItr;
assert(!entity->getPhysicsInfo());
if (!entity->shouldBePhysical()) {
// this entity should no longer be on the internal _pendingAdds
entityItr = _pendingAdds.erase(entityItr);
if (entity->isMoving()) {
_simpleKinematicEntities.insert(entity);
}
} else if (entity->isReadyToComputeShape()) {
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager->getShape(shapeInfo);
if (shape) {
EntityMotionState* motionState = new EntityMotionState(shape, entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
motionState->setMass(entity->computeMass());
_physicalObjects.insert(motionState);
_tempVector.push_back(motionState);
entityItr = _pendingAdds.erase(entityItr);
} else {
qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
++entityItr;
}
} else {
++entityItr;
}
}
return _tempVector;
}
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
_tempVector.clear();
for (auto stateItr : _pendingChanges) {
EntityMotionState* motionState = &(*stateItr);
_tempVector.push_back(motionState);
}
_pendingChanges.clear();
return _tempVector;
}
void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) {
// walk the motionStates looking for those that correspond to entities
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
if (state && state->getType() == MOTION_STATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
EntityItem* entity = entityState->getEntity();
if (entity) {
_outgoingChanges.insert(entityState);
_entitiesToSort.insert(entityState->getEntity());
}
}
}
// send outgoing packets
uint32_t numSubsteps = _physicsEngine->getNumSubsteps();
if (_lastStepSendPackets != numSubsteps) {
_lastStepSendPackets = numSubsteps;
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr;
if (state->doesNotNeedToSendUpdate()) {
stateItr = _outgoingChanges.erase(stateItr);
} else if (state->shouldSendUpdate(numSubsteps)) {
state->sendUpdate(_entityPacketSender, numSubsteps);
++stateItr;
} else {
++stateItr;
}
}
}
}
void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionEvents) {
for (auto collision : collisionEvents) {
// NOTE: The collision event is always aligned such that idA is never NULL.
// however idB may be NULL.
if (!collision.idB.isNull()) {
emit entityCollisionWithEntity(collision.idA, collision.idB, collision);
}
}
}

View file

@ -0,0 +1,73 @@
//
// PhysicalEntitySimulation.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.04.27
// Copyright 2015 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_PhysicalEntitySimulation_h
#define hifi_PhysicalEntitySimulation_h
#include <stdint.h>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
#include "PhysicsEngine.h"
#include "PhysicsTypedefs.h"
class EntityMotionState;
class ShapeManager;
typedef QSet<EntityMotionState*> SetOfEntityMotionStates;
class PhysicalEntitySimulation :public EntitySimulation {
public:
PhysicalEntitySimulation();
~PhysicalEntitySimulation();
void init(EntityTree* tree, PhysicsEngine* engine, ShapeManager* shapeManager, EntityEditPacketSender* packetSender);
protected: // only called by EntitySimulation
// overrides for EntitySimulation
virtual void updateEntitiesInternal(const quint64& now);
virtual void addEntityInternal(EntityItem* entity);
virtual void removeEntityInternal(EntityItem* entity);
virtual void changeEntityInternal(EntityItem* entity);
virtual void clearEntitiesInternal();
public:
VectorOfMotionStates& getObjectsToDelete();
VectorOfMotionStates& getObjectsToAdd();
VectorOfMotionStates& getObjectsToChange();
void handleOutgoingChanges(VectorOfMotionStates& motionStates);
void handleCollisionEvents(CollisionEvents& collisionEvents);
private:
// incoming changes
SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted)
SetOfEntities _pendingAdds; // entities to be be added to PhysicsEngine (and a their EntityMotionState created)
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
// outgoing changes
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
VectorOfMotionStates _tempVector; // temporary array reference, valid immediately after getObjectsToRemove() (and friends)
ShapeManager* _shapeManager = nullptr;
PhysicsEngine* _physicsEngine = nullptr;
EntityEditPacketSender* _entityPacketSender = nullptr;
uint32_t _lastStepSendPackets = 0;
};
#endif // hifi_PhysicalEntitySimulation_h

View file

@ -9,10 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AABox.h>
#include "ObjectMotionState.h"
#include "PhysicsEngine.h"
#include "ShapeInfoUtil.h"
#include "PhysicsHelpers.h"
#include "ThreadSafeDynamicsWorld.h"
#include "PhysicsLogging.h"
@ -26,12 +24,12 @@ uint32_t PhysicsEngine::getNumSubsteps() {
PhysicsEngine::PhysicsEngine(const glm::vec3& offset) :
_originOffset(offset),
_characterController(NULL) {
_characterController(nullptr) {
}
PhysicsEngine::~PhysicsEngine() {
if (_characterController) {
_characterController->setDynamicsWorld(NULL);
_characterController->setDynamicsWorld(nullptr);
}
// TODO: delete engine components... if we ever plan to create more than one instance
delete _collisionConfig;
@ -42,217 +40,153 @@ PhysicsEngine::~PhysicsEngine() {
delete _ghostPairCallback;
}
// begin EntitySimulation overrides
void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
// no need to send updates unless the physics simulation has actually stepped
if (_lastNumSubstepsAtUpdateInternal != _numSubsteps) {
_lastNumSubstepsAtUpdateInternal = _numSubsteps;
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// this is step (4)
QSet<ObjectMotionState*>::iterator stateItr = _outgoingPackets.begin();
while (stateItr != _outgoingPackets.end()) {
ObjectMotionState* state = *stateItr;
if (state->doesNotNeedToSendUpdate()) {
stateItr = _outgoingPackets.erase(stateItr);
} else if (state->shouldSendUpdate(_numSubsteps)) {
state->sendUpdate(_entityPacketSender, _numSubsteps);
++stateItr;
void PhysicsEngine::init() {
if (!_dynamicsWorld) {
_collisionConfig = new btDefaultCollisionConfiguration();
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
_broadphaseFilter = new btDbvtBroadphase();
_constraintSolver = new btSequentialImpulseConstraintSolver;
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
_ghostPairCallback = new btGhostPairCallback();
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
// default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
}
}
void PhysicsEngine::addObject(ObjectMotionState* motionState) {
assert(motionState);
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = 0.0f;
// NOTE: the body may or may not already exist, depending on whether this corresponds to a reinsertion, or a new insertion.
btRigidBody* body = motionState->getRigidBody();
MotionType motionType = motionState->computeObjectMotionType();
motionState->setMotionType(motionType);
switch(motionType) {
case MOTION_TYPE_KINEMATIC: {
if (!body) {
btCollisionShape* shape = motionState->getShape();
assert(shape);
body = new btRigidBody(mass, motionState, shape, inertia);
} else {
++stateItr;
body->setMassProps(mass, inertia);
}
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->updateBodyVelocities();
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->getMass();
btCollisionShape* shape = motionState->getShape();
assert(shape);
shape->calculateLocalInertia(mass, inertia);
if (!body) {
body = new btRigidBody(mass, motionState, shape, inertia);
} else {
body->setMassProps(mass, inertia);
}
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->updateBodyVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
if (!motionState->isMoving()) {
// try to initialize this object as inactive
body->forceActivationState(ISLAND_SLEEPING);
}
break;
}
case MOTION_TYPE_STATIC:
default: {
if (!body) {
assert(motionState->getShape());
body = new btRigidBody(mass, motionState, motionState->getShape(), inertia);
} else {
body->setMassProps(mass, inertia);
}
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
break;
}
}
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
motionState->updateBodyMaterialProperties();
_dynamicsWorld->addRigidBody(body);
motionState->getAndClearIncomingDirtyFlags();
}
void PhysicsEngine::removeObject(ObjectMotionState* object) {
// wake up anything touching this object
bump(object);
removeContacts(object);
btRigidBody* body = object->getRigidBody();
assert(body);
_dynamicsWorld->removeRigidBody(body);
}
void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) {
for (auto object : objects) {
removeObject(object);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
btRigidBody* body = object->getRigidBody();
object->setRigidBody(nullptr);
delete body;
object->releaseShape();
delete object;
}
}
// Same as above, but takes a Set instead of a Vector and ommits some cleanup operations. Only called during teardown.
void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) {
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
_dynamicsWorld->removeRigidBody(body);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
object->setRigidBody(nullptr);
delete body;
object->releaseShape();
delete object;
}
}
void PhysicsEngine::addObjects(VectorOfMotionStates& objects) {
for (auto object : objects) {
addObject(object);
}
}
void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
for (auto object : objects) {
uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
object->handleHardAndEasyChanges(flags, this);
} else if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
object->handleEasyChanges(flags);
}
}
}
void PhysicsEngine::addEntityInternal(EntityItem* entity) {
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (!physicsInfo) {
if (entity->isReadyToComputeShape()) {
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
EntityMotionState* motionState = new EntityMotionState(entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
addObject(shapeInfo, shape, motionState);
} else if (entity->isMoving()) {
EntityMotionState* motionState = new EntityMotionState(entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
motionState->setKinematic(true, _numSubsteps);
_nonPhysicalKinematicObjects.insert(motionState);
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
//qCDebug(physics) << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
}
}
}
}
void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (physicsInfo) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
if (motionState->getRigidBody()) {
removeObjectFromBullet(motionState);
} else {
// only need to hunt in this list when there is no RigidBody
_nonPhysicalKinematicObjects.remove(motionState);
}
_entityMotionStates.remove(motionState);
_incomingChanges.remove(motionState);
_outgoingPackets.remove(motionState);
// NOTE: EntityMotionState dtor will remove its backpointer from EntityItem
delete motionState;
}
}
void PhysicsEngine::entityChangedInternal(EntityItem* entity) {
// queue incoming changes: from external sources (script, EntityServer, etc) to physics engine
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (physicsInfo) {
if ((entity->getDirtyFlags() & (HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS)) > 0) {
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
_incomingChanges.insert(motionState);
}
} else {
// try to add this entity again (maybe something changed such that it will work this time)
addEntity(entity);
}
}
void PhysicsEngine::sortEntitiesThatMovedInternal() {
// entities that have been simulated forward (hence in the _entitiesToBeSorted list)
// also need to be put in the outgoingPackets list
QSet<EntityItem*>::iterator entityItr = _entitiesToBeSorted.begin();
while (entityItr != _entitiesToBeSorted.end()) {
EntityItem* entity = *entityItr;
void* physicsInfo = entity->getPhysicsInfo();
assert(physicsInfo);
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
_outgoingPackets.insert(motionState);
++entityItr;
}
}
void PhysicsEngine::clearEntitiesInternal() {
// For now we assume this would only be called on shutdown in which case we can just let the memory get lost.
QSet<EntityMotionState*>::const_iterator stateItr = _entityMotionStates.begin();
for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) {
removeObjectFromBullet(*stateItr);
delete (*stateItr);
}
_entityMotionStates.clear();
_nonPhysicalKinematicObjects.clear();
_incomingChanges.clear();
_outgoingPackets.clear();
}
// end EntitySimulation overrides
void PhysicsEngine::relayIncomingChangesToSimulation() {
BT_PROFILE("incomingChanges");
// process incoming changes
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
while (stateItr != _incomingChanges.end()) {
ObjectMotionState* motionState = *stateItr;
++stateItr;
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
bool removeMotionState = false;
btRigidBody* body = motionState->getRigidBody();
if (body) {
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
// but it also handles all EASY changes
bool success = updateBodyHard(body, motionState, flags);
if (!success) {
// NOTE: since updateBodyHard() failed we know that motionState has been removed
// from simulation and body has been deleted. Depending on what else has changed
// we might need to remove motionState altogether...
if (flags & EntityItem::DIRTY_VELOCITY) {
motionState->updateKinematicState(_numSubsteps);
if (motionState->isKinematic()) {
// all is NOT lost, we still need to move this object around kinematically
_nonPhysicalKinematicObjects.insert(motionState);
} else {
// no need to keep motionState around
removeMotionState = true;
}
} else {
// no need to keep motionState around
removeMotionState = true;
}
}
} else if (flags) {
// an EASY update does NOT require that the body be pulled out of physics engine
// hence the MotionState has all the knowledge and authority to perform the update.
motionState->updateBodyEasy(flags, _numSubsteps);
}
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
motionState->resetMeasuredBodyAcceleration();
}
} else {
// the only way we should ever get here (motionState exists but no body) is when the object
// is undergoing non-physical kinematic motion.
assert(_nonPhysicalKinematicObjects.contains(motionState));
// it is possible that the changes are such that the object can now be added to the physical simulation
if (flags & EntityItem::DIRTY_SHAPE) {
ShapeInfo shapeInfo;
motionState->computeObjectShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
addObject(shapeInfo, shape, motionState);
_nonPhysicalKinematicObjects.remove(motionState);
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we couldn't add the object to the simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
} else if (flags & EntityItem::DIRTY_VELOCITY) {
// although we still can't add to physics simulation, might need to update kinematic motion...
motionState->updateKinematicState(_numSubsteps);
if (!motionState->isKinematic()) {
_nonPhysicalKinematicObjects.remove(motionState);
removeMotionState = true;
}
}
}
if (removeMotionState) {
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
_outgoingPackets.remove(motionState);
if (motionState->getType() == MOTION_STATE_TYPE_ENTITY) {
_entityMotionStates.remove(static_cast<EntityMotionState*>(motionState));
}
// NOTE: motionState will clean up its own backpointers in the Object
delete motionState;
continue;
}
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
//
// We're in the middle of step (1) hence incoming changes should trump corresponding
// outgoing changes at this point.
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
}
_incomingChanges.clear();
void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
removeObject(object);
addObject(object);
}
void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
@ -269,33 +203,7 @@ void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
}
}
// virtual
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
// _entityTree should be set prior to the init() call
assert(_entityTree);
if (!_dynamicsWorld) {
_collisionConfig = new btDefaultCollisionConfiguration();
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
_broadphaseFilter = new btDbvtBroadphase();
_constraintSolver = new btSequentialImpulseConstraintSolver;
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
_ghostPairCallback = new btGhostPairCallback();
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
// default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
}
assert(packetSender);
_entityPacketSender = packetSender;
EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted);
}
void PhysicsEngine::stepSimulation() {
lock();
CProfileManager::Reset();
BT_PROFILE("stepSimulation");
// NOTE: the grand order of operations is:
@ -304,9 +212,6 @@ void PhysicsEngine::stepSimulation() {
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// This is step (1) pull incoming changes
relayIncomingChangesToSimulation();
const int MAX_NUM_SUBSTEPS = 4;
const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP;
float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds());
@ -316,7 +221,7 @@ void PhysicsEngine::stepSimulation() {
// TODO: move character->preSimulation() into relayIncomingChanges
if (_characterController) {
if (_characterController->needsRemoval()) {
_characterController->setDynamicsWorld(NULL);
_characterController->setDynamicsWorld(nullptr);
}
_characterController->updateShapeIfNecessary();
if (_characterController->needsAddition()) {
@ -325,92 +230,49 @@ void PhysicsEngine::stepSimulation() {
_characterController->preSimulation(timeStep);
}
// This is step (2) step simulation
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow());
unlock();
// TODO: make all of this harvest stuff into one function: relayOutgoingChanges()
if (numSubsteps > 0) {
BT_PROFILE("postSimulation");
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
//
// TODO: untangle these lock sequences.
_numSubsteps += (uint32_t)numSubsteps;
ObjectMotionState::setWorldSimulationStep(_numSubsteps);
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
if (_characterController) {
_characterController->postSimulation();
}
computeCollisionEvents();
unlock();
_entityTree->unlock();
updateContactMap();
_hasOutgoingChanges = true;
}
}
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
BT_PROFILE("nonPhysicalKinematics");
QSet<ObjectMotionState*>::iterator stateItr = _nonPhysicalKinematicObjects.begin();
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
while (stateItr != _nonPhysicalKinematicObjects.end()) {
ObjectMotionState* motionState = *stateItr;
motionState->stepKinematicSimulation(now);
++stateItr;
}
}
void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) {
assert(objectA);
assert(objectB);
auto nodeList = DependencyManager::get<NodeList>();
QUuid myNodeID = nodeList->getSessionUUID();
if (myNodeID.isNull()) {
BT_PROFILE("ownershipInfection");
if (_sessionID.isNull()) {
return;
}
const btCollisionObject* characterCollisionObject =
_characterController ? _characterController->getCollisionObject() : NULL;
const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr;
ObjectMotionState* a = static_cast<ObjectMotionState*>(objectA->getUserPointer());
ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer());
EntityItem* entityA = a ? a->getEntity() : NULL;
EntityItem* entityB = b ? b->getEntity() : NULL;
bool aIsDynamic = entityA && !objectA->isStaticOrKinematicObject();
bool bIsDynamic = entityB && !objectB->isStaticOrKinematicObject();
// collisions cause infectious spread of simulation-ownership. we also attempt to take
// ownership of anything that collides with our avatar.
if ((aIsDynamic && (entityA->getSimulatorID() == myNodeID)) ||
// (a && a->getShouldClaimSimulationOwnership()) ||
(objectA == characterCollisionObject)) {
if (bIsDynamic) {
b->setShouldClaimSimulationOwnership(true);
if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) {
// NOTE: we might own the simulation of a kinematic object (A)
// but we don't claim ownership of kinematic objects (B) based on collisions here.
if (!objectB->isStaticOrKinematicObject()) {
b->bump();
}
} else if ((bIsDynamic && (entityB->getSimulatorID() == myNodeID)) ||
// (b && b->getShouldClaimSimulationOwnership()) ||
(objectB == characterCollisionObject)) {
if (aIsDynamic) {
a->setShouldClaimSimulationOwnership(true);
} else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) {
// SIMILARLY: we might own the simulation of a kinematic object (B)
// but we don't claim ownership of kinematic objects (A) based on collisions here.
if (!objectA->isStaticOrKinematicObject()) {
a->bump();
}
}
}
void PhysicsEngine::computeCollisionEvents() {
BT_PROFILE("computeCollisionEvents");
void PhysicsEngine::updateContactMap() {
BT_PROFILE("updateContactMap");
++_numContactFrames;
// update all contacts every frame
int numManifolds = _collisionDispatcher->getNumManifolds();
@ -432,39 +294,44 @@ void PhysicsEngine::computeCollisionEvents() {
ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer());
if (a || b) {
// the manifold has up to 4 distinct points, but only extract info from the first
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset);
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0));
}
doOwnershipInfection(objectA, objectB);
}
}
}
CollisionEvents& PhysicsEngine::getCollisionEvents() {
const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10;
_collisionEvents.clear();
// scan known contacts and trigger events
ContactMap::iterator contactItr = _contactMap.begin();
while (contactItr != _contactMap.end()) {
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
ContactInfo& contact = contactItr->second;
ContactEventType type = contact.computeType(_numContactFrames);
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) {
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
// TODO: make triggering these events clean and efficient. The code at this context shouldn't
// have to figure out what kind of object (entity, avatar, etc) these are in order to properly
// emit a collision event.
// TODO: enable scripts to filter based on contact event type
ContactEventType type = contactItr->second.computeType(_numContactFrames);
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0){
if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) {
EntityItemID idA = static_cast<EntityMotionState*>(A)->getEntity()->getEntityItemID();
EntityItemID idB;
QUuid idA = A->getObjectID();
QUuid idB;
if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
idB = B->getObjectID();
}
emit entityCollisionWithEntity(idA, idB, contactItr->second);
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset;
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration));
} else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
EntityItemID idA;
EntityItemID idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
emit entityCollisionWithEntity(idA, idB, contactItr->second);
QUuid idB = B->getObjectID();
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset;
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
// hence we must negate the penetration.
glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration));
}
}
@ -476,7 +343,14 @@ void PhysicsEngine::computeCollisionEvents() {
++contactItr;
}
}
++_numContactFrames;
return _collisionEvents;
}
VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
BT_PROFILE("copyOutgoingChanges");
_dynamicsWorld->synchronizeMotionStates();
_hasOutgoingChanges = false;
return _dynamicsWorld->getChangedMotionStates();
}
void PhysicsEngine::dumpStatsIfNecessary() {
@ -495,235 +369,80 @@ void PhysicsEngine::dumpStatsIfNecessary() {
// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing
// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing
void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState) {
assert(shape);
void PhysicsEngine::bump(ObjectMotionState* motionState) {
// Find all objects that touch the object corresponding to motionState and flag the other objects
// for simulation ownership by the local simulation.
assert(motionState);
btCollisionObject* object = motionState->getRigidBody();
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = 0.0f;
btRigidBody* body = NULL;
switch(motionState->computeObjectMotionType()) {
case MOTION_TYPE_KINEMATIC: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->setKinematic(true, _numSubsteps);
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->computeObjectMass(shapeInfo);
shape->calculateLocalInertia(mass, inertia);
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
motionState->updateBodyVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
if (!motionState->isMoving()) {
// try to initialize this object as inactive
body->forceActivationState(ISLAND_SLEEPING);
}
break;
}
case MOTION_TYPE_STATIC:
default: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->setRigidBody(body);
motionState->setKinematic(false, _numSubsteps);
break;
}
}
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
motionState->updateBodyMaterialProperties();
_dynamicsWorld->addRigidBody(body);
motionState->resetMeasuredBodyAcceleration();
}
void PhysicsEngine::bump(EntityItem* bumpEntity) {
// If this node is doing something like deleting an entity, scan for contacts involving the
// entity. For each found, flag the other entity involved as being simulated by this node.
lock();
int numManifolds = _collisionDispatcher->getNumManifolds();
for (int i = 0; i < numManifolds; ++i) {
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
if (contactManifold->getNumContacts() > 0) {
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
if (objectA && objectB) {
void* a = objectA->getUserPointer();
void* b = objectB->getUserPointer();
if (a && b) {
EntityMotionState* entityMotionStateA = static_cast<EntityMotionState*>(a);
EntityMotionState* entityMotionStateB = static_cast<EntityMotionState*>(b);
EntityItem* entityA = entityMotionStateA ? entityMotionStateA->getEntity() : NULL;
EntityItem* entityB = entityMotionStateB ? entityMotionStateB->getEntity() : NULL;
if (entityA && entityB) {
if (entityA == bumpEntity) {
entityMotionStateB->setShouldClaimSimulationOwnership(true);
if (!objectB->isActive()) {
objectB->setActivationState(ACTIVE_TAG);
}
}
if (entityB == bumpEntity) {
entityMotionStateA->setShouldClaimSimulationOwnership(true);
if (!objectA->isActive()) {
objectA->setActivationState(ACTIVE_TAG);
}
}
if (objectB == object) {
if (!objectA->isStaticOrKinematicObject()) {
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer());
if (motionStateA) {
motionStateA->bump();
objectA->setActivationState(ACTIVE_TAG);
}
}
} else if (objectA == object) {
if (!objectB->isStaticOrKinematicObject()) {
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer());
if (motionStateB) {
motionStateB->bump();
objectB->setActivationState(ACTIVE_TAG);
}
}
}
}
}
unlock();
}
void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) {
assert(motionState);
btRigidBody* body = motionState->getRigidBody();
// wake up anything touching this object
EntityItem* entityItem = motionState ? motionState->getEntity() : NULL;
bump(entityItem);
if (body) {
const btCollisionShape* shape = body->getCollisionShape();
_dynamicsWorld->removeRigidBody(body);
_shapeManager.releaseShape(shape);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
motionState->setRigidBody(NULL);
delete body;
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
}
}
// private
bool PhysicsEngine::updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
MotionType newType = motionState->computeObjectMotionType();
// pull body out of physics engine
_dynamicsWorld->removeRigidBody(body);
if (flags & EntityItem::DIRTY_SHAPE) {
// MASS bit should be set whenever SHAPE is set
assert(flags & EntityItem::DIRTY_MASS);
// get new shape
btCollisionShape* oldShape = body->getCollisionShape();
ShapeInfo shapeInfo;
motionState->computeObjectShapeInfo(shapeInfo);
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
if (!newShape) {
// FAIL! we are unable to support these changes!
_shapeManager.releaseShape(oldShape);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
motionState->setRigidBody(NULL);
delete body;
motionState->setKinematic(false, _numSubsteps);
removeContacts(motionState);
return false;
} else if (newShape != oldShape) {
// BUG: if shape doesn't change but density does then we won't compute new mass properties
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
body->setCollisionShape(newShape);
_shapeManager.releaseShape(oldShape);
// compute mass properties
float mass = motionState->computeObjectMass(shapeInfo);
btVector3 inertia(0.0f, 0.0f, 0.0f);
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
body->setMassProps(mass, inertia);
body->updateInertiaTensor();
} else {
// whoops, shape hasn't changed after all so we must release the reference
// that was created when looking it up
_shapeManager.releaseShape(newShape);
}
}
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
if (easyUpdate) {
motionState->updateBodyEasy(flags, _numSubsteps);
}
// update the motion parameters
switch (newType) {
case MOTION_TYPE_KINEMATIC: {
int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT;
collisionFlags &= ~(btCollisionObject::CF_STATIC_OBJECT);
body->setCollisionFlags(collisionFlags);
body->forceActivationState(DISABLE_DEACTIVATION);
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
body->updateInertiaTensor();
motionState->setKinematic(true, _numSubsteps);
break;
}
case MOTION_TYPE_DYNAMIC: {
int collisionFlags = body->getCollisionFlags() & ~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT);
body->setCollisionFlags(collisionFlags);
if (! (flags & EntityItem::DIRTY_MASS)) {
// always update mass properties when going dynamic (unless it's already been done above)
ShapeInfo shapeInfo;
motionState->computeObjectShapeInfo(shapeInfo);
float mass = motionState->computeObjectMass(shapeInfo);
btVector3 inertia(0.0f, 0.0f, 0.0f);
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
body->setMassProps(mass, inertia);
body->updateInertiaTensor();
}
body->forceActivationState(ACTIVE_TAG);
motionState->setKinematic(false, _numSubsteps);
break;
}
default: {
// MOTION_TYPE_STATIC
int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT;
collisionFlags &= ~(btCollisionObject::CF_KINEMATIC_OBJECT);
body->setCollisionFlags(collisionFlags);
body->forceActivationState(DISABLE_SIMULATION);
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
body->updateInertiaTensor();
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
motionState->setKinematic(false, _numSubsteps);
break;
}
}
// reinsert body into physics engine
_dynamicsWorld->addRigidBody(body);
body->activate();
return true;
}
void PhysicsEngine::setCharacterController(DynamicCharacterController* character) {
if (_characterController != character) {
lock();
if (_characterController) {
// remove the character from the DynamicsWorld immediately
_characterController->setDynamicsWorld(NULL);
_characterController = NULL;
_characterController->setDynamicsWorld(nullptr);
_characterController = nullptr;
}
// the character will be added to the DynamicsWorld later
_characterController = character;
unlock();
}
}
bool PhysicsEngine::physicsInfoIsActive(void* physicsInfo) {
if (!physicsInfo) {
return false;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* body = motionState->getRigidBody();
if (!body) {
return false;
}
return body->isActive();
}
bool PhysicsEngine::getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn) {
if (!physicsInfo) {
return false;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* body = motionState->getRigidBody();
if (!body) {
return false;
}
const btTransform& worldTrans = body->getCenterOfMassTransform();
positionReturn = bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset();
rotationReturn = bulletToGLM(worldTrans.getRotation());
return true;
}

View file

@ -14,18 +14,15 @@
#include <stdint.h>
#include <QSet>
#include <QUuid>
#include <QVector>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
#include "BulletUtil.h"
#include "DynamicCharacterController.h"
#include "ContactInfo.h"
#include "EntityMotionState.h"
#include "ShapeManager.h"
#include "DynamicCharacterController.h"
#include "PhysicsTypedefs.h"
#include "ThreadSafeDynamicsWorld.h"
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
@ -39,37 +36,45 @@ public:
ContactKey(void* a, void* b) : _a(a), _b(b) {}
bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); }
bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; }
void* _a; // EntityMotionState pointer
void* _b; // EntityMotionState pointer
void* _a; // ObjectMotionState pointer
void* _b; // ObjectMotionState pointer
};
typedef std::map<ContactKey, ContactInfo> ContactMap;
typedef std::pair<ContactKey, ContactInfo> ContactMapElement;
typedef QVector<Collision> CollisionEvents;
class PhysicsEngine : public EntitySimulation {
class PhysicsEngine {
public:
// TODO: find a good way to make this a non-static method
static uint32_t getNumSubsteps();
PhysicsEngine() = delete; // prevent compiler from creating default ctor
PhysicsEngine(const glm::vec3& offset);
~PhysicsEngine();
void init();
// overrides for EntitySimulation
void updateEntitiesInternal(const quint64& now);
void addEntityInternal(EntityItem* entity);
void removeEntityInternal(EntityItem* entity);
void entityChangedInternal(EntityItem* entity);
void sortEntitiesThatMovedInternal();
void clearEntitiesInternal();
void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; }
virtual void init(EntityEditPacketSender* packetSender);
void addObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState);
void deleteObjects(VectorOfMotionStates& objects);
void deleteObjects(SetOfMotionStates& objects); // only called during teardown
void addObjects(VectorOfMotionStates& objects);
void changeObjects(VectorOfMotionStates& objects);
void reinsertObject(ObjectMotionState* object);
void stepSimulation();
void stepNonPhysicalKinematics(const quint64& now);
void computeCollisionEvents();
void updateContactMap();
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
/// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop.
VectorOfMotionStates& getOutgoingChanges();
/// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop.
CollisionEvents& getCollisionEvents();
/// \brief prints timings for last frame if stats have been requested.
void dumpStatsIfNecessary();
/// \param offset position of simulation origin in domain-frame
@ -78,31 +83,23 @@ public:
/// \return position of simulation origin in domain-frame
const glm::vec3& getOriginOffset() const { return _originOffset; }
/// \param motionState pointer to Object's MotionState
/// \return true if Object added
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
/// \brief call bump on any objects that touch the object corresponding to motionState
void bump(ObjectMotionState* motionState);
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
void removeRigidBody(btRigidBody* body);
void setCharacterController(DynamicCharacterController* character);
void dumpNextStats() { _dumpNextStats = true; }
void bump(EntityItem* bumpEntity);
static bool physicsInfoIsActive(void* physicsInfo);
static bool getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn);
private:
/// \param motionState pointer to Object's MotionState
void removeObjectFromBullet(ObjectMotionState* motionState);
void removeContacts(ObjectMotionState* motionState);
void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB);
// return 'true' of update was successful
bool updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
btClock _clock;
btDefaultCollisionConfiguration* _collisionConfig = NULL;
btCollisionDispatcher* _collisionDispatcher = NULL;
@ -110,18 +107,9 @@ private:
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
btGhostPairCallback* _ghostPairCallback = NULL;
ShapeManager _shapeManager;
glm::vec3 _originOffset;
// EntitySimulation stuff
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
QSet<ObjectMotionState*> _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire
EntityEditPacketSender* _entityPacketSender = NULL;
ContactMap _contactMap;
uint32_t _numContactFrames = 0;
uint32_t _lastNumSubstepsAtUpdateInternal = 0;
@ -130,6 +118,10 @@ private:
DynamicCharacterController* _characterController = NULL;
bool _dumpNextStats = false;
bool _hasOutgoingChanges = false;
QUuid _sessionID;
CollisionEvents _collisionEvents;
};
#endif // hifi_PhysicsEngine_h

View file

@ -0,0 +1,23 @@
//
// PhysicsTypedefs.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2015.04.29
// Copyright 2015 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_PhysicsTypedefs_h
#define hifi_PhysicsTypedefs_h
#include <QSet>
#include <QVector>
class ObjectMotionState;
typedef QSet<ObjectMotionState*> SetOfMotionStates;
typedef QVector<ObjectMotionState*> VectorOfMotionStates;
#endif //hifi_PhysicsTypedefs_h

View file

@ -17,6 +17,7 @@
#include <LinearMath/btQuickprof.h>
#include "ObjectMotionState.h"
#include "ThreadSafeDynamicsWorld.h"
ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
@ -57,7 +58,7 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
/*//process some debugging flags
if (getDebugDrawer()) {
btIDebugDraw* debugDrawer = getDebugDrawer ();
btIDebugDraw* debugDrawer = getDebugDrawer();
gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0;
}*/
if (subSteps) {
@ -84,3 +85,33 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
return subSteps;
}
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
_changedMotionStates.clear();
BT_PROFILE("synchronizeMotionStates");
if (m_synchronizeAllMotionStates) {
//iterate over all collision objects
for (int i=0;i<m_collisionObjects.size();i++) {
btCollisionObject* colObj = m_collisionObjects[i];
btRigidBody* body = btRigidBody::upcast(colObj);
if (body) {
if (body->getMotionState()) {
synchronizeSingleMotionState(body);
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
}
}
}
} else {
//iterate over all active rigid bodies
for (int i=0;i<m_nonStaticRigidBodies.size();i++) {
btRigidBody* body = m_nonStaticRigidBodies[i];
if (body->isActive()) {
if (body->getMotionState()) {
synchronizeSingleMotionState(body);
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
}
}
}
}
}

View file

@ -18,8 +18,11 @@
#ifndef hifi_ThreadSafeDynamicsWorld_h
#define hifi_ThreadSafeDynamicsWorld_h
#include <BulletDynamics/Dynamics/btRigidBody.h>
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include "PhysicsTypedefs.h"
ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld {
public:
BT_DECLARE_ALIGNED_ALLOCATOR();
@ -32,11 +35,17 @@ public:
// virtual overrides from btDiscreteDynamicsWorld
int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.));
void synchronizeMotionStates();
// btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated
// but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide
// smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop).
float getLocalTimeAccumulation() const { return m_localTime; }
VectorOfMotionStates& getChangedMotionStates() { return _changedMotionStates; }
private:
VectorOfMotionStates _changedMotionStates;
};
#endif // hifi_ThreadSafeDynamicsWorld_h

View file

@ -187,6 +187,9 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) {
QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) {
QScriptValue obj = engine->newObject();
obj.setProperty("type", collision.type);
obj.setProperty("idA", quuidToScriptValue(engine, collision.idA));
obj.setProperty("idB", quuidToScriptValue(engine, collision.idB));
obj.setProperty("penetration", vec3toScriptValue(engine, collision.penetration));
obj.setProperty("contactPoint", vec3toScriptValue(engine, collision.contactPoint));
return obj;

View file

@ -13,6 +13,7 @@
#define hifi_RegisteredMetaTypes_h
#include <QtScript/QScriptEngine>
#include <QtCore/QUuid>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
@ -65,11 +66,21 @@ Q_DECLARE_METATYPE(PickRay)
QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay);
void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay);
enum ContactEventType {
CONTACT_EVENT_TYPE_START,
CONTACT_EVENT_TYPE_CONTINUE,
CONTACT_EVENT_TYPE_END
};
class Collision {
public:
Collision() : contactPoint(0.0f), penetration(0.0f) { }
Collision(const glm::vec3& contactPoint, const glm::vec3& penetration) :
contactPoint(contactPoint), penetration(penetration) { }
Collision() : type(CONTACT_EVENT_TYPE_START), idA(), idB(), contactPoint(0.0f), penetration(0.0f) { }
Collision(ContactEventType cType, const QUuid& cIdA, const QUuid& cIdB, const glm::vec3& cPoint, const glm::vec3& cPenetration)
: type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration) { }
ContactEventType type;
QUuid idA;
QUuid idB;
glm::vec3 contactPoint;
glm::vec3 penetration;
};