Merge branch 'master' of https://github.com/highfidelity/hifi into temp0

This commit is contained in:
Sam Gateau 2015-01-21 16:55:25 -08:00
commit ea37d7cfd0
18 changed files with 293 additions and 460 deletions

View file

@ -132,6 +132,35 @@ This package contains only headers, so there's nothing to add to the PATH.
Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.
###Bullet
Bullet 2.82 source can be downloaded [here](https://code.google.com/p/bullet/downloads/detail?name=bullet-2.82-r2704.zip). Bullet does not come with prebuilt libraries, you need to make those yourself.
* Download the zip file and extract into a temporary folder
* Create a directory named cmakebuild. Bullet comes with a build\ directory by default, however, that directory is intended for use with premake, and considering premake doesn't support VS2013, I prefer to run the cmake build on its own directory.
* Make the following modifications to Bullet's source:
1. In file: Extras\HACD\hacdICHull.cpp --- in line: 17 --- insert: #include <algorithm>
2. In file: src\MiniCL\cl_MiniCL_Defs.h --- comment lines 364 to 372
3. In file: CMakeLists.txt set to ON the option USE_MSVC_RUNTIME_LIBRARY_DLL in line 27
Then create the Visual Studio solution and build the libraries - run the following commands from a Visual Studio 2013 command prompt, from within the cmakebuild directory created before:
```shell
cmake .. -G "Visual Studio 12"
msbuild BULLET_PHYSICS.sln /p:Configuration=Debug
```
This will create Debug libraries in cmakebuild\lib\Debug you can replace Debug with Release in the msbuild command and that will generate Release libraries in cmakebuild\lib\Release.
You now have Bullet libraries compiled, now you need to put them in the right place for hifi to find them:
* Create a directory named bullet\ inside your %HIFI_LIB_DIR%
* Create two directores named lib\ and include\ inside bullet\
* Copy all the contents inside src\ from the bullet unzip path into %HIFI_LIB_DIR%\bullet\include\
* Copy all the contents inside cmakebuild\lib\ into %HIFI_LIB_DIR\bullet\lib
_Note that the INSTALL target should handle the copying of files into an install directory automatically, however, without modifications to Cmake, the install target didn't work right for me, please update this instructions if you get that working right - Leo <leo@highfidelity.io>_
###Build High Fidelity using Visual Studio
Follow the same build steps from the CMake section, but pass a different generator to CMake.

View file

@ -8,7 +8,7 @@ body {
background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-family: Arial;
font-size: 11px;
font-size: 8.25pt;
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -31,25 +31,25 @@ body {
.color-box {
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid black;
margin: 2px;
width: 15pt;
height: 15pt;
border: 0.75pt solid black;
margin: 1.5pt;
cursor: pointer;
}
.color-box.highlight {
width: 18px;
height: 18px;
border: 2px solid black;
width: 13.5pt;
height: 13.5pt;
border: 1.5pt solid black;
}
.section-header {
background: #AAA;
border-bottom: 1px solid #CCC;
border-bottom: 0.75pt solid #CCC;
background-color: #333333;
color: #999;
padding: 4px;
padding: 3pt;
}
.section-header label {
@ -61,7 +61,7 @@ body {
.property-section {
display: block;
margin: 10 10;
height: 30px;
height: 22.5pt;
}
.property-section label {
@ -73,7 +73,7 @@ body {
}
.grid-section {
border-top: 1px solid #DDD;
border-top: 0.75pt solid #DDD;
background-color: #efefef;
}
@ -81,8 +81,8 @@ input[type=button] {
cursor: pointer;
background-color: #608e96;
border-color: #608e96;
border-radius: 5px;
padding: 5px 10px;
border-radius: 3.75pt;
padding: 3.75pt 7.5pt;
border: 0;
color: #fff;
font-weight: bold;
@ -90,8 +90,8 @@ input[type=button] {
textarea, input {
margin: 0;
padding: 2px;
border: 1px solid #999;
padding: 1.5pt;
border: 0.75pt solid #999;
background-color: #eee;
}
@ -112,13 +112,13 @@ input.coord {
table#entity-table {
border-collapse: collapse;
font-family: Sans-Serif;
font-size: 10px;
font-size: 7.5pt;
width: 100%;
}
#entity-table tr {
cursor: pointer;
border-bottom: 1px solid rgb(63, 63, 63)
border-bottom: 0.75pt solid rgb(63, 63, 63)
}
#entity-table tr.selected {
@ -128,15 +128,15 @@ table#entity-table {
#entity-table th {
background-color: #333;
color: #fff;
border: 0px black solid;
border: 0pt black solid;
text-align: left;
word-wrap: nowrap;
white-space: nowrap;
}
#entity-table td {
font-size: 11px;
border: 0px black solid;
font-size: 8.25pt;
border: 0pt black solid;
word-wrap: nowrap;
white-space: nowrap;
text-overflow: ellipsis;
@ -148,21 +148,21 @@ table#entity-table {
}
th#entity-type {
width: 60px;
width: 33.75pt;
}
div.input-area {
display: inline-block;
font-size: 10px;
font-size: 7.5pt;
}
input {
}
#type {
font-size: 14px;
font-size: 10.5pt;
}
#type label {
@ -173,22 +173,22 @@ input {
background-color: rgb(102, 102, 102);
color: rgb(204, 204, 204);
border: none;
font-size: 10px;
font-size: 7.5pt;
}
#properties-list input[type=button] {
cursor: pointer;
background-color: rgb(51, 102, 102);
border-color: #608e96;
border-radius: 5px;
padding: 5px 10px;
border-radius: 3.75pt;
padding: 3.75pt 7.5pt;
border: 0;
color: rgb(204, 204, 204);
}
#properties-list .property {
padding: 8px 8px;
border-top: 1px solid rgb(63, 63, 63);
padding: 6pt 6pt;
border-top: 0.75pt solid rgb(63, 63, 63);
min-height: 1em;
}
@ -203,11 +203,11 @@ table#properties-list {
}
#properties-list > div {
margin: 4px 0;
margin: 3pt 0;
}
#properties-list {
border-bottom: 1px solid #e5e5e5;
border-bottom: 0.75pt solid #e5e5e5;
}
#properties-list .label {
@ -221,11 +221,11 @@ table#properties-list {
}
#properties-list .value > div{
padding: 4px 0;
padding: 3pt 0;
}
col#col-label {
width: 130px;
width: 97.5pt;
}
div.outer {

View file

@ -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...
}
{

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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

View 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;
}

View 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

View file

@ -33,6 +33,7 @@ void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
EntityMotionState::EntityMotionState(EntityItem* entity)
: _entity(entity) {
_type = MOTION_STATE_TYPE_ENTITY;
assert(entity != NULL);
}

View file

@ -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;
};

View file

@ -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();
}

View file

@ -30,7 +30,7 @@ public:
protected:
bool _enabled = false;
uint32_t _lastFrame;
uint32_t _lastSubstep;
};
#endif // hifi_KinematicController_h

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}