mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
414 lines
15 KiB
C++
414 lines
15 KiB
C++
//
|
|
// PhysicsEngine.cpp
|
|
// libraries/physcis/src
|
|
//
|
|
// Created by Andrew Meadows 2014.10.29
|
|
// Copyright 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
|
|
//
|
|
// TODO DONE: make _incomingChanges to be list of MotionState*'s.
|
|
// TODO DONE: make MotionState able to clear incoming flags
|
|
// TODO: make MotionState::setWorldTransform() put itself on _incomingChanges list
|
|
// TODO: make sure incoming and outgoing pipelines are connected
|
|
// TODO: give PhysicsEngine instance an _entityPacketSender
|
|
// TODO: provide some sort of "reliable" send for "stopped" update
|
|
|
|
#include "PhysicsEngine.h"
|
|
#ifdef USE_BULLET_PHYSICS
|
|
|
|
#include "ShapeInfoUtil.h"
|
|
#include "ThreadSafeDynamicsWorld.h"
|
|
|
|
class EntityTree;
|
|
|
|
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
|
: _collisionConfig(NULL),
|
|
_collisionDispatcher(NULL),
|
|
_broadphaseFilter(NULL),
|
|
_constraintSolver(NULL),
|
|
_dynamicsWorld(NULL),
|
|
_originOffset(offset),
|
|
_voxels(),
|
|
_frameCount(0) {
|
|
}
|
|
|
|
PhysicsEngine::~PhysicsEngine() {
|
|
}
|
|
|
|
// begin EntitySimulation overrides
|
|
void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
|
|
QSet<ObjectMotionState*>::iterator stateItr = _outgoingPackets.begin();
|
|
uint32_t frame = getFrameCount();
|
|
float subStepRemainder = getSubStepRemainder();
|
|
while (stateItr != _outgoingPackets.end()) {
|
|
ObjectMotionState* state = *stateItr;
|
|
if (state->shouldSendUpdate(frame, subStepRemainder)) {
|
|
state->sendUpdate(_entityPacketSender);
|
|
++stateItr;
|
|
} else if (state->isAtRest()) {
|
|
stateItr = _outgoingPackets.erase(stateItr);
|
|
} else {
|
|
++stateItr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PhysicsEngine::addEntityInternal(EntityItem* entity) {
|
|
assert(entity);
|
|
void* physicsInfo = entity->getPhysicsInfo();
|
|
if (!physicsInfo) {
|
|
EntityMotionState* motionState = new EntityMotionState(entity);
|
|
_entityMotionStates.insert(motionState);
|
|
if (addObject(motionState)) {
|
|
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
|
} else {
|
|
// We failed to add the object to the simulation. Probably because we couldn't create a shape for it.
|
|
delete motionState;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
|
|
assert(entity);
|
|
void* physicsInfo = entity->getPhysicsInfo();
|
|
if (physicsInfo) {
|
|
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
|
removeObject(motionState);
|
|
entity->setPhysicsInfo(NULL);
|
|
_entityMotionStates.remove(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) {
|
|
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
|
_incomingChanges.insert(motionState);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
removeObject(*stateItr);
|
|
}
|
|
_entityMotionStates.clear();
|
|
_incomingChanges.clear();
|
|
}
|
|
// end EntitySimulation overrides
|
|
|
|
void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|
// process incoming changes
|
|
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
|
|
while (stateItr != _incomingChanges.end()) {
|
|
ObjectMotionState* motionState = *stateItr;
|
|
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
|
|
|
btRigidBody* body = motionState->_body;
|
|
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
|
|
updateObjectHard(body, motionState, flags);
|
|
} else if (flags) {
|
|
// an EASY update does NOT require that the body be pulled out of physics engine
|
|
updateObjectEasy(body, motionState, flags);
|
|
}
|
|
}
|
|
|
|
// NOTE: the order of operations is:
|
|
// (1) relayIncomingChanges()
|
|
// (2) step simulation
|
|
// (3) send outgoing packets
|
|
// This means incoming changes here (step (1)) should trump corresponding outgoing changes
|
|
motionState->clearOutgoingPacketFlags(flags); // trump
|
|
motionState->clearIncomingDirtyFlags(flags); // processed
|
|
++stateItr;
|
|
}
|
|
_incomingChanges.clear();
|
|
}
|
|
|
|
// virtual
|
|
void PhysicsEngine::init() {
|
|
// _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, _entityTree);
|
|
|
|
// 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));
|
|
|
|
// GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects
|
|
btTransform groundTransform;
|
|
groundTransform.setIdentity();
|
|
for (int i = 0; i < 3; ++i) {
|
|
btVector3 normal(0.0f, 0.0f, 0.0f);
|
|
normal[i] = 1.0f;
|
|
btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f);
|
|
|
|
btCollisionObject* groundObject = new btCollisionObject();
|
|
groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
|
groundObject->setCollisionShape(plane);
|
|
|
|
groundObject->setWorldTransform(groundTransform);
|
|
_dynamicsWorld->addCollisionObject(groundObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
const float FIXED_SUBSTEP = 1.0f / 60.0f;
|
|
|
|
void PhysicsEngine::stepSimulation() {
|
|
const int MAX_NUM_SUBSTEPS = 4;
|
|
const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * FIXED_SUBSTEP;
|
|
|
|
float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds());
|
|
_clock.reset();
|
|
float timeStep = btMin(dt, MAX_TIMESTEP);
|
|
// TODO: Andrew to build a list of outgoingChanges when motionStates are synched, then send the updates out
|
|
int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, FIXED_SUBSTEP);
|
|
_frameCount += (uint32_t)numSubSteps;
|
|
}
|
|
|
|
bool PhysicsEngine::addVoxel(const glm::vec3& position, float scale) {
|
|
glm::vec3 halfExtents = glm::vec3(0.5f * scale);
|
|
glm::vec3 trueCenter = position + halfExtents;
|
|
PositionHashKey key(trueCenter);
|
|
VoxelObject* proxy = _voxels.find(key);
|
|
if (!proxy) {
|
|
// create a shape
|
|
ShapeInfo info;
|
|
info.setBox(halfExtents);
|
|
btCollisionShape* shape = _shapeManager.getShape(info);
|
|
|
|
// NOTE: the shape creation will fail when the size of the voxel is out of range
|
|
if (shape) {
|
|
// create a collisionObject
|
|
btCollisionObject* object = new btCollisionObject();
|
|
object->setCollisionShape(shape);
|
|
btTransform transform;
|
|
transform.setIdentity();
|
|
// we shift the center into the simulation's frame
|
|
glm::vec3 shiftedCenter = (position - _originOffset) + halfExtents;
|
|
transform.setOrigin(btVector3(shiftedCenter.x, shiftedCenter.y, shiftedCenter.z));
|
|
object->setWorldTransform(transform);
|
|
|
|
// add to map and world
|
|
_voxels.insert(key, VoxelObject(trueCenter, object));
|
|
_dynamicsWorld->addCollisionObject(object);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PhysicsEngine::removeVoxel(const glm::vec3& position, float scale) {
|
|
glm::vec3 halfExtents = glm::vec3(0.5f * scale);
|
|
glm::vec3 trueCenter = position + halfExtents;
|
|
PositionHashKey key(trueCenter);
|
|
VoxelObject* proxy = _voxels.find(key);
|
|
if (proxy) {
|
|
// remove from world
|
|
assert(proxy->_object);
|
|
_dynamicsWorld->removeCollisionObject(proxy->_object);
|
|
|
|
// release shape
|
|
ShapeInfo info;
|
|
info.setBox(halfExtents);
|
|
bool released = _shapeManager.releaseShape(info);
|
|
assert(released);
|
|
|
|
// delete object and remove from voxel map
|
|
delete proxy->_object;
|
|
_voxels.remove(key);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Bullet collision flags are as follows:
|
|
// CF_STATIC_OBJECT= 1,
|
|
// CF_KINEMATIC_OBJECT= 2,
|
|
// CF_NO_CONTACT_RESPONSE = 4,
|
|
// CF_CUSTOM_MATERIAL_CALLBACK = 8,//this allows per-triangle material (friction/restitution)
|
|
// CF_CHARACTER_OBJECT = 16,
|
|
// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing
|
|
// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing
|
|
|
|
bool PhysicsEngine::addObject(ObjectMotionState* motionState) {
|
|
assert(motionState);
|
|
ShapeInfo info;
|
|
motionState->computeShapeInfo(info);
|
|
btCollisionShape* shape = _shapeManager.getShape(info);
|
|
if (shape) {
|
|
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
|
float mass = 0.0f;
|
|
btRigidBody* body = NULL;
|
|
switch(motionState->computeMotionType()) {
|
|
case MOTION_TYPE_KINEMATIC: {
|
|
body = new btRigidBody(mass, motionState, shape, inertia);
|
|
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
|
body->setActivationState(DISABLE_DEACTIVATION);
|
|
body->updateInertiaTensor();
|
|
motionState->_body = body;
|
|
break;
|
|
}
|
|
case MOTION_TYPE_DYNAMIC: {
|
|
mass = motionState->getMass();
|
|
shape->calculateLocalInertia(mass, inertia);
|
|
body = new btRigidBody(mass, motionState, shape, inertia);
|
|
body->updateInertiaTensor();
|
|
motionState->_body = body;
|
|
motionState->applyVelocities();
|
|
motionState->applyGravity();
|
|
break;
|
|
}
|
|
case MOTION_TYPE_STATIC:
|
|
default: {
|
|
body = new btRigidBody(mass, motionState, shape, inertia);
|
|
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
|
body->updateInertiaTensor();
|
|
motionState->_body = body;
|
|
break;
|
|
}
|
|
}
|
|
// wtf?
|
|
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
|
|
body->setRestitution(motionState->_restitution);
|
|
body->setFriction(motionState->_friction);
|
|
_dynamicsWorld->addRigidBody(body);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
|
assert(motionState);
|
|
btRigidBody* body = motionState->_body;
|
|
if (body) {
|
|
const btCollisionShape* shape = body->getCollisionShape();
|
|
ShapeInfo info;
|
|
ShapeInfoUtil::collectInfoFromShape(shape, info);
|
|
_dynamicsWorld->removeRigidBody(body);
|
|
_shapeManager.releaseShape(info);
|
|
delete body;
|
|
motionState->_body = NULL;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// private
|
|
void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
|
|
MotionType newType = motionState->computeMotionType();
|
|
|
|
// pull body out of physics engine
|
|
_dynamicsWorld->removeRigidBody(body);
|
|
|
|
if (flags & EntityItem::DIRTY_SHAPE) {
|
|
btCollisionShape* oldShape = body->getCollisionShape();
|
|
ShapeInfo info;
|
|
motionState->computeShapeInfo(info);
|
|
btCollisionShape* newShape = _shapeManager.getShape(info);
|
|
if (newShape != oldShape) {
|
|
body->setCollisionShape(newShape);
|
|
_shapeManager.releaseShape(oldShape);
|
|
} else {
|
|
// whoops, shape hasn't changed after all so we must release the reference
|
|
// that was created when looking it up
|
|
_shapeManager.releaseShape(newShape);
|
|
}
|
|
// MASS bit should be set whenever SHAPE is set
|
|
assert(flags & EntityItem::DIRTY_MASS);
|
|
}
|
|
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
|
|
if (easyUpdate) {
|
|
updateObjectEasy(body, motionState, flags);
|
|
}
|
|
|
|
// 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();
|
|
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)
|
|
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
|
float mass = motionState->getMass();
|
|
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
|
|
body->setMassProps(mass, inertia);
|
|
body->updateInertiaTensor();
|
|
}
|
|
body->forceActivationState(ACTIVE_TAG);
|
|
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));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// reinsert body into physics engine
|
|
_dynamicsWorld->addRigidBody(body);
|
|
|
|
body->activate();
|
|
}
|
|
|
|
// private
|
|
void PhysicsEngine::updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
|
|
if (flags & EntityItem::DIRTY_POSITION) {
|
|
btTransform transform;
|
|
motionState->getWorldTransform(transform);
|
|
body->setWorldTransform(transform);
|
|
}
|
|
if (flags & EntityItem::DIRTY_VELOCITY) {
|
|
motionState->applyVelocities();
|
|
motionState->applyGravity();
|
|
}
|
|
body->setRestitution(motionState->_restitution);
|
|
body->setFriction(motionState->_friction);
|
|
|
|
if (flags & EntityItem::DIRTY_MASS) {
|
|
float mass = motionState->getMass();
|
|
btVector3 inertia(0.0f, 0.0f, 0.0f);
|
|
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
|
|
body->setMassProps(mass, inertia);
|
|
body->updateInertiaTensor();
|
|
}
|
|
body->activate();
|
|
|
|
// TODO: support collision groups
|
|
};
|
|
|
|
#endif // USE_BULLET_PHYSICS
|