mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:44:02 +02:00
Merge pull request #4144 from AndrewMeadows/isentropic
Bullet collisions trigger collision events in scripts.
This commit is contained in:
commit
5a7912de17
16 changed files with 231 additions and 427 deletions
|
@ -192,7 +192,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_justStarted(true),
|
||||
_physicsEngine(glm::vec3(0.0f)),
|
||||
_entities(true, this, this),
|
||||
_entityCollisionSystem(),
|
||||
_entityClipboardRenderer(false, this, this),
|
||||
_entityClipboard(),
|
||||
_viewFrustum(),
|
||||
|
@ -1689,17 +1688,16 @@ void Application::init() {
|
|||
_entities.init();
|
||||
_entities.setViewFrustum(getViewFrustum());
|
||||
|
||||
EntityTree* entityTree = _entities.getTree();
|
||||
|
||||
_entityCollisionSystem.init(&_entityEditSender, entityTree, &_avatarManager);
|
||||
|
||||
entityTree->setSimulation(&_entityCollisionSystem);
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_physicsEngine.setEntityTree(tree);
|
||||
tree->setSimulation(&_physicsEngine);
|
||||
_physicsEngine.init(&_entityEditSender);
|
||||
|
||||
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
|
||||
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
|
||||
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
|
||||
&_entities, &EntityTreeRenderer::entityCollisionWithEntity);
|
||||
|
||||
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
|
||||
|
@ -1723,10 +1721,6 @@ void Application::init() {
|
|||
// save settings when avatar changes
|
||||
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
|
||||
|
||||
EntityTree* tree = _entities.getTree();
|
||||
_physicsEngine.setEntityTree(tree);
|
||||
tree->setSimulation(&_physicsEngine);
|
||||
_physicsEngine.init(&_entityEditSender);
|
||||
// make sure our texture cache knows about window size changes
|
||||
DependencyManager::get<TextureCache>()->associateWithWidget(glCanvas.data());
|
||||
|
||||
|
@ -2047,9 +2041,6 @@ void Application::update(float deltaTime) {
|
|||
// NOTE: the _entities.update() call below will wait for lock
|
||||
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
|
||||
_entities.update(); // update the models...
|
||||
// The _entityCollisionSystem.updateCollisions() call below merely tries for lock,
|
||||
// and on failure it skips collision detection.
|
||||
_entityCollisionSystem.updateCollisions(); // collide the entities...
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include <AbstractScriptingServicesInterface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <EntityCollisionSystem.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <GeometryCache.h>
|
||||
|
@ -466,7 +465,6 @@ private:
|
|||
PhysicsEngine _physicsEngine;
|
||||
|
||||
EntityTreeRenderer _entities;
|
||||
EntityCollisionSystem _entityCollisionSystem;
|
||||
EntityTreeRenderer _entityClipboardRenderer;
|
||||
EntityTree _entityClipboard;
|
||||
|
||||
|
|
|
@ -1,302 +0,0 @@
|
|||
//
|
||||
// EntityCollisionSystem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/23/14.
|
||||
// Copyright 2013-2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <AbstractAudioInterface.h>
|
||||
#include <AvatarData.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <HeadData.h>
|
||||
#include <HandData.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "EntityCollisionSystem.h"
|
||||
#include "EntityEditPacketSender.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
const int MAX_COLLISIONS_PER_Entity = 16;
|
||||
|
||||
EntityCollisionSystem::EntityCollisionSystem()
|
||||
: SimpleEntitySimulation(),
|
||||
_packetSender(NULL),
|
||||
_avatars(NULL),
|
||||
_collisions(MAX_COLLISIONS_PER_Entity) {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
|
||||
EntityTree* entities, AvatarHashMap* avatars) {
|
||||
assert(entities);
|
||||
setEntityTree(entities);
|
||||
_packetSender = packetSender;
|
||||
_avatars = avatars;
|
||||
}
|
||||
|
||||
EntityCollisionSystem::~EntityCollisionSystem() {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisions() {
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
assert(_entityTree);
|
||||
// update all Entities
|
||||
if (_entityTree->tryLockForWrite()) {
|
||||
foreach (EntityItem* entity, _movingEntities) {
|
||||
checkEntity(entity);
|
||||
}
|
||||
_entityTree->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EntityCollisionSystem::checkEntity(EntityItem* entity) {
|
||||
updateCollisionWithEntities(entity);
|
||||
updateCollisionWithAvatars(entity);
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* entityA,
|
||||
EntityItem* entityB, const Collision& collision) {
|
||||
|
||||
EntityItemID idA = entityA->getEntityItemID();
|
||||
EntityItemID idB = entityB->getEntityItemID();
|
||||
emit entityCollisionWithEntity(idA, idB, collision);
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
||||
|
||||
if (entityA->getIgnoreForCollisions()) {
|
||||
return; // bail early if this entity is to be ignored...
|
||||
}
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entityA->isKnownID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
EntityItem* entityB = NULL;
|
||||
|
||||
const int MAX_COLLISIONS_PER_ENTITY = 32;
|
||||
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
|
||||
bool shapeCollisionsAccurate = false;
|
||||
|
||||
bool shapeCollisions = _entityTree->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
||||
collisions, Octree::NoLock, &shapeCollisionsAccurate);
|
||||
|
||||
if (shapeCollisions) {
|
||||
for(int i = 0; i < collisions.size(); i++) {
|
||||
|
||||
CollisionInfo* collision = collisions[i];
|
||||
penetration = collision->_penetration;
|
||||
entityB = static_cast<EntityItem*>(collision->_extraData);
|
||||
|
||||
// The collision _extraData should be a valid entity, but if for some reason
|
||||
// it's NULL then continue with a warning.
|
||||
if (!entityB) {
|
||||
qDebug() << "UNEXPECTED - we have a collision with missing _extraData. Something went wrong down below!";
|
||||
continue; // skip this loop pass if the entity is NULL
|
||||
}
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entityB->isKnownID()) {
|
||||
continue; // skip this loop pass if the entity has an unknown ID
|
||||
}
|
||||
|
||||
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
|
||||
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
|
||||
|
||||
// Even if the Entities overlap... when the Entities are already moving appart
|
||||
// we don't want to count this as a collision.
|
||||
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
|
||||
|
||||
bool fullyEnclosedCollision = glm::length(penetrationInTreeUnits) > entityA->getLargestDimension();
|
||||
|
||||
bool wantToMoveA = entityA->getCollisionsWillMove();
|
||||
bool wantToMoveB = entityB->getCollisionsWillMove();
|
||||
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
|
||||
|
||||
// only do collisions if the entities are moving toward each other and one or the other
|
||||
// of the entities are movable from collisions
|
||||
bool doCollisions = !fullyEnclosedCollision && movingTowardEachOther && (wantToMoveA || wantToMoveB);
|
||||
|
||||
if (doCollisions) {
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
glm::vec3 axis = glm::normalize(penetration);
|
||||
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
|
||||
|
||||
float massA = entityA->computeMass();
|
||||
float massB = entityB->computeMass();
|
||||
float totalMass = massA + massB;
|
||||
float massRatioA = (2.0f * massB / totalMass);
|
||||
float massRatioB = (2.0f * massA / totalMass);
|
||||
|
||||
// in the event that one of our entities is non-moving, then fix up these ratios
|
||||
if (wantToMoveA && !wantToMoveB) {
|
||||
massRatioA = 2.0f;
|
||||
massRatioB = 0.0f;
|
||||
}
|
||||
|
||||
if (!wantToMoveA && wantToMoveB) {
|
||||
massRatioA = 0.0f;
|
||||
massRatioB = 2.0f;
|
||||
}
|
||||
|
||||
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||
// and velocity and apply it
|
||||
if (wantToMoveA) {
|
||||
// handle Entity A
|
||||
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * massRatioA;
|
||||
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
|
||||
|
||||
EntityItemProperties propertiesA = entityA->getProperties();
|
||||
EntityItemID idA(entityA->getID());
|
||||
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
|
||||
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
|
||||
propertiesA.setLastEdited(now);
|
||||
|
||||
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
|
||||
// thereby waking up static non-moving entities.
|
||||
_entityTree->updateEntity(entityA, propertiesA);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
|
||||
}
|
||||
|
||||
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||
// and velocity and apply it
|
||||
if (wantToMoveB) {
|
||||
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
|
||||
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
|
||||
|
||||
EntityItemProperties propertiesB = entityB->getProperties();
|
||||
|
||||
EntityItemID idB(entityB->getID());
|
||||
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
|
||||
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
|
||||
propertiesB.setLastEdited(now);
|
||||
|
||||
// NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation,
|
||||
// thereby waking up static non-moving entities.
|
||||
_entityTree->updateEntity(entityB, propertiesB);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
|
||||
}
|
||||
|
||||
// NOTE: Do this after updating the entities so that the callback can delete the entities if they want to
|
||||
Collision collision;
|
||||
collision.penetration = penetration;
|
||||
collision.contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
|
||||
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::updateCollisionWithAvatars(EntityItem* entity) {
|
||||
|
||||
// Entities that are in hand, don't collide with avatars
|
||||
if (!_avatars) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity->getIgnoreForCollisions() || !entity->getCollisionsWillMove()) {
|
||||
return; // bail early if this entity is to be ignored or wont move
|
||||
}
|
||||
|
||||
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
|
||||
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
||||
const float ELASTICITY = 0.9f;
|
||||
const float DAMPING = 0.1f;
|
||||
glm::vec3 penetration;
|
||||
|
||||
_collisions.clear();
|
||||
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
|
||||
AvatarData* avatar = avatarPointer.data();
|
||||
|
||||
float totalRadius = avatar->getBoundingRadius() + radius;
|
||||
glm::vec3 relativePosition = center - avatar->getPosition();
|
||||
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (avatar->findSphereCollisions(center, radius, _collisions)) {
|
||||
int numCollisions = _collisions.size();
|
||||
for (int i = 0; i < numCollisions; ++i) {
|
||||
CollisionInfo* collision = _collisions.getCollision(i);
|
||||
collision->_damping = DAMPING;
|
||||
collision->_elasticity = ELASTICITY;
|
||||
|
||||
collision->_addedVelocity /= (float)(TREE_SCALE);
|
||||
glm::vec3 relativeVelocity = collision->_addedVelocity - entity->getVelocity();
|
||||
|
||||
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.0f) {
|
||||
// only collide when Entity and collision point are moving toward each other
|
||||
// (doing this prevents some "collision snagging" when Entity penetrates the object)
|
||||
collision->_penetration /= (float)(TREE_SCALE);
|
||||
applyHardCollision(entity, *collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo) {
|
||||
|
||||
// don't collide entities with unknown IDs,
|
||||
if (!entity->isKnownID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// HALTING_* params are determined using expected acceleration of gravity over some timescale.
|
||||
// This is a HACK for entities that bounce in a 1.0 gravitational field and should eventually be made more universal.
|
||||
const float HALTING_ENTITY_PERIOD = 0.0167f; // ~1/60th of a second
|
||||
const float HALTING_ENTITY_SPEED = 9.8 * HALTING_ENTITY_PERIOD / (float)(TREE_SCALE);
|
||||
|
||||
//
|
||||
// Update the entity in response to a hard collision. Position will be reset exactly
|
||||
// to outside the colliding surface. Velocity will be modified according to elasticity.
|
||||
//
|
||||
// if elasticity = 0.0, collision is inelastic (vel normal to collision is lost)
|
||||
// if elasticity = 1.0, collision is 100% elastic.
|
||||
//
|
||||
glm::vec3 position = entity->getPosition();
|
||||
glm::vec3 velocity = entity->getVelocity();
|
||||
|
||||
const float EPSILON = 0.0f;
|
||||
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
|
||||
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
|
||||
if (velocityDotPenetration < EPSILON) {
|
||||
// entity is moving into collision surface
|
||||
//
|
||||
// TODO: do something smarter here by comparing the mass of the entity vs that of the other thing
|
||||
// (other's mass could be stored in the Collision Info). The smaller mass should surrender more
|
||||
// position offset and should slave more to the other's velocity in the static-friction case.
|
||||
position -= collisionInfo._penetration;
|
||||
|
||||
if (glm::length(relativeVelocity) < HALTING_ENTITY_SPEED) {
|
||||
// static friction kicks in and entities moves with colliding object
|
||||
velocity = collisionInfo._addedVelocity;
|
||||
} else {
|
||||
glm::vec3 direction = glm::normalize(collisionInfo._penetration);
|
||||
velocity += glm::dot(relativeVelocity, direction) * (1.0f + collisionInfo._elasticity) * direction; // dynamic reflection
|
||||
velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
EntityItemID entityItemID(entity->getID());
|
||||
|
||||
properties.setPosition(position * (float)TREE_SCALE);
|
||||
properties.setVelocity(velocity * (float)TREE_SCALE);
|
||||
properties.setLastEdited(usecTimestampNow());
|
||||
|
||||
_entityTree->updateEntity(entity, properties);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties);
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// EntityCollisionSystem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/23/14.
|
||||
// Copyright 2013-2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityCollisionSystem_h
|
||||
#define hifi_EntityCollisionSystem_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "SimpleEntitySimulation.h"
|
||||
|
||||
class AbstractAudioInterface;
|
||||
class AvatarData;
|
||||
class EntityEditPacketSender;
|
||||
class EntityTree;
|
||||
|
||||
class EntityCollisionSystem : public QObject, public SimpleEntitySimulation {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityCollisionSystem();
|
||||
|
||||
void init(EntityEditPacketSender* packetSender, EntityTree* entities, AvatarHashMap* _avatars = NULL);
|
||||
|
||||
~EntityCollisionSystem();
|
||||
|
||||
void updateCollisions();
|
||||
|
||||
void checkEntity(EntityItem* Entity);
|
||||
void updateCollisionWithEntities(EntityItem* Entity);
|
||||
void updateCollisionWithAvatars(EntityItem* Entity);
|
||||
void queueEntityPropertiesUpdate(EntityItem* Entity);
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
private:
|
||||
void applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo);
|
||||
|
||||
static bool updateOperation(OctreeElement* element, void* extraData);
|
||||
void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const Collision& penetration);
|
||||
|
||||
EntityEditPacketSender* _packetSender;
|
||||
AbstractAudioInterface* _audio;
|
||||
AvatarHashMap* _avatars;
|
||||
CollisionList _collisions;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityCollisionSystem_h
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef hifi_EntitySimulation_h
|
||||
#define hifi_EntitySimulation_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
@ -31,7 +32,8 @@ const int DIRTY_SIMULATION_FLAGS =
|
|||
EntityItem::DIRTY_LIFETIME |
|
||||
EntityItem::DIRTY_UPDATEABLE;
|
||||
|
||||
class EntitySimulation {
|
||||
class EntitySimulation : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL) { }
|
||||
virtual ~EntitySimulation() { setEntityTree(NULL); }
|
||||
|
@ -61,6 +63,9 @@ public:
|
|||
|
||||
EntityTree* getEntityTree() { return _entityTree; }
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
protected:
|
||||
|
||||
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
|
||||
|
|
29
libraries/physics/src/ContactInfo.cpp
Normal file
29
libraries/physics/src/ContactInfo.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ContactEvent.cpp
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// 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 "BulletUtil.h"
|
||||
#include "ContactInfo.h"
|
||||
|
||||
void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) {
|
||||
_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);
|
||||
}
|
||||
|
||||
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
|
||||
if (_lastStep != thisStep) {
|
||||
return CONTACT_EVENT_TYPE_END;
|
||||
}
|
||||
return (_numSteps == 1) ? CONTACT_EVENT_TYPE_START : CONTACT_EVENT_TYPE_CONTINUE;
|
||||
}
|
37
libraries/physics/src/ContactInfo.h
Normal file
37
libraries/physics/src/ContactInfo.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// ContactEvent.h
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// 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_ContactEvent_h
|
||||
#define hifi_ContactEvent_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
enum ContactEventType {
|
||||
CONTACT_EVENT_TYPE_START,
|
||||
CONTACT_EVENT_TYPE_CONTINUE,
|
||||
CONTACT_EVENT_TYPE_END
|
||||
};
|
||||
|
||||
class ContactInfo : public Collision
|
||||
{
|
||||
public:
|
||||
void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset);
|
||||
ContactEventType computeType(uint32_t thisStep);
|
||||
private:
|
||||
uint32_t _lastStep = 0;
|
||||
uint32_t _numSteps = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_ContactEvent_h
|
|
@ -33,6 +33,7 @@ void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
|
|||
|
||||
EntityMotionState::EntityMotionState(EntityItem* entity)
|
||||
: _entity(entity) {
|
||||
_type = MOTION_STATE_TYPE_ENTITY;
|
||||
assert(entity != NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ public:
|
|||
uint32_t getIncomingDirtyFlags() const;
|
||||
void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
|
||||
|
||||
EntityItem* getEntity() const { return _entity; }
|
||||
|
||||
protected:
|
||||
EntityItem* _entity;
|
||||
};
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
#include "PhysicsEngine.h"
|
||||
|
||||
KinematicController::KinematicController() {
|
||||
_lastFrame = PhysicsEngine::getFrameCount();
|
||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
||||
}
|
||||
|
||||
void KinematicController::start() {
|
||||
_enabled = true;
|
||||
_lastFrame = PhysicsEngine::getFrameCount();
|
||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
|
||||
protected:
|
||||
bool _enabled = false;
|
||||
uint32_t _lastFrame;
|
||||
uint32_t _lastSubstep;
|
||||
};
|
||||
|
||||
#endif // hifi_KinematicController_h
|
||||
|
|
|
@ -170,3 +170,16 @@ void ObjectMotionState::removeKinematicController() {
|
|||
_kinematicController = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||
// give the body a (void*) back-pointer to this ObjectMotionState
|
||||
if (_body != body) {
|
||||
if (_body) {
|
||||
_body->setUserPointer(NULL);
|
||||
}
|
||||
_body = body;
|
||||
if (_body) {
|
||||
_body->setUserPointer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <EntityItem.h>
|
||||
|
||||
#include "ContactInfo.h"
|
||||
#include "ShapeInfo.h"
|
||||
|
||||
enum MotionType {
|
||||
|
@ -25,6 +26,12 @@ enum MotionType {
|
|||
MOTION_TYPE_KINEMATIC // keyframed motion
|
||||
};
|
||||
|
||||
enum MotionStateType {
|
||||
MOTION_STATE_TYPE_UNKNOWN,
|
||||
MOTION_STATE_TYPE_ENTITY,
|
||||
MOTION_STATE_TYPE_AVATAR
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
@ -58,6 +65,7 @@ public:
|
|||
virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0;
|
||||
virtual void updateObjectVelocities() = 0;
|
||||
|
||||
MotionStateType getType() const { return _type; }
|
||||
virtual MotionType getMotionType() const { return _motionType; }
|
||||
|
||||
virtual void computeShapeInfo(ShapeInfo& info) = 0;
|
||||
|
@ -88,9 +96,15 @@ public:
|
|||
virtual void addKinematicController() = 0;
|
||||
virtual void removeKinematicController();
|
||||
|
||||
btRigidBody* getRigidBody() const { return _body; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
protected:
|
||||
// TODO: move these materials properties to EntityItem
|
||||
void setRigidBody(btRigidBody* body);
|
||||
|
||||
MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN;
|
||||
|
||||
// TODO: move these materials properties outside of ObjectMotionState
|
||||
float _friction;
|
||||
float _restitution;
|
||||
float _linearDamping;
|
||||
|
@ -98,7 +112,6 @@ protected:
|
|||
|
||||
MotionType _motionType;
|
||||
|
||||
// _body has NO setters -- it is only changed by PhysicsEngine
|
||||
btRigidBody* _body;
|
||||
|
||||
bool _sentMoving; // true if last update was moving
|
||||
|
|
|
@ -13,21 +13,15 @@
|
|||
#include "ShapeInfoUtil.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
||||
static uint32_t _frameCount;
|
||||
static uint32_t _numSubsteps;
|
||||
|
||||
// static
|
||||
uint32_t PhysicsEngine::getFrameCount() {
|
||||
return _frameCount;
|
||||
uint32_t PhysicsEngine::getNumSubsteps() {
|
||||
return _numSubsteps;
|
||||
}
|
||||
|
||||
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
||||
: _collisionConfig(NULL),
|
||||
_collisionDispatcher(NULL),
|
||||
_broadphaseFilter(NULL),
|
||||
_constraintSolver(NULL),
|
||||
_dynamicsWorld(NULL),
|
||||
_originOffset(offset),
|
||||
_entityPacketSender(NULL) {
|
||||
: _originOffset(offset) {
|
||||
}
|
||||
|
||||
PhysicsEngine::~PhysicsEngine() {
|
||||
|
@ -47,8 +41,8 @@ void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
|
|||
ObjectMotionState* state = *stateItr;
|
||||
if (state->doesNotNeedToSendUpdate()) {
|
||||
stateItr = _outgoingPackets.erase(stateItr);
|
||||
} else if (state->shouldSendUpdate(_frameCount)) {
|
||||
state->sendUpdate(_entityPacketSender, _frameCount);
|
||||
} else if (state->shouldSendUpdate(_numSubsteps)) {
|
||||
state->sendUpdate(_entityPacketSender, _numSubsteps);
|
||||
++stateItr;
|
||||
} else {
|
||||
++stateItr;
|
||||
|
@ -135,7 +129,7 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
ObjectMotionState* motionState = *stateItr;
|
||||
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||
|
||||
btRigidBody* body = motionState->_body;
|
||||
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
|
||||
|
@ -144,7 +138,7 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
} 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->updateObjectEasy(flags, _frameCount);
|
||||
motionState->updateObjectEasy(flags, _numSubsteps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,6 +157,20 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
_incomingChanges.clear();
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeContacts(ObjectMotionState* motionState) {
|
||||
// trigger events for new/existing/old contacts
|
||||
ContactMap::iterator contactItr = _contactMap.begin();
|
||||
while (contactItr != _contactMap.end()) {
|
||||
if (contactItr->first._a == motionState || contactItr->first._b == motionState) {
|
||||
ContactMap::iterator iterToDelete = contactItr;
|
||||
++contactItr;
|
||||
_contactMap.erase(iterToDelete);
|
||||
} else {
|
||||
++contactItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
|
||||
// _entityTree should be set prior to the init() call
|
||||
|
@ -219,8 +227,8 @@ void PhysicsEngine::stepSimulation() {
|
|||
float timeStep = btMin(dt, MAX_TIMESTEP);
|
||||
|
||||
// This is step (2).
|
||||
int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
_frameCount += (uint32_t)numSubSteps;
|
||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||
_numSubsteps += (uint32_t)numSubsteps;
|
||||
unlock();
|
||||
|
||||
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
|
||||
|
@ -236,6 +244,60 @@ void PhysicsEngine::stepSimulation() {
|
|||
_dynamicsWorld->synchronizeMotionStates();
|
||||
unlock();
|
||||
_entityTree->unlock();
|
||||
|
||||
computeCollisionEvents();
|
||||
}
|
||||
|
||||
void PhysicsEngine::computeCollisionEvents() {
|
||||
// update all contacts
|
||||
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());
|
||||
|
||||
void* a = objectA->getUserPointer();
|
||||
void* b = 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(_numSubsteps, contactManifold->getContactPoint(0), _originOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
EntityItemID idA = static_cast<EntityMotionState*>(A)->getEntity()->getEntityItemID();
|
||||
EntityItemID idB;
|
||||
if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
idB = static_cast<EntityMotionState*>(B)->getEntity()->getEntityItemID();
|
||||
}
|
||||
emit entityCollisionWithEntity(idA, idB, contactItr->second);
|
||||
} 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);
|
||||
}
|
||||
|
||||
// TODO: enable scripts to filter based on contact event type
|
||||
ContactEventType type = contactItr->second.computeType(_numSubsteps);
|
||||
if (type == CONTACT_EVENT_TYPE_END) {
|
||||
ContactMap::iterator iterToDelete = contactItr;
|
||||
++contactItr;
|
||||
_contactMap.erase(iterToDelete);
|
||||
} else {
|
||||
++contactItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bullet collision flags are as follows:
|
||||
|
@ -259,7 +321,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
motionState->addKinematicController();
|
||||
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
||||
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
||||
|
@ -271,7 +333,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
shape->calculateLocalInertia(mass, inertia);
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
motionState->updateObjectVelocities();
|
||||
// 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
|
||||
|
@ -285,7 +347,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||
body->updateInertiaTensor();
|
||||
motionState->_body = body;
|
||||
motionState->setRigidBody(body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +360,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
|
||||
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||
assert(motionState);
|
||||
btRigidBody* body = motionState->_body;
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (body) {
|
||||
const btCollisionShape* shape = body->getCollisionShape();
|
||||
ShapeInfo shapeInfo;
|
||||
|
@ -306,8 +368,10 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
|||
_dynamicsWorld->removeRigidBody(body);
|
||||
_shapeManager.releaseShape(shapeInfo);
|
||||
delete body;
|
||||
motionState->_body = NULL;
|
||||
motionState->setRigidBody(NULL);
|
||||
motionState->removeKinematicController();
|
||||
|
||||
removeContacts(motionState);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -349,7 +413,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
|||
}
|
||||
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
|
||||
if (easyUpdate) {
|
||||
motionState->updateObjectEasy(flags, _frameCount);
|
||||
motionState->updateObjectEasy(flags, _numSubsteps);
|
||||
}
|
||||
|
||||
// update the motion parameters
|
||||
|
|
|
@ -23,6 +23,7 @@ const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f;
|
|||
#include <EntitySimulation.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "ContactInfo.h"
|
||||
#include "EntityMotionState.h"
|
||||
#include "ShapeManager.h"
|
||||
#include "ThreadSafeDynamicsWorld.h"
|
||||
|
@ -31,10 +32,26 @@ const float HALF_SIMULATION_EXTENT = 512.0f; // meters
|
|||
|
||||
class ObjectMotionState;
|
||||
|
||||
// simple class for keeping track of contacts
|
||||
class ContactKey {
|
||||
public:
|
||||
ContactKey() = delete;
|
||||
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;
|
||||
void* _b;
|
||||
};
|
||||
|
||||
typedef std::map<ContactKey, ContactInfo> ContactMap;
|
||||
typedef std::pair<ContactKey, ContactInfo> ContactMapElement;
|
||||
|
||||
class PhysicsEngine : public EntitySimulation {
|
||||
public:
|
||||
static uint32_t getFrameCount();
|
||||
// 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();
|
||||
|
@ -51,6 +68,8 @@ public:
|
|||
|
||||
void stepSimulation();
|
||||
|
||||
void computeCollisionEvents();
|
||||
|
||||
/// \param offset position of simulation origin in domain-frame
|
||||
void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; }
|
||||
|
||||
|
@ -68,22 +87,19 @@ public:
|
|||
/// process queue of changed from external sources
|
||||
void relayIncomingChangesToSimulation();
|
||||
|
||||
/// \return duration of fixed simulation substep
|
||||
float getFixedSubStep() const;
|
||||
|
||||
protected:
|
||||
private:
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||
|
||||
btClock _clock;
|
||||
btDefaultCollisionConfiguration* _collisionConfig;
|
||||
btCollisionDispatcher* _collisionDispatcher;
|
||||
btBroadphaseInterface* _broadphaseFilter;
|
||||
btSequentialImpulseConstraintSolver* _constraintSolver;
|
||||
ThreadSafeDynamicsWorld* _dynamicsWorld;
|
||||
btDefaultCollisionConfiguration* _collisionConfig = NULL;
|
||||
btCollisionDispatcher* _collisionDispatcher = NULL;
|
||||
btBroadphaseInterface* _broadphaseFilter = NULL;
|
||||
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
|
||||
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
|
||||
ShapeManager _shapeManager;
|
||||
|
||||
private:
|
||||
glm::vec3 _originOffset;
|
||||
|
||||
// EntitySimulation stuff
|
||||
|
@ -91,7 +107,9 @@ private:
|
|||
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;
|
||||
EntityEditPacketSender* _entityPacketSender = NULL;
|
||||
|
||||
ContactMap _contactMap;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicsEngine_h
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
#include "SimpleEntityKinematicController.h"
|
||||
|
||||
void SimpleEntityKinematicController:: stepForward() {
|
||||
uint32_t frame = PhysicsEngine::getFrameCount();
|
||||
float dt = (frame - _lastFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
uint32_t substep = PhysicsEngine::getNumSubsteps();
|
||||
float dt = (substep - _lastSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
_entity->simulateSimpleKinematicMotion(dt);
|
||||
_lastFrame = frame;
|
||||
_lastSubstep = substep;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue