mirror of
https://github.com/overte-org/overte.git
synced 2025-08-05 00:59:56 +02:00
Merge pull request #9267 from AndrewMeadows/report-all-collision-events
fix for missed START collision events
This commit is contained in:
commit
b597f3e098
11 changed files with 96 additions and 96 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
bool shouldBeLocallyOwned() const override;
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -146,6 +146,8 @@ public:
|
|||
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
|
||||
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
|
||||
|
||||
virtual bool shouldBeLocallyOwned() const { return false; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue