Merge pull request #4007 from AndrewMeadows/inertia

Bullet physics part 1
This commit is contained in:
Brad Hefta-Gaub 2014-12-31 10:33:00 -08:00
commit 43e6ba77fa
86 changed files with 2904 additions and 113 deletions

View file

@ -0,0 +1,18 @@
#
# IncludeBullet.cmake
#
# 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
#
macro(INCLUDE_BULLET)
find_package(Bullet)
if (BULLET_FOUND)
include_directories("${BULLET_INCLUDE_DIRS}")
if (APPLE OR UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BULLET_PHYSICS -isystem ${BULLET_INCLUDE_DIRS}")
endif ()
endif (BULLET_FOUND)
endmacro(INCLUDE_BULLET)

View file

@ -38,6 +38,7 @@ endif ()
# set up the external glm library
include_glm()
include_bullet()
# create the InterfaceConfig.h file based on GL_HEADERS above
configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceConfig.h")

View file

@ -71,6 +71,7 @@
#include <PacketHeaders.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <PhysicsEngine.h>
#include <ProgramObject.h>
#include <ResourceCache.h>
#include <SoundCache.h>
@ -155,6 +156,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
#ifdef USE_BULLET_PHYSICS
_physicsEngine(glm::vec3(0.0f)),
#endif // USE_BULLET_PHYSICS
_entities(true, this, this),
_entityCollisionSystem(),
_entityClipboardRenderer(false, this, this),
@ -2038,6 +2042,12 @@ void Application::init() {
// save settings when avatar changes
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
#ifdef USE_BULLET_PHYSICS
EntityTree* tree = _entities.getTree();
_physicsEngine.setEntityTree(tree);
tree->setSimulation(&_physicsEngine);
_physicsEngine.init(&_entityEditSender);
#endif // USE_BULLET_PHYSICS
// make sure our texture cache knows about window size changes
DependencyManager::get<TextureCache>()->associateWithWidget(glCanvas.data());
@ -2347,6 +2357,13 @@ void Application::update(float deltaTime) {
updateDialogs(deltaTime); // update various stats dialogs if present
updateCursor(deltaTime); // Handle cursor updates
#ifdef USE_BULLET_PHYSICS
{
PerformanceTimer perfTimer("physics");
_physicsEngine.stepSimulation();
}
#endif // USE_BULLET_PHYSICS
if (!_aboutToQuit) {
PerformanceTimer perfTimer("entities");
// NOTE: the _entities.update() call below will wait for lock
@ -4197,6 +4214,25 @@ void Application::openUrl(const QUrl& url) {
}
}
void Application::updateMyAvatarTransform() {
bumpSettings();
#ifdef USE_BULLET_PHYSICS
const float SIMULATION_OFFSET_QUANTIZATION = 16.0f; // meters
glm::vec3 avatarPosition = _myAvatar->getPosition();
glm::vec3 physicsWorldOffset = _physicsEngine.getOriginOffset();
if (glm::distance(avatarPosition, physicsWorldOffset) > SIMULATION_OFFSET_QUANTIZATION) {
glm::vec3 newOriginOffset = avatarPosition;
int halfExtent = (int)HALF_SIMULATION_EXTENT;
for (int i = 0; i < 3; ++i) {
newOriginOffset[i] = (float)(glm::max(halfExtent,
((int)(avatarPosition[i] / SIMULATION_OFFSET_QUANTIZATION)) * (int)SIMULATION_OFFSET_QUANTIZATION));
}
// TODO: Andrew to replace this with method that actually moves existing object positions in PhysicsEngine
_physicsEngine.setOriginOffset(newOriginOffset);
}
#endif // USE_BULLET_PHYSICS
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed

View file

@ -47,6 +47,7 @@
#include "Menu.h"
#include "MetavoxelSystem.h"
#include "PacketHeaders.h"
#include "Physics.h"
#include "Stars.h"
#include "avatar/Avatar.h"
#include "avatar/AvatarManager.h"
@ -378,6 +379,7 @@ public slots:
void openUrl(const QUrl& url);
void updateMyAvatarTransform();
void bumpSettings() { ++_numChangedSettings; }
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
@ -501,6 +503,10 @@ private:
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
#ifdef USE_BULLET_PHYSICS
PhysicsEngine _physicsEngine;
#endif // USE_BULLET_PHYSICS
EntityTreeRenderer _entities;
EntityCollisionSystem _entityCollisionSystem;
EntityTreeRenderer _entityClipboardRenderer;

View file

@ -41,4 +41,4 @@ void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, fl
void applyDampedSpring(float deltaTime, glm::vec3& velocity, glm::vec3& position, glm::vec3& targetPosition, float k, float damping) {
}
}

View file

@ -5,7 +5,7 @@ setup_hifi_library(Network Script)
include_glm()
link_hifi_libraries(audio shared octree voxels networking physics gpu model fbx)
link_hifi_libraries(audio shared octree voxels networking gpu model fbx)
# call macro to include our dependency includes and bubble them up via a property on our target
include_dependency_includes()

View file

@ -5,7 +5,7 @@ setup_hifi_library(Network Script)
include_glm()
link_hifi_libraries(avatars shared octree gpu model fbx networking animation physics)
link_hifi_libraries(avatars shared octree gpu model fbx networking animation)
# call macro to include our dependency includes and bubble them up via a property on our target
include_dependency_includes()

View file

@ -14,9 +14,9 @@
#include <ByteCountCoding.h>
#include "BoxEntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "BoxEntityItem.h"
EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
@ -95,3 +95,9 @@ void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
}
void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const {
glm::vec3 halfExtents = 0.5f * getDimensionsInMeters();
info.setBox(halfExtents);
}

View file

@ -46,11 +46,13 @@ public:
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
void computeShapeInfo(ShapeInfo& info) const;
protected:
rgbColor _color;
};

View file

@ -92,6 +92,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) {
_lastEditedFromRemote = 0;
_lastEditedFromRemoteInRemoteTime = 0;
_created = UNKNOWN_CREATED_TIME;
_physicsInfo = NULL;
_dirtyFlags = 0;
_changedOnServer = 0;
initFromEntityItemID(entityItemID);
@ -106,12 +107,18 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert
_lastEditedFromRemote = 0;
_lastEditedFromRemoteInRemoteTime = 0;
_created = UNKNOWN_CREATED_TIME;
_physicsInfo = NULL;
_dirtyFlags = 0;
_changedOnServer = 0;
initFromEntityItemID(entityItemID);
setProperties(properties);
}
EntityItem::~EntityItem() {
// be sure to clean up _physicsInfo before calling this dtor
assert(_physicsInfo == NULL);
}
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties;
@ -138,7 +145,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const {
// ALL this fits...
// object ID [16 bytes]
// ByteCountCoded(type code) [~1 byte]
@ -516,7 +522,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript);
READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT, setScript);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint);
READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
@ -583,6 +589,11 @@ bool EntityItem::isRestingOnSurface() const {
}
void EntityItem::simulate(const quint64& now) {
if (_physicsInfo) {
// we rely on bullet for simulation, so bail
return;
}
bool wantDebug = false;
if (_lastSimulated == 0) {
@ -795,7 +806,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, updateScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, updateAngularVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
@ -977,15 +988,29 @@ float EntityItem::getRadius() const {
return radius;
}
void EntityItem::computeShapeInfo(ShapeInfo& info) const {
info.clear();
}
void EntityItem::recalculateCollisionShape() {
AACube entityAACube = getMinimumAACube();
entityAACube.scale(TREE_SCALE); // scale to meters
_collisionShape.setTranslation(entityAACube.calcCenter());
_collisionShape.setScale(entityAACube.getScale());
// TODO: use motionState to update physics object
}
const float MIN_POSITION_DELTA = 0.0001f;
const float MIN_DIMENSION_DELTA = 0.0001f;
const float MIN_ALIGNMENT_DOT = 0.9999f;
const float MIN_MASS_DELTA = 0.001f;
const float MIN_VELOCITY_DELTA = 0.025f;
const float MIN_GRAVITY_DELTA = 0.001f;
const float MIN_SPIN_DELTA = 0.0003f;
void EntityItem::updatePosition(const glm::vec3& value) {
if (_position != value) {
glm::vec3 debugPosition = value * (float) TREE_SCALE;
if (glm::distance(_position, value) * (float)TREE_SCALE > MIN_POSITION_DELTA) {
_position = value;
recalculateCollisionShape();
_dirtyFlags |= EntityItem::DIRTY_POSITION;
@ -994,7 +1019,7 @@ void EntityItem::updatePosition(const glm::vec3& value) {
void EntityItem::updatePositionInMeters(const glm::vec3& value) {
glm::vec3 position = glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f);
if (_position != position) {
if (glm::distance(_position, position) * (float)TREE_SCALE > MIN_POSITION_DELTA) {
_position = position;
recalculateCollisionShape();
_dirtyFlags |= EntityItem::DIRTY_POSITION;
@ -1002,24 +1027,24 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) {
}
void EntityItem::updateDimensions(const glm::vec3& value) {
if (_dimensions != value) {
if (glm::distance(_dimensions, value) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) {
_dimensions = value;
recalculateCollisionShape();
_dirtyFlags |= EntityItem::DIRTY_SHAPE;
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
void EntityItem::updateDimensionsInMeters(const glm::vec3& value) {
glm::vec3 dimensions = value / (float) TREE_SCALE;
if (_dimensions != dimensions) {
if (glm::distance(_dimensions, dimensions) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) {
_dimensions = dimensions;
recalculateCollisionShape();
_dirtyFlags |= EntityItem::DIRTY_SHAPE;
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
void EntityItem::updateRotation(const glm::quat& rotation) {
if (_rotation != rotation) {
if (glm::dot(_rotation, rotation) < MIN_ALIGNMENT_DOT) {
_rotation = rotation;
recalculateCollisionShape();
_dirtyFlags |= EntityItem::DIRTY_POSITION;
@ -1027,29 +1052,37 @@ void EntityItem::updateRotation(const glm::quat& rotation) {
}
void EntityItem::updateMass(float value) {
if (_mass != value) {
if (fabsf(_mass - value) > MIN_MASS_DELTA) {
_mass = value;
_dirtyFlags |= EntityItem::DIRTY_MASS;
}
}
void EntityItem::updateVelocity(const glm::vec3& value) {
if (_velocity != value) {
_velocity = value;
void EntityItem::updateVelocity(const glm::vec3& value) {
if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) {
if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) {
_velocity = glm::vec3(0.0f);
} else {
_velocity = value;
}
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
}
void EntityItem::updateVelocityInMeters(const glm::vec3& value) {
glm::vec3 velocity = value / (float) TREE_SCALE;
if (_velocity != velocity) {
_velocity = velocity;
if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) {
if (glm::length(value) < MIN_VELOCITY_DELTA) {
_velocity = glm::vec3(0.0f);
} else {
_velocity = velocity;
}
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
}
void EntityItem::updateGravity(const glm::vec3& value) {
if (_gravity != value) {
if (glm::distance(_gravity, value) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) {
_gravity = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
@ -1057,14 +1090,14 @@ void EntityItem::updateGravity(const glm::vec3& value) {
void EntityItem::updateGravityInMeters(const glm::vec3& value) {
glm::vec3 gravity = value / (float) TREE_SCALE;
if (_gravity != gravity) {
if ( glm::distance(_gravity, gravity) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) {
_gravity = gravity;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
}
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
if (_angularVelocity != value) {
if (glm::distance(_angularVelocity, value) > MIN_SPIN_DELTA) {
_angularVelocity = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
@ -1091,10 +1124,3 @@ void EntityItem::updateLifetime(float value) {
}
}
void EntityItem::updateScript(const QString& value) {
if (_script != value) {
_script = value;
_dirtyFlags |= EntityItem::DIRTY_SCRIPT;
}
}

View file

@ -22,6 +22,7 @@
#include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // for OctreeElement::AppendState
#include <OctreePacketData.h>
#include <ShapeInfo.h>
#include <VoxelDetail.h>
#include "EntityItemID.h"
@ -50,16 +51,13 @@ public:
DIRTY_SHAPE = 0x0020,
DIRTY_LIFETIME = 0x0040,
DIRTY_UPDATEABLE = 0x0080,
// add new simulation-relevant flags above
// all other flags below
DIRTY_SCRIPT = 0x8000
};
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
EntityItem(const EntityItemID& entityItemID);
EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual ~EntityItem() { }
virtual ~EntityItem();
// ID and EntityItemID related methods
QUuid getID() const { return _id; }
@ -276,6 +274,7 @@ public:
void applyHardCollision(const CollisionInfo& collisionInfo);
virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; }
virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); }
virtual void computeShapeInfo(ShapeInfo& info) const;
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
void updatePosition(const glm::vec3& value);
@ -292,13 +291,15 @@ public:
void updateIgnoreForCollisions(bool value);
void updateCollisionsWillMove(bool value);
void updateLifetime(float value);
void updateScript(const QString& value);
uint32_t getDirtyFlags() const { return _dirtyFlags; }
void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; }
bool isMoving() const;
void* getPhysicsInfo() const { return _physicsInfo; }
void setPhysicsInfo(void* data) { _physicsInfo = data; }
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
@ -310,9 +311,9 @@ protected:
bool _newlyCreated;
quint64 _lastSimulated; // last time this entity called simulate()
quint64 _lastUpdated; // last time this entity called update()
quint64 _lastEdited; // this is the last official local or remote edit time
quint64 _lastEditedFromRemote; // this is the last time we received and edit from the server
quint64 _lastEditedFromRemoteInRemoteTime; // time in server time space the last time we received and edit from the server
quint64 _lastEdited; // last official local or remote edit time
quint64 _lastEditedFromRemote; // last time we received and edit from the server
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame)
quint64 _created;
quint64 _changedOnServer;
@ -344,6 +345,10 @@ protected:
AACubeShape _collisionShape;
// _physicsInfo is a hook reserved for use by the EntitySimulation, which is guaranteed to set _physicsInfo
// to a non-NULL value when the EntityItem has a representation in the physics engine.
void* _physicsInfo; // only set by EntitySimulation
// DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about.
uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation
};

View file

@ -134,6 +134,13 @@ public:
void debugDump() const;
void setLastEdited(quint64 usecTime);
// Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type) creates the following methods and variables:
// type getFoo() const;
// void setFoo(type);
// bool fooChanged() const;
// type _foo;
// bool _fooChanged;
DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool);
DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3);
DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3);

View file

@ -101,12 +101,13 @@ void EntitySimulation::sortEntitiesThatMoved() {
}
++itemItr;
}
_entitiesToBeSorted.clear();
if (moveOperator.hasMovingEntities()) {
PerformanceTimer perfTimer("recurseTreeWithOperator");
_entityTree->recurseTreeWithOperator(&moveOperator);
}
sortEntitiesThatMovedInternal();
_entitiesToBeSorted.clear();
}
void EntitySimulation::addEntity(EntityItem* entity) {
@ -122,6 +123,10 @@ void EntitySimulation::addEntity(EntityItem* entity) {
_updateableEntities.insert(entity);
}
addEntityInternal(entity);
// DirtyFlags are used to signal changes to entities that have already been added,
// so we can clear them for this entity which has just been added.
entity->clearDirtyFlags();
}
void EntitySimulation::removeEntity(EntityItem* entity) {
@ -173,7 +178,6 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
}
entityChangedInternal(entity);
}
entity->clearDirtyFlags();
}
void EntitySimulation::clearEntities() {

View file

@ -16,8 +16,21 @@
#include <PerfStat.h>
#include "EntityItem.h"
#include "EntityTree.h"
// the EntitySimulation needs to know when these things change on an entity,
// so it can sort EntityItem or relay its state to the PhysicsEngine.
const int DIRTY_SIMULATION_FLAGS =
EntityItem::DIRTY_POSITION |
EntityItem::DIRTY_VELOCITY |
EntityItem::DIRTY_MASS |
EntityItem::DIRTY_COLLISION_GROUP |
EntityItem::DIRTY_MOTION_TYPE |
EntityItem::DIRTY_SHAPE |
EntityItem::DIRTY_LIFETIME |
EntityItem::DIRTY_UPDATEABLE;
class EntitySimulation {
public:
EntitySimulation() : _entityTree(NULL) { }
@ -49,10 +62,18 @@ protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places.
// NOTE: updateEntitiesInternal() should clear all dirty flags on each changed entity as side effect
virtual void updateEntitiesInternal(const quint64& now) = 0;
virtual void addEntityInternal(EntityItem* entity) = 0;
virtual void removeEntityInternal(EntityItem* entity) = 0;
virtual void entityChangedInternal(EntityItem* entity) = 0;
virtual void sortEntitiesThatMovedInternal() {}
virtual void clearEntitiesInternal() = 0;
void expireMortalEntities(const quint64& now);

View file

@ -132,19 +132,21 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
}
}
} else {
QString entityScriptBefore = entity->getScript();
uint32_t preFlags = entity->getDirtyFlags();
UpdateEntityOperator theOperator(this, containingElement, entity, properties);
recurseTreeWithOperator(&theOperator);
_isDirty = true;
if (_simulation && entity->getDirtyFlags() != 0) {
_simulation->entityChanged(entity);
}
QString entityScriptAfter = entity->getScript();
if (entityScriptBefore != entityScriptAfter) {
emit entityScriptChanging(entity->getEntityItemID()); // the entity script has changed
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
if (newFlags) {
if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->entityChanged(entity);
}
} else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
entity->clearDirtyFlags();
}
}
}

View file

@ -14,7 +14,6 @@
#include "EntityItem.h"
#include "SimpleEntitySimulation.h"
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
QSet<EntityItem*>::iterator itemItr = _movingEntities.begin();
while (itemItr != _movingEntities.end()) {
@ -57,6 +56,7 @@ void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) {
_movableButStoppedEntities.remove(entity);
}
}
entity->clearDirtyFlags();
}
void SimpleEntitySimulation::clearEntitiesInternal() {

View file

@ -97,6 +97,12 @@ void SphereEntityItem::recalculateCollisionShape() {
_sphereShape.setRadius(largestDiameter / 2.0f);
}
void SphereEntityItem::computeShapeInfo(ShapeInfo& info) const {
glm::vec3 halfExtents = 0.5f * getDimensionsInMeters();
// TODO: support ellipsoid shapes
info.setSphere(halfExtents.x);
}
bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const {
@ -122,6 +128,3 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
}
return false;
}

View file

@ -55,6 +55,8 @@ public:
// TODO: implement proper contains for 3D ellipsoid
//virtual bool contains(const glm::vec3& point) const;
void computeShapeInfo(ShapeInfo& info) const;
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,

View file

@ -5,7 +5,7 @@ setup_hifi_library()
include_glm()
link_hifi_libraries(shared networking physics)
link_hifi_libraries(shared networking)
# find ZLIB
find_package(ZLIB REQUIRED)

View file

@ -4,16 +4,13 @@ set(TARGET_NAME physics)
setup_hifi_library()
include_glm()
include_bullet()
if (BULLET_FOUND)
target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
endif (BULLET_FOUND)
link_hifi_libraries(shared)
## find BULLET
#find_package(BULLET REQUIRED)
#
#include_directories(SYSTEM "${BULLET_INCLUDE_DIRS}")
#
## append BULLET to our list of libraries to link
#list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${BULLET_LIBRARIES}")
link_hifi_libraries(shared fbx entities)
include_hifi_library_headers(fbx)
# call macro to include our dependency includes and bubble them up via a property on our target
include_dependency_includes()

View file

@ -0,0 +1,38 @@
//
// BulletUtil.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.02
// 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
//
#ifndef hifi_BulletUtil_h
#define hifi_BulletUtil_h
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
inline glm::vec3 bulletToGLM(const btVector3& b) {
return glm::vec3(b.getX(), b.getY(), b.getZ());
}
inline glm::quat bulletToGLM(const btQuaternion& b) {
return glm::quat(b.getW(), b.getX(), b.getY(), b.getZ());
}
inline btVector3 glmToBullet(const glm::vec3& g) {
return btVector3(g.x, g.y, g.z);
}
inline btQuaternion glmToBullet(const glm::quat& g) {
return btQuaternion(g.x, g.y, g.z, g.w);
}
#endif // USE_BULLET_PHYSICS
#endif // hifi_BulletUtil_h

View file

@ -1,6 +1,6 @@
//
// Constraint.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// ContactConstraint.cpp
// interface/src/avatar
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// ContactConstraint.h
// interface/src/avatar
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// ContactPoint.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.30
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// ContactPoint.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.30
// Copyright 2014 High Fidelity, Inc.
@ -15,7 +15,8 @@
#include <QtGlobal>
#include <glm/glm.hpp>
#include "CollisionInfo.h"
#include <CollisionInfo.h>
#include "VerletPoint.h"
class Shape;

View file

@ -1,6 +1,6 @@
//
// DistanceConstraint.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// DistanceConstraint.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -0,0 +1,43 @@
//
// DoubleHashKey.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.02
// 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
//
#include "DoubleHashKey.h"
const int NUM_PRIMES = 64;
const unsigned int PRIMES[] = {
4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U,
4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U,
4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U,
4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U,
4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U,
4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U,
4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U,
4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U
};
unsigned int DoubleHashKey::hashFunction(unsigned int value, int primeIndex) {
unsigned int hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U);
hash += ~(hash << 15);
hash ^= (hash >> 10);
hash += (hash << 3);
hash ^= (hash >> 6);
hash += ~(hash << 11);
return hash ^ (hash >> 16);
}
unsigned int DoubleHashKey::hashFunction2(unsigned int value) {
unsigned hash = 0x811c9dc5U;
for (int i = 0; i < 4; i++ ) {
unsigned int byte = (value << (i * 8)) >> (24 - i * 8);
hash = ( hash ^ byte ) * 0x01000193U;
}
return hash;
}

View file

@ -0,0 +1,38 @@
//
// DoubleHashKey.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.02
// 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
//
#ifndef hifi_DoubleHashKey_h
#define hifi_DoubleHashKey_h
// DoubleHashKey for use with btHashMap
class DoubleHashKey {
public:
static unsigned int hashFunction(unsigned int value, int primeIndex);
static unsigned int hashFunction2(unsigned int value);
DoubleHashKey() : _hash(0), _hash2(0) { }
DoubleHashKey(unsigned int value, int primeIndex = 0) :
_hash(hashFunction(value, primeIndex)),
_hash2(hashFunction2(value)) {
}
bool equals(const DoubleHashKey& other) const {
return _hash == other._hash && _hash2 == other._hash2;
}
unsigned int getHash() const { return (unsigned int)_hash; }
int _hash;
int _hash2;
};
#endif // hifi_DoubleHashKey_h

View file

@ -0,0 +1,172 @@
//
// EntityMotionState.cpp
// libraries/entities/src
//
// Created by Andrew Meadows on 2014.11.06
// Copyright 2013 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 <EntityItem.h>
#include <EntityEditPacketSender.h>
#ifdef USE_BULLET_PHYSICS
#include "BulletUtil.h"
#endif // USE_BULLET_PHYSICS
#include "EntityMotionState.h"
QSet<EntityItem*>* _outgoingEntityList;
// static
void EntityMotionState::setOutgoingEntityList(QSet<EntityItem*>* list) {
assert(list);
_outgoingEntityList = list;
}
// static
void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) {
assert(_outgoingEntityList);
_outgoingEntityList->insert(entity);
}
EntityMotionState::EntityMotionState(EntityItem* entity)
: _entity(entity) {
assert(entity != NULL);
}
EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity->setPhysicsInfo(NULL);
_entity = NULL;
}
MotionType EntityMotionState::computeMotionType() const {
// HACK: According to EntityTree the meaning of "static" is "not moving" whereas
// to Bullet it means "can't move". For demo purposes we temporarily interpret
// Entity::weightless to mean Bullet::static.
return _entity->getCollisionsWillMove() ? MOTION_TYPE_DYNAMIC : MOTION_TYPE_STATIC;
}
#ifdef USE_BULLET_PHYSICS
// This callback is invoked by the physics simulation in two cases:
// (1) when the RigidBody is first added to the world
// (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC)
// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's --
// it is an opportunity for outside code to update the object's simulation position
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
}
// This callback is invoked by the physics simulation at the end of each simulation frame...
// iff the corresponding RigidBody is DYNAMIC and has moved.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
_entity->setPositionInMeters(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset());
_entity->setRotation(bulletToGLM(worldTrans.getRotation()));
glm::vec3 v;
getVelocity(v);
_entity->setVelocityInMeters(v);
getAngularVelocity(v);
_entity->setAngularVelocity(v);
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
EntityMotionState::enqueueOutgoingEntity(_entity);
}
#endif // USE_BULLET_PHYSICS
void EntityMotionState::applyVelocities() const {
#ifdef USE_BULLET_PHYSICS
if (_body) {
setVelocity(_entity->getVelocityInMeters());
setAngularVelocity(_entity->getAngularVelocity());
_body->setActivationState(ACTIVE_TAG);
}
#endif // USE_BULLET_PHYSICS
}
void EntityMotionState::applyGravity() const {
#ifdef USE_BULLET_PHYSICS
if (_body) {
setGravity(_entity->getGravityInMeters());
_body->setActivationState(ACTIVE_TAG);
}
#endif // USE_BULLET_PHYSICS
}
void EntityMotionState::computeShapeInfo(ShapeInfo& info) {
_entity->computeShapeInfo(info);
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) {
#ifdef USE_BULLET_PHYSICS
if (_outgoingPacketFlags) {
EntityItemProperties properties = _entity->getProperties();
if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) {
btTransform worldTrans = _body->getWorldTransform();
_sentPosition = bulletToGLM(worldTrans.getOrigin());
properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset());
_sentRotation = bulletToGLM(worldTrans.getRotation());
properties.setRotation(_sentRotation);
}
if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) {
if (_body->isActive()) {
_sentVelocity = bulletToGLM(_body->getLinearVelocity());
_sentAngularVelocity = bulletToGLM(_body->getAngularVelocity());
// if the speeds are very small we zero them out
const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 4.0e-6f; // 2mm/sec
bool zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED);
if (zeroSpeed) {
_sentVelocity = glm::vec3(0.0f);
}
const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec
bool zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED;
if (zeroSpin) {
_sentAngularVelocity = glm::vec3(0.0f);
}
_sentMoving = ! (zeroSpeed && zeroSpin);
} else {
_sentVelocity = _sentAngularVelocity = glm::vec3(0.0f);
_sentMoving = false;
}
properties.setVelocity(_sentVelocity);
_sentAcceleration = bulletToGLM(_body->getGravity());
properties.setGravity(_sentAcceleration);
properties.setAngularVelocity(_sentAngularVelocity);
}
// RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit.
if (_sentMoving) {
_numNonMovingUpdates = 0;
} else {
_numNonMovingUpdates++;
}
if (_numNonMovingUpdates <= 1) {
// we only update lastEdited when we're sending new physics data
// (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data)
quint64 lastSimulated = _entity->getLastSimulated();
_entity->setLastEdited(lastSimulated);
properties.setLastEdited(lastSimulated);
} else {
properties.setLastEdited(_entity->getLastEdited());
}
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
// The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them
// to the full set. These flags may be momentarily cleared by incoming external changes.
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
_sentFrame = frame;
}
#endif // USE_BULLET_PHYSICS
}

View file

@ -0,0 +1,71 @@
//
// EntityMotionState.h
// libraries/entities/src
//
// Created by Andrew Meadows on 2014.11.06
// Copyright 2013 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_EntityMotionState_h
#define hifi_EntityMotionState_h
#include <AACube.h>
#include "ObjectMotionState.h"
#ifndef USE_BULLET_PHYSICS
// ObjectMotionState stubbery
class ObjectMotionState {
public:
// so that this stub implementation is not completely empty we give the class a data member
bool _stubData;
};
#endif // USE_BULLET_PHYSICS
class EntityItem;
// From the MotionState's perspective:
// Inside = physics simulation
// Outside = external agents (scripts, user interaction, other simulations)
class EntityMotionState : public ObjectMotionState {
public:
// The OutgoingEntityQueue is a pointer to a QSet (owned by an EntitySimulation) of EntityItem*'s
// that have been changed by the physics simulation.
// All ObjectMotionState's with outgoing changes put themselves on the list.
static void setOutgoingEntityList(QSet<EntityItem*>* list);
static void enqueueOutgoingEntity(EntityItem* entity);
EntityMotionState(EntityItem* item);
virtual ~EntityMotionState();
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
MotionType computeMotionType() const;
#ifdef USE_BULLET_PHYSICS
// this relays incoming position/rotation to the RigidBody
void getWorldTransform(btTransform& worldTrans) const;
// this relays outgoing position/rotation to the EntityItem
void setWorldTransform(const btTransform& worldTrans);
#endif // USE_BULLET_PHYSICS
// these relay incoming values to the RigidBody
void applyVelocities() const;
void applyGravity() const;
void computeShapeInfo(ShapeInfo& info);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame);
uint32_t getIncomingDirtyFlags() const { return _entity->getDirtyFlags(); }
void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); }
protected:
EntityItem* _entity;
};
#endif // hifi_EntityMotionState_h

View file

@ -1,6 +1,6 @@
//
// FixedConstraint.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// FixedConstraint.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -0,0 +1,173 @@
//
// ObjectMotionState.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.05
// 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
//
#ifdef USE_BULLET_PHYSICS
#include <math.h>
#include "BulletUtil.h"
#include "ObjectMotionState.h"
const float MIN_DENSITY = 200.0f;
const float DEFAULT_DENSITY = 1000.0f;
const float MAX_DENSITY = 20000.0f;
const float MIN_VOLUME = 0.001f;
const float DEFAULT_VOLUME = 1.0f;
const float MAX_VOLUME = 1000000.0f;
const float DEFAULT_FRICTION = 0.5f;
const float MAX_FRICTION = 10.0f;
const float DEFAULT_RESTITUTION = 0.0f;
// origin of physics simulation in world frame
glm::vec3 _worldOffset(0.0f);
// static
void ObjectMotionState::setWorldOffset(const glm::vec3& offset) {
_worldOffset = offset;
}
// static
const glm::vec3& ObjectMotionState::getWorldOffset() {
return _worldOffset;
}
ObjectMotionState::ObjectMotionState() :
_density(DEFAULT_DENSITY),
_volume(DEFAULT_VOLUME),
_friction(DEFAULT_FRICTION),
_restitution(DEFAULT_RESTITUTION),
_wasInWorld(false),
_motionType(MOTION_TYPE_STATIC),
_body(NULL),
_sentMoving(false),
_numNonMovingUpdates(0),
_outgoingPacketFlags(DIRTY_PHYSICS_FLAGS),
_sentFrame(0),
_sentPosition(0.0f),
_sentRotation(),
_sentVelocity(0.0f),
_sentAngularVelocity(0.0f),
_sentAcceleration(0.0f) {
}
ObjectMotionState::~ObjectMotionState() {
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
assert(_body == NULL);
}
void ObjectMotionState::setDensity(float density) {
_density = btMax(btMin(fabsf(density), MAX_DENSITY), MIN_DENSITY);
}
void ObjectMotionState::setFriction(float friction) {
_friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f);
}
void ObjectMotionState::setRestitution(float restitution) {
_restitution = btMax(btMin(fabsf(restitution), 1.0f), 0.0f);
}
void ObjectMotionState::setVolume(float volume) {
_volume = btMax(btMin(fabsf(volume), MAX_VOLUME), MIN_VOLUME);
}
void ObjectMotionState::setVelocity(const glm::vec3& velocity) const {
_body->setLinearVelocity(glmToBullet(velocity));
}
void ObjectMotionState::setAngularVelocity(const glm::vec3& velocity) const {
_body->setAngularVelocity(glmToBullet(velocity));
}
void ObjectMotionState::setGravity(const glm::vec3& gravity) const {
_body->setGravity(glmToBullet(gravity));
}
void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const {
velocityOut = bulletToGLM(_body->getLinearVelocity());
}
void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const {
angularVelocityOut = bulletToGLM(_body->getAngularVelocity());
}
// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates
// we alwasy resend packets for objects that have stopped moving up to some max limit.
const int MAX_NUM_NON_MOVING_UPDATES = 5;
bool ObjectMotionState::doesNotNeedToSendUpdate() const {
return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES;
}
const float FIXED_SUBSTEP = 1.0f / 60.0f;
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) const {
assert(_body);
float dt = (float)(simulationFrame - _sentFrame) * FIXED_SUBSTEP + subStepRemainder;
bool isActive = _body->isActive();
if (isActive) {
const float MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS = 10.0f;
if (dt > MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS) {
return true;
}
} else if (_sentMoving) {
if (!isActive) {
// this object just went inactive so send an update immediately
return true;
}
} else {
const float NON_MOVING_UPDATE_PERIOD = 1.0f;
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
// at a faster rate than the MAX period above, and only send a limited number of them.
return true;
}
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
// of remote EntitySimulation) and return true if the error is significant.
// NOTE: math in done the simulation-frame, which is NOT necessarily the same as the world-frame
// due to _worldOffset.
// TODO: Andrew to reconcile Bullet and legacy damping coefficients.
// compute position error
glm::vec3 extrapolatedPosition = _sentPosition + dt * (_sentVelocity + (0.5f * dt) * _sentAcceleration);
btTransform worldTrans = _body->getWorldTransform();
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
float dx2 = glm::distance2(position, extrapolatedPosition);
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
return true;
}
// compute rotation error
float spin = glm::length(_sentAngularVelocity);
glm::quat extrapolatedRotation = _sentRotation;
const float MIN_SPIN = 1.0e-4f;
if (spin > MIN_SPIN) {
glm::vec3 axis = _sentAngularVelocity / spin;
extrapolatedRotation = glm::angleAxis(dt * spin, axis) * _sentRotation;
}
const float MIN_ROTATION_DOT = 0.98f;
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
return (glm::dot(actualRotation, extrapolatedRotation) < MIN_ROTATION_DOT);
}
#endif // USE_BULLET_PHYSICS

View file

@ -0,0 +1,115 @@
//
// ObjectMotionState.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.05
// 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
//
#ifndef hifi_ObjectMotionState_h
#define hifi_ObjectMotionState_h
#include <EntityItem.h>
enum MotionType {
MOTION_TYPE_STATIC, // no motion
MOTION_TYPE_DYNAMIC, // motion according to physical laws
MOTION_TYPE_KINEMATIC // keyframed motion
};
// 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.
typedef unsigned int uint32_t;
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY |
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP);
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS;
// These are the outgoing flags that the PhysicsEngine can affect:
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY;
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
#include <EntityItem.h> // for EntityItem::DIRTY_FOO bitmasks
#include "ShapeInfo.h"
class OctreeEditPacketSender;
class ObjectMotionState : public btMotionState {
public:
// The WorldOffset is used to keep the positions of objects in the simulation near the origin, to
// reduce numerical error when computing vector differences. In other words: The EntityMotionState
// class translates between the simulation-frame and the world-frame as known by the render pipeline,
// various object trees, etc. The EntityMotionState class uses a static "worldOffset" to help in
// the translations.
static void setWorldOffset(const glm::vec3& offset);
static const glm::vec3& getWorldOffset();
ObjectMotionState();
~ObjectMotionState();
virtual void applyVelocities() const = 0;
virtual void applyGravity() const = 0;
virtual void computeShapeInfo(ShapeInfo& info) = 0;
virtual MotionType getMotionType() const { return _motionType; }
void setDensity(float density);
void setFriction(float friction);
void setRestitution(float restitution);
void setVolume(float volume);
float getMass() const { return _volume * _density; }
void setVelocity(const glm::vec3& velocity) const;
void setAngularVelocity(const glm::vec3& velocity) const;
void setGravity(const glm::vec3& gravity) const;
void getVelocity(glm::vec3& velocityOut) const;
void getAngularVelocity(glm::vec3& angularVelocityOut) const;
virtual uint32_t getIncomingDirtyFlags() const = 0;
virtual void clearIncomingDirtyFlags(uint32_t flags) = 0;
void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; }
bool doesNotNeedToSendUpdate() const;
virtual bool shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) const;
virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0;
virtual MotionType computeMotionType() const = 0;
friend class PhysicsEngine;
protected:
float _density;
float _volume;
float _friction;
float _restitution;
bool _wasInWorld;
MotionType _motionType;
// _body has NO setters -- it is only changed by PhysicsEngine
btRigidBody* _body;
bool _sentMoving; // true if last update was moving
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
uint32_t _outgoingPacketFlags;
uint32_t _sentFrame;
glm::vec3 _sentPosition; // in simulation-frame (not world-frame)
glm::quat _sentRotation;;
glm::vec3 _sentVelocity;
glm::vec3 _sentAngularVelocity;
glm::vec3 _sentAcceleration;
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_ObjectMotionState_h

View file

@ -0,0 +1,454 @@
//
// 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
//
#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(),
_entityPacketSender(NULL),
_frameCount(0) {
}
PhysicsEngine::~PhysicsEngine() {
}
// begin EntitySimulation overrides
void PhysicsEngine::updateEntitiesInternal(const quint64& now) {
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// this is step (4)
QSet<ObjectMotionState*>::iterator stateItr = _outgoingPackets.begin();
uint32_t frame = getFrameCount();
float subStepRemainder = getSubStepRemainder();
while (stateItr != _outgoingPackets.end()) {
ObjectMotionState* state = *stateItr;
if (state->doesNotNeedToSendUpdate()) {
stateItr = _outgoingPackets.erase(stateItr);
} else if (state->shouldSendUpdate(frame, subStepRemainder)) {
state->sendUpdate(_entityPacketSender, frame);
++stateItr;
} else {
++stateItr;
}
}
}
void PhysicsEngine::addEntityInternal(EntityItem* entity) {
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (!physicsInfo) {
EntityMotionState* motionState = new EntityMotionState(entity);
if (addObject(motionState)) {
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
} else {
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
delete motionState;
}
}
}
void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (physicsInfo) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
removeObject(motionState);
_entityMotionStates.remove(motionState);
_incomingChanges.remove(motionState);
_outgoingPackets.remove(motionState);
delete 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);
} else {
// try to add this entity again (maybe something changed such that it will work this time)
addEntity(entity);
}
}
void PhysicsEngine::sortEntitiesThatMovedInternal() {
// entities that have been simulated forward (hence in the _entitiesToBeSorted list)
// also need to be put in the outgoingPackets list
QSet<EntityItem*>::iterator entityItr = _entitiesToBeSorted.begin();
while (entityItr != _entitiesToBeSorted.end()) {
EntityItem* entity = *entityItr;
void* physicsInfo = entity->getPhysicsInfo();
assert(physicsInfo);
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
_outgoingPackets.insert(motionState);
++entityItr;
}
}
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);
delete (*stateItr);
}
_entityMotionStates.clear();
_incomingChanges.clear();
_outgoingPackets.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 grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
//
// We're in the middle of step (1) hence incoming changes should trump corresponding
// outgoing changes at this point.
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
++stateItr;
}
_incomingChanges.clear();
}
// virtual
void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
// _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);
}
}
assert(packetSender);
_entityPacketSender = packetSender;
EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted);
}
const float FIXED_SUBSTEP = 1.0f / 60.0f;
void PhysicsEngine::stepSimulation() {
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// this is step (1)
relayIncomingChangesToSimulation();
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);
// steps (2) and (3) are performed by ThreadSafeDynamicsWorld::stepSimulation())
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

View file

@ -0,0 +1,123 @@
//
// PhysicsEngine.h
// 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
//
#ifndef hifi_PhysicsEngine_h
#define hifi_PhysicsEngine_h
typedef unsigned int uint32_t;
#ifdef USE_BULLET_PHYSICS
#include <QSet>
#include <btBulletDynamicsCommon.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
#include "BulletUtil.h"
#include "EntityMotionState.h"
#include "PositionHashKey.h"
#include "ShapeManager.h"
#include "ThreadSafeDynamicsWorld.h"
#include "VoxelObject.h"
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
class PhysicsEngine : public EntitySimulation {
public:
PhysicsEngine(const glm::vec3& offset);
~PhysicsEngine();
// overrides for EntitySimulation
void updateEntitiesInternal(const quint64& now);
void addEntityInternal(EntityItem* entity);
void removeEntityInternal(EntityItem* entity);
void entityChangedInternal(EntityItem* entity);
void sortEntitiesThatMovedInternal();
void clearEntitiesInternal();
virtual void init(EntityEditPacketSender* packetSender);
void stepSimulation();
/// \param offset position of simulation origin in domain-frame
void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; }
/// \return position of simulation origin in domain-frame
const glm::vec3& getOriginOffset() const { return _originOffset; }
/// \param position the minimum corner of the voxel
/// \param scale the length of the voxel side
/// \return true if Voxel added
bool addVoxel(const glm::vec3& position, float scale);
/// \param position the minimum corner of the voxel
/// \param scale the length of the voxel side
/// \return true if Voxel removed
bool removeVoxel(const glm::vec3& position, float scale);
/// \param motionState pointer to Object's MotionState
/// \return true if Object added
bool addObject(ObjectMotionState* motionState);
/// \param motionState pointer to Object's MotionState
/// \return true if Object removed
bool removeObject(ObjectMotionState* motionState);
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
/// \return duration of fixed simulation substep
float getFixedSubStep() const;
/// \return number of simulation frames the physics engine has taken
uint32_t getFrameCount() const { return _frameCount; }
/// \return substep remainder used for Bullet MotionState extrapolation
// Bullet will extrapolate the positions provided to MotionState::setWorldTransform() in an effort to provide
// smoother visible motion when the render frame rate does not match that of the simulation loop. We provide
// access to this fraction for improved filtering of update packets to interested parties.
float getSubStepRemainder() { return _dynamicsWorld->getLocalTimeAccumulation(); }
protected:
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;
ShapeManager _shapeManager;
private:
glm::vec3 _originOffset;
btHashMap<PositionHashKey, VoxelObject> _voxels;
// EntitySimulation stuff
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
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;
uint32_t _frameCount;
};
#else // USE_BULLET_PHYSICS
// PhysicsEngine stubbery until Bullet is required
class PhysicsEngine {
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_PhysicsEngine_h

View file

@ -1,6 +1,6 @@
//
// PhysicsEntity.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.06.11
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// PhysicsEntity.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.
@ -18,8 +18,12 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "CollisionInfo.h"
#include "RayIntersectionInfo.h"
#include <CollisionInfo.h>
#include <RayIntersectionInfo.h>
#ifdef USE_BULLET_PHYSICS
#include "PhysicsEngine.h"
#endif // USE_BULLET_PHYSICS
class Shape;
class PhysicsSimulation;

View file

@ -1,6 +1,6 @@
//
// PhysicsSimulation.cpp
// interface/src/avatar
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.06.06
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// PhysicsSimulation.h
// interface/src/avatar
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.06.06
// Copyright 2014 High Fidelity, Inc.

View file

@ -0,0 +1,37 @@
//
// PositionHashKey.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.05
// 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
//
#include <math.h>
#include "PositionHashKey.h"
// static
int computeHash(const glm::vec3& center) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
int hash = DoubleHashKey::hashFunction((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f), 0);
hash ^= DoubleHashKey::hashFunction((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f), 1);
return hash ^ DoubleHashKey::hashFunction((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f), 2);
}
// static
int computeHash2(const glm::vec3& center) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
int hash = DoubleHashKey::hashFunction2((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f));
hash ^= DoubleHashKey::hashFunction2((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f));
return hash ^ DoubleHashKey::hashFunction2((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f));
}
PositionHashKey::PositionHashKey(glm::vec3 center) : DoubleHashKey() {
_hash = computeHash(center);
_hash2 = computeHash2(center);
}

View file

@ -0,0 +1,26 @@
//
// PositionHashKey.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.05
// 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
//
#ifndef hifi_PositionHashKey_h
#define hifi_PositionHashKey_h
#include <glm/glm.hpp>
#include <SharedUtil.h>
#include "DoubleHashKey.h"
class PositionHashKey : public DoubleHashKey {
public:
PositionHashKey(glm::vec3 center);
};
#endif // hifi_PositionHashKey_h

View file

@ -1,6 +1,6 @@
//
// Ragdoll.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// Ragdoll.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.05.30
// Copyright 2014 High Fidelity, Inc.

View file

@ -0,0 +1,172 @@
//
// ShapeInfoUtil.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.12.01
// 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
//
#include <Shape.h> // for FOO_SHAPE types
#include <SharedUtil.h> // for MILLIMETERS_PER_METER
#include "ShapeInfoUtil.h"
#include "BulletUtil.h"
#ifdef USE_BULLET_PHYSICS
int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) {
int bulletShapeType = INVALID_SHAPE_PROXYTYPE;
switch(shapeInfoType) {
case BOX_SHAPE:
bulletShapeType = BOX_SHAPE_PROXYTYPE;
break;
case SPHERE_SHAPE:
bulletShapeType = SPHERE_SHAPE_PROXYTYPE;
break;
case CAPSULE_SHAPE:
bulletShapeType = CAPSULE_SHAPE_PROXYTYPE;
break;
case CYLINDER_SHAPE:
bulletShapeType = CYLINDER_SHAPE_PROXYTYPE;
break;
}
return bulletShapeType;
}
int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) {
int shapeInfoType = INVALID_SHAPE;
switch(bulletShapeType) {
case BOX_SHAPE_PROXYTYPE:
shapeInfoType = BOX_SHAPE;
break;
case SPHERE_SHAPE_PROXYTYPE:
shapeInfoType = SPHERE_SHAPE;
break;
case CAPSULE_SHAPE_PROXYTYPE:
shapeInfoType = CAPSULE_SHAPE;
break;
case CYLINDER_SHAPE_PROXYTYPE:
shapeInfoType = CYLINDER_SHAPE;
break;
}
return shapeInfoType;
}
void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info) {
if (shape) {
int type = ShapeInfoUtil::fromBulletShapeType(shape->getShapeType());
switch(type) {
case BOX_SHAPE: {
const btBoxShape* boxShape = static_cast<const btBoxShape*>(shape);
info.setBox(bulletToGLM(boxShape->getHalfExtentsWithMargin()));
}
break;
case SPHERE_SHAPE: {
const btSphereShape* sphereShape = static_cast<const btSphereShape*>(shape);
info.setSphere(sphereShape->getRadius());
}
break;
case CYLINDER_SHAPE: {
// NOTE: we only support cylinders along yAxis
const btCylinderShape* cylinderShape = static_cast<const btCylinderShape*>(shape);
btVector3 halfExtents = cylinderShape->getHalfExtentsWithMargin();
info.setCylinder(halfExtents.getX(), halfExtents.getY());
}
break;
case CAPSULE_SHAPE: {
// NOTE: we only support capsules along yAxis
const btCapsuleShape* capsuleShape = static_cast<const btCapsuleShape*>(shape);
info.setCapsule(capsuleShape->getRadius(), capsuleShape->getHalfHeight());
}
break;
default:
info.clear();
break;
}
} else {
info.clear();
}
}
btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
const QVector<glm::vec3>& data = info.getData();
switch(info.getType()) {
case BOX_SHAPE: {
// data[0] is halfExtents
shape = new btBoxShape(glmToBullet(data[0]));
}
break;
case SPHERE_SHAPE: {
float radius = data[0].z;
shape = new btSphereShape(radius);
}
break;
case CYLINDER_SHAPE: {
// NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X
// data[0] = btVector3(radius, halfHeight, unused)
shape = new btCylinderShape(glmToBullet(data[0]));
}
break;
case CAPSULE_SHAPE: {
float radius = data[0].x;
float height = 2.0f * data[0].y;
shape = new btCapsuleShape(radius, height);
}
break;
}
return shape;
}
DoubleHashKey ShapeInfoUtil::computeHash(const ShapeInfo& info) {
DoubleHashKey key;
// compute hash
// scramble the bits of the type
// TODO?: provide lookup table for hash of info._type rather than recompute?
int primeIndex = 0;
unsigned int hash = DoubleHashKey::hashFunction((unsigned int)info.getType(), primeIndex++);
const QVector<glm::vec3>& data = info.getData();
glm::vec3 tmpData;
int numData = data.size();
for (int i = 0; i < numData; ++i) {
tmpData = data[i];
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
unsigned int floatHash =
DoubleHashKey::hashFunction((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f), primeIndex++);
hash ^= floatHash;
}
}
key._hash = (int)hash;
// compute hash2
// scramble the bits of the type
// TODO?: provide lookup table for hash2 of info._type rather than recompute?
hash = DoubleHashKey::hashFunction2((unsigned int)info.getType());
for (int i = 0; i < numData; ++i) {
tmpData = data[i];
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
unsigned int floatHash =
DoubleHashKey::hashFunction2((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f));
hash += ~(floatHash << 17);
hash ^= (floatHash >> 11);
hash += (floatHash << 4);
hash ^= (floatHash >> 7);
hash += ~(floatHash << 10);
hash = (hash << 16) | (hash >> 16);
}
}
key._hash2 = (int)hash;
return key;
}
#endif // USE_BULLET_PHYSICS

View file

@ -0,0 +1,39 @@
//
// ShapeInfoUtil.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.12.01
// 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
//
#ifndef hifi_ShapeInfoUtil_h
#define hifi_ShapeInfoUtil_h
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
#include <ShapeInfo.h>
#include "DoubleHashKey.h"
// translates between ShapeInfo and btShape
namespace ShapeInfoUtil {
void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
DoubleHashKey computeHash(const ShapeInfo& info);
// TODO? just use bullet shape types everywhere?
int toBulletShapeType(int shapeInfoType);
int fromBulletShapeType(int bulletShapeType);
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_ShapeInfoUtil_h

View file

@ -0,0 +1,109 @@
//
// ShapeManager.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
//
#ifdef USE_BULLET_PHYSICS
#include <glm/gtx/norm.hpp>
#include "ShapeInfoUtil.h"
#include "ShapeManager.h"
ShapeManager::ShapeManager() {
}
ShapeManager::~ShapeManager() {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
delete shapeRef->_shape;
}
_shapeMap.clear();
}
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
// Very small or large objects are not supported.
float diagonal = glm::length2(info.getBoundingBoxDiagonal());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) {
return NULL;
}
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
shapeRef->_refCount++;
return shapeRef->_shape;
}
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
if (shape) {
ShapeReference newRef;
newRef._refCount = 1;
newRef._shape = shape;
_shapeMap.insert(key, newRef);
}
return shape;
}
bool ShapeManager::releaseShape(const ShapeInfo& info) {
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
if (shapeRef->_refCount > 0) {
shapeRef->_refCount--;
if (shapeRef->_refCount == 0) {
_pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 127;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
collectGarbage();
}
}
return true;
} else {
// attempt to remove shape that has no refs
assert(false);
}
} else {
// attempt to remove unmanaged shape
assert(false);
}
return false;
}
bool ShapeManager::releaseShape(const btCollisionShape* shape) {
ShapeInfo info;
ShapeInfoUtil::collectInfoFromShape(shape, info);
return releaseShape(info);
}
void ShapeManager::collectGarbage() {
int numShapes = _pendingGarbage.size();
for (int i = 0; i < numShapes; ++i) {
DoubleHashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->_refCount == 0) {
delete shapeRef->_shape;
_shapeMap.remove(key);
}
}
_pendingGarbage.clear();
}
int ShapeManager::getNumReferences(const ShapeInfo& info) const {
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
const ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
return shapeRef->_refCount;
}
return -1;
}
#endif // USE_BULLET_PHYSICS

View file

@ -0,0 +1,56 @@
//
// ShapeManager.h
// 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
//
#ifndef hifi_ShapeManager_h
#define hifi_ShapeManager_h
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h>
#include <ShapeInfo.h>
#include "DoubleHashKey.h"
class ShapeManager {
public:
ShapeManager();
~ShapeManager();
/// \return pointer to shape
btCollisionShape* getShape(const ShapeInfo& info);
/// \return true if shape was found and released
bool releaseShape(const ShapeInfo& info);
bool releaseShape(const btCollisionShape* shape);
/// delete shapes that have zero references
void collectGarbage();
// validation methods
int getNumShapes() const { return _shapeMap.size(); }
int getNumReferences(const ShapeInfo& info) const;
private:
struct ShapeReference {
int _refCount;
btCollisionShape* _shape;
ShapeReference() : _refCount(0), _shape(NULL) {}
};
btHashMap<DoubleHashKey, ShapeReference> _shapeMap;
btAlignedObjectArray<DoubleHashKey> _pendingGarbage;
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_ShapeManager_h

View file

@ -0,0 +1,91 @@
/*
* Bullet Continuous Collision Detection and Physics Library
* Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it freely,
* subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12.
* */
#include <EntityTree.h>
#include "ThreadSafeDynamicsWorld.h"
#ifdef USE_BULLET_PHYSICS
ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
btDispatcher* dispatcher,
btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration,
EntityTree* entities)
: btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) {
assert(entities);
_entities = entities;
}
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
_entities->lockForWrite();
btDiscreteDynamicsWorld::synchronizeMotionStates();
_entities->unlock();
}
int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) {
int subSteps = 0;
if (maxSubSteps) {
//fixed timestep with interpolation
m_fixedTimeStep = fixedTimeStep;
m_localTime += timeStep;
if (m_localTime >= fixedTimeStep)
{
subSteps = int( m_localTime / fixedTimeStep);
m_localTime -= subSteps * fixedTimeStep;
}
} else {
//variable timestep
fixedTimeStep = timeStep;
m_localTime = m_latencyMotionStateInterpolation ? 0 : timeStep;
m_fixedTimeStep = 0;
if (btFuzzyZero(timeStep))
{
subSteps = 0;
maxSubSteps = 0;
} else
{
subSteps = 1;
maxSubSteps = 1;
}
}
/*//process some debugging flags
if (getDebugDrawer()) {
btIDebugDraw* debugDrawer = getDebugDrawer ();
gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0;
}*/
if (subSteps) {
//clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
int clampedSimulationSteps = (subSteps > maxSubSteps)? maxSubSteps : subSteps;
saveKinematicState(fixedTimeStep*clampedSimulationSteps);
applyGravity();
for (int i=0;i<clampedSimulationSteps;i++) {
internalSingleStepSimulation(fixedTimeStep);
}
}
// We only sync motion states once at the end of all substeps.
// This is to avoid placing multiple, repeated thread locks on _entities.
synchronizeMotionStates();
clearForces();
return subSteps;
}
#endif // USE_BULLET_PHYSICS

View file

@ -0,0 +1,59 @@
/*
* Bullet Continuous Collision Detection and Physics Library
* Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it freely,
* subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* Copied and modified from btDiscreteDynamicsWorld.h by AndrewMeadows on 2014.11.12.
* */
#ifndef hifi_ThreadSafeDynamicsWorld_h
#define hifi_ThreadSafeDynamicsWorld_h
#ifdef USE_BULLET_PHYSICS
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
class EntityTree;
ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld {
public:
BT_DECLARE_ALIGNED_ALLOCATOR();
ThreadSafeDynamicsWorld(
btDispatcher* dispatcher,
btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration,
EntityTree* entities);
// virtual overrides from btDiscreteDynamicsWorld
int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.));
void synchronizeMotionStates();
// btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated
// but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide
// smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop).
float getLocalTimeAccumulation() const { return m_localTime; }
private:
EntityTree* _entities;
};
#else // USE_BULLET_PHYSICS
// stubbery for ThreadSafeDynamicsWorld when Bullet not available
class ThreadSafeDynamicsWorld {
public:
ThreadSafeDynamicsWorld() {}
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_ThreadSafeDynamicsWorld_h

View file

@ -1,6 +1,6 @@
//
// VerletCapsuleShape.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// VerletCapsuleShape.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// VerletPoint.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// VerletPoint.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows 2014.07.24
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// VerletSphereShape.cpp
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// VerletSphereShape.h
// libraries/shared/src
// libraries/physics/src
//
// Created by Andrew Meadows on 2014.06.16
// Copyright 2014 High Fidelity, Inc.

View file

@ -0,0 +1,31 @@
//
// VoxelObject.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.11.05
// 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
//
#ifndef hifi_VoxelObject_h
#define hifi_VoxelObject_h
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
// VoxelObject is a simple wrapper for tracking a Voxel in a PhysicsEngine
class VoxelObject {
public:
VoxelObject(const glm::vec3& center, btCollisionObject* object) : _object(object), _center(center) {
assert(object != NULL);
}
btCollisionObject* _object;
glm::vec3 _center;
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_VoxelObject_h

View file

@ -78,6 +78,10 @@ inline bool operator==(const AACube& a, const AACube& b) {
return a.getCorner() == b.getCorner() && a.getScale() == b.getScale();
}
inline bool operator!=(const AACube& a, const AACube& b) {
return a.getCorner() != b.getCorner() || a.getScale() != b.getScale();
}
inline QDebug operator<<(QDebug debug, const AACube& cube) {
debug << "AACube[ ("
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("

View file

@ -13,7 +13,7 @@
#include <glm/gtx/norm.hpp>
#include "AACubeShape.h"
#include <SharedUtil.h> // for SQUARE_ROOT_OF_3
#include "SharedUtil.h" // for SQUARE_ROOT_OF_3
glm::vec3 faceNormals[3] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f) };

View file

@ -12,11 +12,9 @@
#include <iostream>
#include <glm/gtx/vector_angle.hpp>
#include <GeometryUtil.h>
#include <SharedUtil.h>
#include "CapsuleShape.h"
#include "GeometryUtil.h"
#include "SharedUtil.h"
CapsuleShape::CapsuleShape() : Shape(CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {}

View file

@ -12,9 +12,8 @@
#ifndef hifi_CapsuleShape_h
#define hifi_CapsuleShape_h
#include <SharedUtil.h>
#include "Shape.h"
#include "SharedUtil.h"
// default axis of CapsuleShape is Y-axis
const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f);

View file

@ -10,10 +10,10 @@
//
#include <SharedUtil.h>
#include "CollisionInfo.h"
#include "Shape.h"
#include "SharedUtil.h"
CollisionInfo::CollisionInfo() :
_data(NULL),

View file

@ -9,10 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <SharedUtil.h>
#include <GLMHelpers.h>
#include "GLMHelpers.h"
#include "PlaneShape.h"
#include "SharedUtil.h"
const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f);

View file

@ -1,6 +1,6 @@
//
// RayIntersectionInfo.h
// interface/src/avatar
// libraries/physcis/src
//
// Created by Andrew Meadows 2014.09.09
// Copyright 2014 High Fidelity, Inc.

View file

@ -25,12 +25,19 @@ class VerletPoint;
const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX)
// DANGER: until ShapeCollider goes away the order of these values matter. Specifically,
// UNKNOWN_SHAPE must be equal to the number of shapes that ShapeCollider actually supports.
const quint8 SPHERE_SHAPE = 0;
const quint8 CAPSULE_SHAPE = 1;
const quint8 PLANE_SHAPE = 2;
const quint8 AACUBE_SHAPE = 3;
const quint8 LIST_SHAPE = 4;
const quint8 UNKNOWN_SHAPE = 5;
const quint8 INVALID_SHAPE = 5;
// new shapes to be supported by Bullet
const quint8 BOX_SHAPE = 7;
const quint8 CYLINDER_SHAPE = 8;
class Shape {
public:

View file

@ -11,16 +11,14 @@
#include <glm/gtx/norm.hpp>
#include <GeometryUtil.h>
#include <StreamUtils.h>
#include "ShapeCollider.h"
#include "AACubeShape.h"
#include "CapsuleShape.h"
#include "GeometryUtil.h"
#include "ListShape.h"
#include "PlaneShape.h"
#include "ShapeCollider.h"
#include "SphereShape.h"
#include "StreamUtils.h"
// NOTE:

View file

@ -14,10 +14,10 @@
#include <QVector>
#include <SharedUtil.h>
#include "CollisionInfo.h"
#include "RayIntersectionInfo.h"
#include "SharedUtil.h"
class Shape;
class SphereShape;

View file

@ -0,0 +1,60 @@
//
// ShapeInfo.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
//
#include <math.h>
#include "SharedUtil.h" // for MILLIMETERS_PER_METER
//#include "DoubleHashKey.h"
#include "ShapeInfo.h"
void ShapeInfo::clear() {
_type = INVALID_SHAPE;
_data.clear();
}
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_type = BOX_SHAPE;
_data.clear();
_data.push_back(halfExtents);
}
void ShapeInfo::setSphere(float radius) {
_type = SPHERE_SHAPE;
_data.clear();
_data.push_back(glm::vec3(radius));
}
void ShapeInfo::setCylinder(float radius, float halfHeight) {
_type = CYLINDER_SHAPE;
_data.clear();
// NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X
_data.push_back(glm::vec3(radius, halfHeight, radius));
}
void ShapeInfo::setCapsule(float radius, float halfHeight) {
_type = CAPSULE_SHAPE;
_data.clear();
_data.push_back(glm::vec3(radius, halfHeight, radius));
}
glm::vec3 ShapeInfo::getBoundingBoxDiagonal() const {
switch(_type) {
case BOX_SHAPE:
case SPHERE_SHAPE:
case CYLINDER_SHAPE:
case CAPSULE_SHAPE:
return 2.0f * _data[0];
default:
break;
}
return glm::vec3(0.0f);
}

View file

@ -0,0 +1,42 @@
//
// ShapeInfo.h
// 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
//
#ifndef hifi_ShapeInfo_h
#define hifi_ShapeInfo_h
#include <QVector>
#include <glm/glm.hpp>
#include "Shape.h"
class ShapeInfo {
public:
ShapeInfo() : _type(INVALID_SHAPE) {}
void clear();
void setBox(const glm::vec3& halfExtents);
void setSphere(float radius);
void setCylinder(float radius, float halfHeight);
void setCapsule(float radius, float halfHeight);
const int getType() const { return _type; }
const QVector<glm::vec3>& getData() const { return _data; }
glm::vec3 getBoundingBoxDiagonal() const;
protected:
int _type;
QVector<glm::vec3> _data;
};
#endif // hifi_ShapeInfo_h

View file

@ -12,9 +12,8 @@
#ifndef hifi_SphereShape_h
#define hifi_SphereShape_h
#include <SharedUtil.h>
#include "Shape.h"
#include "SharedUtil.h"
class SphereShape : public Shape {

View file

@ -3,8 +3,8 @@ set(TARGET_NAME physics-tests)
setup_hifi_project()
include_glm()
include_bullet()
# link in the shared libraries
link_hifi_libraries(shared physics)
include_dependency_includes()

View file

@ -0,0 +1,114 @@
//
// BulletUtilTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.11.02
// 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
//
#include <iostream>
#include <BulletUtil.h>
#include <SharedUtil.h>
#include "BulletUtilTests.h"
#ifdef USE_BULLET_PHYSICS
void BulletUtilTests::fromBulletToGLM() {
btVector3 bV(1.23f, 4.56f, 7.89f);
glm::vec3 gV = bulletToGLM(bV);
if (gV.x != bV.getX()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.x = " << bV.getX() << " != glm.x = " << gV.x << std::endl;
}
if (gV.y != bV.getY()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.y = " << bV.getY() << " != glm.y = " << gV.y << std::endl;
}
if (gV.z != bV.getZ()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.z = " << bV.getZ() << " != glm.z = " << gV.z << std::endl;
}
float angle = 0.317f * PI;
btVector3 axis(1.23f, 2.34f, 3.45f);
axis.normalize();
btQuaternion bQ(axis, angle);
glm::quat gQ = bulletToGLM(bQ);
if (gQ.x != bQ.getX()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.x = " << bQ.getX() << " != glm.x = " << gQ.x << std::endl;
}
if (gQ.y != bQ.getY()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.y = " << bQ.getY() << " != glm.y = " << gQ.y << std::endl;
}
if (gQ.z != bQ.getZ()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.z = " << bQ.getZ() << " != glm.z = " << gQ.z << std::endl;
}
if (gQ.w != bQ.getW()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch bullet.w = " << bQ.getW() << " != glm.w = " << gQ.w << std::endl;
}
}
void BulletUtilTests::fromGLMToBullet() {
glm::vec3 gV(1.23f, 4.56f, 7.89f);
btVector3 bV = glmToBullet(gV);
if (gV.x != bV.getX()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.x = " << gV.x << " != bullet.x = " << bV.getX() << std::endl;
}
if (gV.y != bV.getY()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.y = " << gV.y << " != bullet.y = " << bV.getY() << std::endl;
}
if (gV.z != bV.getZ()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.z = " << gV.z << " != bullet.z = " << bV.getZ() << std::endl;
}
float angle = 0.317f * PI;
btVector3 axis(1.23f, 2.34f, 3.45f);
axis.normalize();
btQuaternion bQ(axis, angle);
glm::quat gQ = bulletToGLM(bQ);
if (gQ.x != bQ.getX()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.x = " << gQ.x << " != bullet.x = " << bQ.getX() << std::endl;
}
if (gQ.y != bQ.getY()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.y = " << gQ.y << " != bullet.y = " << bQ.getY() << std::endl;
}
if (gQ.z != bQ.getZ()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.z = " << gQ.z << " != bullet.z = " << bQ.getZ() << std::endl;
}
if (gQ.w != bQ.getW()) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: x mismatch glm.w = " << gQ.w << " != bullet.w = " << bQ.getW() << std::endl;
}
}
void BulletUtilTests::runAllTests() {
fromBulletToGLM();
fromGLMToBullet();
}
#else // USE_BULLET_PHYSICS
void BulletUtilTests::fromBulletToGLM() {
}
void BulletUtilTests::fromGLMToBullet() {
}
void BulletUtilTests::runAllTests() {
}
#endif // USE_BULLET_PHYSICS

View file

@ -0,0 +1,21 @@
//
// BulletUtilTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.11.02
// 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
//
#ifndef hifi_BulletUtilTests_h
#define hifi_BulletUtilTests_h
namespace BulletUtilTests {
void fromBulletToGLM();
void fromGLMToBullet();
void runAllTests();
}
#endif // hifi_BulletUtilTests_h

View file

@ -0,0 +1,260 @@
//
// ShapeInfoTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.11.02
// 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
//
#include <iostream>
#ifdef USE_BULLET_PHYSICS
#include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h>
#endif // USE_BULLET_PHYSICS
#include <DoubleHashKey.h>
#include <ShapeInfo.h>
#include <ShapeInfoUtil.h>
#include <StreamUtils.h>
#include "ShapeInfoTests.h"
void ShapeInfoTests::testHashFunctions() {
#ifdef USE_BULLET_PHYSICS
int maxTests = 10000000;
ShapeInfo info;
btHashMap<btHashInt, int> hashes;
int bits[32];
unsigned int masks[32];
for (int i = 0; i < 32; ++i) {
bits[i] = 0;
masks[i] = 1U << i;
}
float deltaLength = 0.002f;
float endLength = 100.0f;
int numSteps = (int)(endLength / deltaLength);
int testCount = 0;
int numCollisions = 0;
btClock timer;
for (int x = 1; x < numSteps && testCount < maxTests; ++x) {
float radiusX = (float)x * deltaLength;
// test sphere
info.setSphere(radiusX);
++testCount;
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
int* hashPtr = hashes.find(key._hash);
if (hashPtr && *hashPtr == key._hash2) {
std::cout << testCount << " hash collision radiusX = " << radiusX
<< " h1 = 0x" << std::hex << (unsigned int)(key._hash)
<< " h2 = 0x" << std::hex << (unsigned int)(key._hash2)
<< std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key._hash, key._hash2);
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key._hash2) {
++bits[k];
}
}
for (int y = 1; y < numSteps && testCount < maxTests; ++y) {
float radiusY = (float)y * deltaLength;
// test cylinder and capsule
int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE };
for (int i = 0; i < 2; ++i) {
switch(types[i]) {
case CYLINDER_SHAPE_PROXYTYPE: {
info.setCylinder(radiusX, radiusY);
break;
}
case CAPSULE_SHAPE_PROXYTYPE: {
info.setCapsule(radiusX, radiusY);
break;
}
}
++testCount;
key = ShapeInfoUtil::computeHash(info);
hashPtr = hashes.find(key._hash);
if (hashPtr && *hashPtr == key._hash2) {
std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY
<< " h1 = 0x" << std::hex << (unsigned int)(key._hash)
<< " h2 = 0x" << std::hex << (unsigned int)(key._hash2)
<< std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key._hash, key._hash2);
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key._hash2) {
++bits[k];
}
}
}
for (int z = 1; z < numSteps && testCount < maxTests; ++z) {
float radiusZ = (float)z * deltaLength;
// test box
info.setBox(glm::vec3(radiusX, radiusY, radiusZ));
++testCount;
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
hashPtr = hashes.find(key._hash);
if (hashPtr && *hashPtr == key._hash2) {
std::cout << testCount << " hash collision radiusX = " << radiusX
<< " radiusY = " << radiusY << " radiusZ = " << radiusZ
<< " h1 = 0x" << std::hex << (unsigned int)(key._hash)
<< " h2 = 0x" << std::hex << (unsigned int)(key._hash2)
<< std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key._hash, key._hash2);
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key._hash2) {
++bits[k];
}
}
}
}
}
unsigned long int msec = timer.getTimeMilliseconds();
std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl;
// print out distribution of bits
for (int i = 0; i < 32; ++i) {
std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeInfoTests::testBoxShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
glm::vec3 halfExtents(1.23f, 4.56f, 7.89f);
info.setBox(halfExtents);
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: NULL Box shape" << std::endl;
}
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo);
if (key._hash != otherKey._hash) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Box shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl;
}
if (key._hash2 != otherKey._hash2) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Box shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl;
}
delete shape;
#endif // USE_BULLET_PHYSICS
}
void ShapeInfoTests::testSphereShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
info.setSphere(radius);
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo);
if (key._hash != otherKey._hash) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Sphere shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl;
}
if (key._hash2 != otherKey._hash2) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Sphere shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl;
}
delete shape;
#endif // USE_BULLET_PHYSICS
}
void ShapeInfoTests::testCylinderShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
float height = 4.56f;
info.setCylinder(radius, height);
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo);
if (key._hash != otherKey._hash) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Cylinder shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl;
}
if (key._hash2 != otherKey._hash2) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Cylinder shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl;
}
delete shape;
#endif // USE_BULLET_PHYSICS
}
void ShapeInfoTests::testCapsuleShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
float height = 4.56f;
info.setCapsule(radius, height);
DoubleHashKey key = ShapeInfoUtil::computeHash(info);
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo);
if (key._hash != otherKey._hash) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Capsule shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl;
}
if (key._hash2 != otherKey._hash2) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected Capsule shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl;
}
delete shape;
#endif // USE_BULLET_PHYSICS
}
void ShapeInfoTests::runAllTests() {
//#define MANUAL_TEST
#ifdef MANUAL_TEST
testHashFunctions();
#endif // MANUAL_TEST
testBoxShape();
testSphereShape();
testCylinderShape();
testCapsuleShape();
}

View file

@ -0,0 +1,24 @@
//
// ShapeInfoTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.11.02
// 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
//
#ifndef hifi_ShapeInfoTests_h
#define hifi_ShapeInfoTests_h
namespace ShapeInfoTests {
void testHashFunctions();
void testBoxShape();
void testSphereShape();
void testCylinderShape();
void testCapsuleShape();
void runAllTests();
}
#endif // hifi_ShapeInfoTests_h

View file

@ -0,0 +1,247 @@
//
// ShapeManagerTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// 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
//
#include <iostream>
#include <ShapeInfoUtil.h>
#include <ShapeManager.h>
#include <StreamUtils.h>
#include "ShapeManagerTests.h"
void ShapeManagerTests::testShapeAccounting() {
#ifdef USE_BULLET_PHYSICS
ShapeManager shapeManager;
ShapeInfo info;
info.setBox(glm::vec3(1.0f, 1.0f, 1.0f));
// NOTE: ShapeManager returns -1 as refcount when the shape is unknown,
// which is distinct from "known but with zero references"
int numReferences = shapeManager.getNumReferences(info);
if (numReferences != -1) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected ignorant ShapeManager after initialization" << std::endl;
}
// create one shape and verify we get a valid pointer
btCollisionShape* shape = shapeManager.getShape(info);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected shape creation for default parameters" << std::endl;
}
// verify number of shapes
if (shapeManager.getNumShapes() != 1) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape" << std::endl;
}
// reference the shape again and verify that we get the same pointer
btCollisionShape* otherShape = shapeManager.getShape(info);
if (otherShape != shape) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape* " << (void*)(shape)
<< " but found shape* " << (void*)(otherShape) << std::endl;
}
// verify number of references
numReferences = shapeManager.getNumReferences(info);
int expectedNumReferences = 2;
if (numReferences != expectedNumReferences) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected " << expectedNumReferences
<< " references but found " << numReferences << std::endl;
}
// release all references
bool released = shapeManager.releaseShape(info);
numReferences--;
while (numReferences > 0) {
released = shapeManager.releaseShape(info) && released;
numReferences--;
}
if (!released) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape released" << std::endl;
}
// verify shape still exists (not yet garbage collected)
if (shapeManager.getNumShapes() != 1) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release but before garbage collection" << std::endl;
}
// verify shape's refcount is zero
numReferences = shapeManager.getNumReferences(info);
if (numReferences != 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected refcount = 0 for shape but found refcount = " << numReferences << std::endl;
}
// reference the shape again and verify refcount is updated
otherShape = shapeManager.getShape(info);
numReferences = shapeManager.getNumReferences(info);
if (numReferences != 1) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl;
}
// verify that shape is not collected as garbage
shapeManager.collectGarbage();
if (shapeManager.getNumShapes() != 1) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release" << std::endl;
}
numReferences = shapeManager.getNumReferences(info);
if (numReferences != 1) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl;
}
// release reference and verify that it is collected as garbage
released = shapeManager.releaseShape(info);
shapeManager.collectGarbage();
if (shapeManager.getNumShapes() != 0) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl;
}
numReferences = shapeManager.getNumReferences(info);
if (numReferences != -1) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl;
}
// add the shape again and verify that it gets added again
otherShape = shapeManager.getShape(info);
numReferences = shapeManager.getNumReferences(info);
if (numReferences != 1) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::addManyShapes() {
#ifdef USE_BULLET_PHYSICS
ShapeManager shapeManager;
int numSizes = 100;
float startSize = 1.0f;
float endSize = 99.0f;
float deltaSize = (endSize - startSize) / (float)numSizes;
ShapeInfo info;
for (int i = 0; i < numSizes; ++i) {
float s = startSize + (float)i * deltaSize;
glm::vec3 scale(s, 1.23f + s, s - 0.573f);
info.setBox(0.5f * scale);
btCollisionShape* shape = shapeManager.getShape(info);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl;
}
float radius = 0.5f * s;
info.setSphere(radius);
shape = shapeManager.getShape(info);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl;
}
}
int numShapes = shapeManager.getNumShapes();
if (numShapes != 2 * numSizes) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::addBoxShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
glm::vec3 halfExtents(1.23f, 4.56f, 7.89f);
info.setBox(halfExtents);
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
if (shape != otherShape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: Box ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::addSphereShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
info.setSphere(radius);
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
if (shape != otherShape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: Sphere ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::addCylinderShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
float height = 4.56f;
info.setCylinder(radius, height);
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
if (shape != otherShape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: Cylinder ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::addCapsuleShape() {
#ifdef USE_BULLET_PHYSICS
ShapeInfo info;
float radius = 1.23f;
float height = 4.56f;
info.setCapsule(radius, height);
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
ShapeInfo otherInfo;
ShapeInfoUtil::collectInfoFromShape(shape, otherInfo);
btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
if (shape != otherShape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: Capsule ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl;
}
#endif // USE_BULLET_PHYSICS
}
void ShapeManagerTests::runAllTests() {
testShapeAccounting();
addManyShapes();
addBoxShape();
addSphereShape();
addCylinderShape();
addCapsuleShape();
}

View file

@ -0,0 +1,25 @@
//
// ShapeManagerTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// 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
//
#ifndef hifi_ShapeManagerTests_h
#define hifi_ShapeManagerTests_h
namespace ShapeManagerTests {
void testShapeAccounting();
void addManyShapes();
void addBoxShape();
void addSphereShape();
void addCylinderShape();
void addCapsuleShape();
void runAllTests();
}
#endif // hifi_ShapeManagerTests_h

View file

@ -10,9 +10,15 @@
#include "ShapeColliderTests.h"
#include "VerletShapeTests.h"
#include "ShapeInfoTests.h"
#include "ShapeManagerTests.h"
#include "BulletUtilTests.h"
int main(int argc, char** argv) {
ShapeColliderTests::runAllTests();
VerletShapeTests::runAllTests();
ShapeInfoTests::runAllTests();
ShapeManagerTests::runAllTests();
BulletUtilTests::runAllTests();
return 0;
}