Merge pull request #9267 from AndrewMeadows/report-all-collision-events

fix for missed START collision events
This commit is contained in:
Brad Hefta-Gaub 2017-01-31 08:54:58 -08:00 committed by GitHub
commit b597f3e098
11 changed files with 96 additions and 96 deletions

View file

@ -4377,6 +4377,10 @@ void Application::update(float deltaTime) {
PROFILE_RANGE_EX(simulation_physics, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("harvestChanges");
if (_physicsEngine->hasOutgoingChanges()) {
// grab the collision events BEFORE handleOutgoingChanges() because at this point
// we have a better idea of which objects we own or should own.
auto& collisionEvents = _physicsEngine->getCollisionEvents();
getEntities()->getTree()->withWriteLock([&] {
PerformanceTimer perfTimer("handleOutgoingChanges");
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
@ -4384,11 +4388,10 @@ void Application::update(float deltaTime) {
avatarManager->handleOutgoingChanges(outgoingChanges);
});
auto collisionEvents = _physicsEngine->getCollisionEvents();
avatarManager->handleCollisionEvents(collisionEvents);
if (!_aboutToQuit) {
// handleCollisionEvents() AFTER handleOutgoinChanges()
PerformanceTimer perfTimer("entities");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);

View file

@ -956,68 +956,29 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const
}
}
bool EntityTreeRenderer::isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision) {
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
if (!entity) {
return false;
}
QUuid simulatorID = entity->getSimulatorID();
if (simulatorID.isNull()) {
// Can be null if it has never moved since being created or coming out of persistence.
// However, for there to be a collission, one of the two objects must be moving.
const EntityItemID& otherID = (id == collision.idA) ? collision.idB : collision.idA;
EntityItemPointer otherEntity = entityTree->findEntityByEntityItemID(otherID);
if (!otherEntity) {
return false;
}
simulatorID = otherEntity->getSimulatorID();
}
if (simulatorID.isNull() || (simulatorID != myNodeID)) {
return false;
}
return true;
}
void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision) {
if (!isCollisionOwner(myNodeID, entityTree, id, collision)) {
return;
}
SharedSoundPointer collisionSound;
float mass = 1.0; // value doesn't get used, but set it so compiler is quiet
AACube minAACube;
bool success = false;
_tree->withReadLock([&] {
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
if (entity) {
collisionSound = entity->getCollisionSound();
mass = entity->computeMass();
minAACube = entity->getMinimumAACube(success);
}
});
if (!success) {
return;
}
void EntityTreeRenderer::playEntityCollisionSound(EntityItemPointer entity, const Collision& collision) {
assert((bool)entity);
SharedSoundPointer collisionSound = entity->getCollisionSound();
if (!collisionSound) {
return;
}
bool success = false;
AACube minAACube = entity->getMinimumAACube(success);
if (!success) {
return;
}
float mass = entity->computeMass();
const float COLLISION_PENETRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity()
const float COLLISION_PENETRATION_TO_VELOCITY = 50.0f; // as a subsitute for RELATIVE entity->getVelocity()
// The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact,
// but that first contact depends on exactly where we hit in the physics step.
// We can get a more consistent initial-contact energy reading by using the changed velocity.
// Note that velocityChange is not a good indicator for continuing collisions, because it does not distinguish
// between bounce and sliding along a surface.
const float linearVelocity = (collision.type == CONTACT_EVENT_TYPE_START) ?
glm::length(collision.velocityChange) :
glm::length(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY;
const float energy = mass * linearVelocity * linearVelocity / 2.0f;
const glm::vec3 position = collision.contactPoint;
const float speedSquared = (collision.type == CONTACT_EVENT_TYPE_START) ?
glm::length2(collision.velocityChange) :
glm::length2(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY;
const float energy = mass * speedSquared / 2.0f;
const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 150.0f : 5.0f;
const float COLLISION_MINIMUM_VOLUME = 0.005f;
const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME);
@ -1031,7 +992,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT
// Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2)
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
const float stretchFactor = log(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2);
AudioInjector::playSound(collisionSound, volume, stretchFactor, position);
AudioInjector::playSound(collisionSound, volume, stretchFactor, collision.contactPoint);
}
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
@ -1041,30 +1002,28 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
if (!_tree || _shuttingDown) {
return;
}
// Don't respond to small continuous contacts.
const float COLLISION_MINUMUM_PENETRATION = 0.002f;
if ((collision.type == CONTACT_EVENT_TYPE_CONTINUE) && (glm::length(collision.penetration) < COLLISION_MINUMUM_PENETRATION)) {
return;
}
// See if we should play sounds
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree);
const QUuid& myNodeID = DependencyManager::get<NodeList>()->getSessionUUID();
playEntityCollisionSound(myNodeID, entityTree, idA, collision);
playEntityCollisionSound(myNodeID, entityTree, idB, collision);
// And now the entity scripts
if (isCollisionOwner(myNodeID, entityTree, idA, collision)) {
// trigger scripted collision sounds and events for locally owned objects
EntityItemPointer entityA = entityTree->findEntityByEntityItemID(idA);
if ((bool)entityA && myNodeID == entityA->getSimulatorID()) {
playEntityCollisionSound(entityA, collision);
emit collisionWithEntity(idA, idB, collision);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
}
}
if (isCollisionOwner(myNodeID, entityTree, idB, collision)) {
emit collisionWithEntity(idB, idA, collision);
EntityItemPointer entityB = entityTree->findEntityByEntityItemID(idB);
if ((bool)entityB && myNodeID == entityB->getSimulatorID()) {
playEntityCollisionSound(entityB, collision);
// since we're swapping A and B we need to send the inverted collision
Collision invertedCollision(collision);
invertedCollision.invert();
emit collisionWithEntity(idB, idA, invertedCollision);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
}
}
}

View file

@ -170,11 +170,7 @@ private:
bool _wantScripts;
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
bool isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision);
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision);
static void playEntityCollisionSound(EntityItemPointer entity, const Collision& collision);
bool _lastPointerEventValid;
PointerEvent _lastPointerEvent;

View file

@ -13,15 +13,25 @@
void ContactInfo::update(uint32_t currentStep, const btManifoldPoint& p) {
_lastStep = currentStep;
++_numSteps;
positionWorldOnB = p.m_positionWorldOnB;
normalWorldOnB = p.m_normalWorldOnB;
distance = p.m_distance1;
}
}
const uint32_t STEPS_BETWEEN_CONTINUE_EVENTS = 9;
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
if (_lastStep != thisStep) {
return CONTACT_EVENT_TYPE_END;
if (_continueExpiry == 0) {
_continueExpiry = thisStep + STEPS_BETWEEN_CONTINUE_EVENTS;
return CONTACT_EVENT_TYPE_START;
}
return (_numSteps == 1) ? CONTACT_EVENT_TYPE_START : CONTACT_EVENT_TYPE_CONTINUE;
return (_lastStep == thisStep) ? CONTACT_EVENT_TYPE_CONTINUE : CONTACT_EVENT_TYPE_END;
}
bool ContactInfo::readyForContinue(uint32_t thisStep) {
if (thisStep > _continueExpiry) {
_continueExpiry = thisStep + STEPS_BETWEEN_CONTINUE_EVENTS;
return true;
}
return false;
}

View file

@ -19,20 +19,22 @@
class ContactInfo {
public:
public:
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; }
bool readyForContinue(uint32_t thisStep);
btVector3 positionWorldOnB;
btVector3 normalWorldOnB;
btScalar distance;
private:
uint32_t _lastStep = 0;
uint32_t _numSteps = 0;
};
uint32_t _lastStep { 0 };
uint32_t _continueExpiry { 0 };
};
#endif // hifi_ContactEvent_h

View file

@ -762,6 +762,11 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
_entity->computeCollisionGroupAndFinalMask(group, mask);
}
bool EntityMotionState::shouldBeLocallyOwned() const {
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID();
}
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
}

View file

@ -78,6 +78,8 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
bool shouldBeLocallyOwned() const override;
friend class PhysicalEntitySimulation;
protected:

View file

@ -146,6 +146,8 @@ public:
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
virtual bool shouldBeLocallyOwned() const { return false; }
friend class PhysicsEngine;
protected:

View file

@ -270,7 +270,7 @@ void PhysicsEngine::stepSimulation() {
}
auto onSubStep = [this]() {
updateContactMap();
this->updateContactMap();
};
int numSubsteps = _dynamicsWorld->stepSimulationWithSubstepCallback(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS,
@ -393,7 +393,6 @@ void PhysicsEngine::updateContactMap() {
}
const CollisionEvents& PhysicsEngine::getCollisionEvents() {
const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10;
_collisionEvents.clear();
// scan known contacts and trigger events
@ -402,28 +401,42 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
while (contactItr != _contactMap.end()) {
ContactInfo& contact = contactItr->second;
ContactEventType type = contact.computeType(_numContactFrames);
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) {
const btScalar SIGNIFICANT_DEPTH = -0.002f; // penetrations have negative distance
if (type != CONTACT_EVENT_TYPE_CONTINUE ||
(contact.distance < SIGNIFICANT_DEPTH &&
contact.readyForContinue(_numContactFrames))) {
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(contactItr->first._a);
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(contactItr->first._b);
glm::vec3 velocityChange = (motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f)) +
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
if (motionStateA) {
// NOTE: the MyAvatar RigidBody is the only object in the simulation that does NOT have a MotionState
// which means should we ever want to report ALL collision events against the avatar we can
// modify the logic below.
//
// We only create events when at least one of the objects is (or should be) owned in the local simulation.
if (motionStateA && (motionStateA->shouldBeLocallyOwned())) {
QUuid idA = motionStateA->getObjectID();
QUuid idB;
if (motionStateB) {
idB = motionStateB->getObjectID();
}
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset;
glm::vec3 velocityChange = motionStateA->getObjectLinearVelocityChange() +
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
} else if (motionStateB) {
} else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) {
QUuid idB = motionStateB->getObjectID();
QUuid idA;
if (motionStateA) {
idA = motionStateA->getObjectID();
}
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset;
glm::vec3 velocityChange = motionStateB->getObjectLinearVelocityChange() +
(motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f));
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
// hence we must negate the penetration.
// hence we negate the penetration (because penetration always points from B to A).
glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange));
_collisionEvents.push_back(Collision(type, idB, idA, position, penetration, velocityChange));
}
}

View file

@ -742,6 +742,12 @@ void collisionFromScriptValue(const QScriptValue &object, Collision& collision)
// TODO: implement this when we know what it means to accept collision events from JS
}
void Collision::invert() {
std::swap(idA, idB);
contactPoint += penetration;
penetration *= -1.0f;
}
QScriptValue quuidToScriptValue(QScriptEngine* engine, const QUuid& uuid) {
if (uuid.isNull()) {
return QScriptValue::NullValue;

View file

@ -142,11 +142,13 @@ public:
const glm::vec3& cPenetration, const glm::vec3& velocityChange)
: type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration), velocityChange(velocityChange) { }
void invert(); // swap A and B
ContactEventType type;
QUuid idA;
QUuid idB;
glm::vec3 contactPoint;
glm::vec3 penetration;
glm::vec3 contactPoint; // on B in world-frame
glm::vec3 penetration; // from B towards A in world-frame
glm::vec3 velocityChange;
};
Q_DECLARE_METATYPE(Collision)