mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 12:04:18 +02:00
Merge pull request #4772 from AndrewMeadows/nova
PhysicsEngine cleanup in preparation for collidable avatars
This commit is contained in:
commit
b7b62c8d42
33 changed files with 1719 additions and 1321 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
238
libraries/physics/src/PhysicalEntitySimulation.cpp
Normal file
238
libraries/physics/src/PhysicalEntitySimulation.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
libraries/physics/src/PhysicalEntitySimulation.h
Normal file
73
libraries/physics/src/PhysicalEntitySimulation.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
23
libraries/physics/src/PhysicsTypedefs.h
Normal file
23
libraries/physics/src/PhysicsTypedefs.h
Normal 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
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue