diff --git a/libraries/entities/src/EntityCollisionSystem.cpp b/libraries/entities/src/EntityCollisionSystem.cpp index 61d8447a30..172850bc64 100644 --- a/libraries/entities/src/EntityCollisionSystem.cpp +++ b/libraries/entities/src/EntityCollisionSystem.cpp @@ -134,8 +134,8 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { glm::vec3 axis = glm::normalize(penetration); glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis; - float massA = entityA->getMass(); - float massB = entityB->getMass(); + float massA = entityA->computeMass(); + float massB = entityB->computeMass(); float totalMass = massA + massB; float massRatioA = (2.0f * massB / totalMass); float massRatioB = (2.0f * massA / totalMass); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 885d589ed1..3f4d319398 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -37,10 +37,10 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _position = ENTITY_ITEM_ZERO_VEC3; _dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; + _density = ENTITY_ITEM_DEFAULT_DENSITY; _rotation = ENTITY_ITEM_DEFAULT_ROTATION; _glowLevel = ENTITY_ITEM_DEFAULT_GLOW_LEVEL; _localRenderAlpha = ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA; - _mass = ENTITY_ITEM_DEFAULT_MASS; _velocity = ENTITY_ITEM_DEFAULT_VELOCITY; _gravity = ENTITY_ITEM_DEFAULT_GRAVITY; _damping = ENTITY_ITEM_DEFAULT_DAMPING; @@ -219,7 +219,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet } APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation()); - APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass()); + APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, computeMass()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping()); @@ -555,7 +555,34 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s } } +float EntityItem::computeMass() const { + // NOTE: we group the operations here in and attempt to reduce floating point error. + return ((_density * (_volumeMultiplier * _dimensions.x)) * _dimensions.y) * _dimensions.z; +} + const float ENTITY_ITEM_EPSILON_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; +const float MAX_DENSITY = 10000.0f; // kg/m^3 density of silver +const float MIN_DENSITY = 100.0f; // kg/m^3 density of styrofoam + +void EntityItem::setMass(float mass) { + // Setting the mass actually changes the _density (at fixed volume), however + // we must protect the density range to help maintain stability of physics simulation + // therefore this method might not accept the mass that is supplied. + + // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due + // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) + // in an attempt to reduce floating point error of the final result. + float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + + // compute new density + const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 + if (volume < 1.0e-6f) { + // avoid divide by zero + _density = glm::min(mass / MIN_VOLUME, MAX_DENSITY); + } else { + _density = glm::max(glm::min(mass / volume, MAX_DENSITY), MIN_DENSITY); + } +} // TODO: we probably want to change this to make "down" be the direction of the entity's gravity vector // for now, this is always true DOWN even if entity has non-down gravity. @@ -771,7 +798,7 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, getMass); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, computeMass); COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); @@ -1047,9 +1074,29 @@ void EntityItem::updateRotation(const glm::quat& rotation) { } } -void EntityItem::updateMass(float value) { - if (fabsf(_mass - value) > MIN_MASS_DELTA) { - _mass = value; +void EntityItem::updateMass(float mass) { + // Setting the mass actually changes the _density (at fixed volume), however + // we must protect the density range to help maintain stability of physics simulation + // therefore this method might not accept the mass that is supplied. + + // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due + // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) + // in an attempt to reduce floating point error of the final result. + float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + + // compute new density + float newDensity = _density; + const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 + if (volume < 1.0e-6f) { + // avoid divide by zero + newDensity = glm::min(mass / MIN_VOLUME, MAX_DENSITY); + } else { + newDensity = glm::max(glm::min(mass / volume, MAX_DENSITY), MIN_DENSITY); + } + + const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent + if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) { + _density = newDensity; _dirtyFlags |= EntityItem::DIRTY_MASS; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index b84739e07e..3cd4ad9490 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -171,8 +171,8 @@ public: float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; } - float getMass() const { return _mass; } - void setMass(float value) { _mass = value; } + float computeMass() const; + void setMass(float mass); const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters @@ -303,7 +303,12 @@ protected: glm::quat _rotation; float _glowLevel; float _localRenderAlpha; - float _mass; + float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg + // NOTE: _volumeMultiplier is used to compute volume: + // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z = m^3 + // DANGER: due to the size of TREE_SCALE the _volumeMultiplier is always a large number, and therefore + // will tend to introduce floating point error. We must keep this in mind when using it. + float _volumeMultiplier = (float)TREE_SCALE * (float)TREE_SCALE * (float)TREE_SCALE; glm::vec3 _velocity; glm::vec3 _gravity; float _damping; diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 285788c960..acb37eed3f 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -36,8 +36,11 @@ const float ENTITY_ITEM_IMMORTAL_LIFETIME = -1.0f; /// special lifetime which me const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME; const glm::quat ENTITY_ITEM_DEFAULT_ROTATION; -const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(0.1f) / (float)TREE_SCALE; -const float ENTITY_ITEM_DEFAULT_MASS = 1.0f; +const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f; +const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH) / (float)TREE_SCALE; +const float ENTITY_ITEM_DEFAULT_DENSITY = 1000.0f; // density of water +const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH; +const float ENTITY_ITEM_DEFAULT_MASS = ENTITY_ITEM_DEFAULT_DENSITY * ENTITY_ITEM_DEFAULT_VOLUME; const glm::vec3 ENTITY_ITEM_DEFAULT_VELOCITY = ENTITY_ITEM_ZERO_VEC3; const glm::vec3 ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY = ENTITY_ITEM_ZERO_VEC3; diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 9a9c07ef9a..1c16ba4a7b 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -32,6 +32,10 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit { _type = EntityTypes::Sphere; setProperties(properties); + // NOTE: _volumeMultiplier is used to compute volume: + // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z + // The formula below looks funny because _dimension.xyz = diameter rather than radius. + _volumeMultiplier *= PI / 6.0f; } EntityItemProperties SphereEntityItem::getProperties() const { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 019f00fb48..a15f8a9f51 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -109,7 +109,7 @@ void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { _body->setDamping(_linearDamping, _angularDamping); if (flags & EntityItem::DIRTY_MASS) { - float mass = getMass(); + float mass = _entity->computeMass(); btVector3 inertia(0.0f, 0.0f, 0.0f); _body->getCollisionShape()->calculateLocalInertia(mass, inertia); _body->setMassProps(mass, inertia); @@ -137,8 +137,12 @@ void EntityMotionState::updateObjectVelocities() { #endif // USE_BULLET_PHYSICS } -void EntityMotionState::computeShapeInfo(ShapeInfo& info) { - _entity->computeShapeInfo(info); +void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) { + _entity->computeShapeInfo(shapeInfo); +} + +float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const { + return _entity->computeMass(); } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 863edebe7d..57f3b52672 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -57,7 +57,8 @@ public: void updateObjectEasy(uint32_t flags, uint32_t frame); void updateObjectVelocities(); - void computeShapeInfo(ShapeInfo& info); + void computeShapeInfo(ShapeInfo& shapeInfo); + float computeMass(const ShapeInfo& shapeInfo) const; void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame); diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 6e0b2a784c..9b3e69eb8d 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -44,13 +44,10 @@ const glm::vec3& ObjectMotionState::getWorldOffset() { ObjectMotionState::ObjectMotionState() : - _density(DEFAULT_DENSITY), - _volume(DEFAULT_VOLUME), _friction(DEFAULT_FRICTION), _restitution(DEFAULT_RESTITUTION), _linearDamping(0.0f), _angularDamping(0.0f), - _wasInWorld(false), _motionType(MOTION_TYPE_STATIC), _body(NULL), _sentMoving(false), @@ -69,10 +66,6 @@ ObjectMotionState::~ObjectMotionState() { 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); } @@ -89,10 +82,6 @@ void ObjectMotionState::setAngularDamping(float damping) { _angularDamping = btMax(btMin(fabsf(damping), 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)); } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index b9d077f4bb..ea3d5de73b 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -60,18 +60,15 @@ public: virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0; virtual void updateObjectVelocities() = 0; - virtual void computeShapeInfo(ShapeInfo& info) = 0; - virtual MotionType getMotionType() const { return _motionType; } - void setDensity(float density); + virtual void computeShapeInfo(ShapeInfo& info) = 0; + virtual float computeMass(const ShapeInfo& shapeInfo) const = 0; + void setFriction(float friction); void setRestitution(float restitution); void setLinearDamping(float damping); void setAngularDamping(float damping); - void setVolume(float volume); - - float getMass() const { return _volume * _density; } void setVelocity(const glm::vec3& velocity) const; void setAngularVelocity(const glm::vec3& velocity) const; @@ -92,13 +89,12 @@ public: friend class PhysicsEngine; protected: - float _density; - float _volume; + // TODO: move these materials properties to EntityItem float _friction; float _restitution; float _linearDamping; float _angularDamping; - bool _wasInWorld; + MotionType _motionType; // _body has NO setters -- it is only changed by PhysicsEngine diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 666fcd2e89..e08f271255 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -246,9 +246,9 @@ void PhysicsEngine::stepSimulation() { bool PhysicsEngine::addObject(ObjectMotionState* motionState) { assert(motionState); - ShapeInfo info; - motionState->computeShapeInfo(info); - btCollisionShape* shape = _shapeManager.getShape(info); + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + btCollisionShape* shape = _shapeManager.getShape(shapeInfo); if (shape) { btVector3 inertia(0.0f, 0.0f, 0.0f); float mass = 0.0f; @@ -263,7 +263,7 @@ bool PhysicsEngine::addObject(ObjectMotionState* motionState) { break; } case MOTION_TYPE_DYNAMIC: { - mass = motionState->getMass(); + mass = motionState->computeMass(shapeInfo); shape->calculateLocalInertia(mass, inertia); body = new btRigidBody(mass, motionState, shape, inertia); body->updateInertiaTensor(); @@ -301,10 +301,10 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) { btRigidBody* body = motionState->_body; if (body) { const btCollisionShape* shape = body->getCollisionShape(); - ShapeInfo info; - ShapeInfoUtil::collectInfoFromShape(shape, info); + ShapeInfo shapeInfo; + ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo); _dynamicsWorld->removeRigidBody(body); - _shapeManager.releaseShape(info); + _shapeManager.releaseShape(shapeInfo); delete body; motionState->_body = NULL; return true; @@ -320,20 +320,31 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio _dynamicsWorld->removeRigidBody(body); if (flags & EntityItem::DIRTY_SHAPE) { + // MASS bit should be set whenever SHAPE is set + assert(flags & EntityItem::DIRTY_MASS); + + // get new shape btCollisionShape* oldShape = body->getCollisionShape(); - ShapeInfo info; - motionState->computeShapeInfo(info); - btCollisionShape* newShape = _shapeManager.getShape(info); + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + btCollisionShape* newShape = _shapeManager.getShape(shapeInfo); if (newShape != oldShape) { + // BUG: if shape doesn't change but density does then we won't compute new mass properties + // TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly. body->setCollisionShape(newShape); _shapeManager.releaseShape(oldShape); + + // compute mass properties + float mass = motionState->computeMass(shapeInfo); + btVector3 inertia(0.0f, 0.0f, 0.0f); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); } 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) { @@ -356,9 +367,11 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio 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) + // always update mass properties when going dynamic (unless it's already been done above) + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + float mass = motionState->computeMass(shapeInfo); btVector3 inertia(0.0f, 0.0f, 0.0f); - float mass = motionState->getMass(); body->getCollisionShape()->calculateLocalInertia(mass, inertia); body->setMassProps(mass, inertia); body->updateInertiaTensor();