Readded collision objects and movement physics

This commit is contained in:
scrawl 2015-05-12 03:02:15 +02:00
parent 54c1f19c18
commit 47758c11cd
38 changed files with 1238 additions and 473 deletions

View file

@ -151,7 +151,7 @@ if(WIN32)
set(QT_USE_QTMAIN TRUE) set(QT_USE_QTMAIN TRUE)
endif(WIN32) endif(WIN32)
set(BOOST_COMPONENTS system filesystem program_options thread wave) set(BOOST_COMPONENTS system filesystem program_options thread)
if(WIN32) if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
endif(WIN32) endif(WIN32)
@ -202,7 +202,6 @@ target_link_libraries(openmw-cs
${OGRE_LIBRARIES} ${OGRE_LIBRARIES}
${OPENSCENEGRAPH_LIBRARIES} ${OPENSCENEGRAPH_LIBRARIES}
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${BULLET_LIBRARIES}
${QT_LIBRARIES} ${QT_LIBRARIES}
components components
) )

View file

@ -74,7 +74,7 @@ add_openmw_dir (mwworld
) )
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
physicssystem physicssystem trace collisiontype actor convert
) )
add_openmw_dir (mwclass add_openmw_dir (mwclass
@ -101,9 +101,9 @@ add_openmw_dir (mwbase
# Main executable # Main executable
if (ANDROID) if (ANDROID)
set(BOOST_COMPONENTS system filesystem program_options thread wave atomic) set(BOOST_COMPONENTS system filesystem program_options thread atomic)
else () else ()
set(BOOST_COMPONENTS system filesystem program_options thread wave) set(BOOST_COMPONENTS system filesystem program_options thread)
endif () endif ()
if(WIN32) if(WIN32)

View file

@ -302,7 +302,7 @@ namespace MWBase
virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0;
///< Convert position to cell numbers ///< Convert position to cell numbers
virtual void queueMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &velocity) = 0; virtual void queueMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) = 0;
///< Queues movement for \a ptr (in local space), to be applied in the next call to ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.
@ -555,7 +555,7 @@ namespace MWBase
virtual bool isInStorm() const = 0; virtual bool isInStorm() const = 0;
/// @see MWWorld::WeatherManager::getStormDirection /// @see MWWorld::WeatherManager::getStormDirection
virtual Ogre::Vector3 getStormDirection() const = 0; virtual osg::Vec3f getStormDirection() const = 0;
/// Resets all actors in the current active cells to their original location within that cell. /// Resets all actors in the current active cells to their original location within that cell.
virtual void resetActors() = 0; virtual void resetActors() = 0;

View file

@ -35,8 +35,7 @@ namespace MWClass
void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const

View file

@ -40,8 +40,7 @@ namespace MWClass
void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Armor::getModel(const MWWorld::Ptr &ptr) const std::string Armor::getModel(const MWWorld::Ptr &ptr) const

View file

@ -37,8 +37,7 @@ namespace MWClass
void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Book::getModel(const MWWorld::Ptr &ptr) const std::string Book::getModel(const MWWorld::Ptr &ptr) const

View file

@ -37,8 +37,7 @@ namespace MWClass
void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Clothing::getModel(const MWWorld::Ptr &ptr) const std::string Clothing::getModel(const MWWorld::Ptr &ptr) const

View file

@ -41,8 +41,7 @@ namespace MWClass
void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const

View file

@ -47,8 +47,9 @@ namespace MWClass
ptr.get<ESM::Light>(); ptr.get<ESM::Light>();
assert (ref->mBase != NULL); assert (ref->mBase != NULL);
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, (ref->mBase->mData.mFlags & ESM::Light::Carry) != 0); if ((ref->mBase->mData.mFlags & ESM::Light::Carry) == 0)
physics.addObject(ptr, model);
if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault))
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,

View file

@ -36,8 +36,7 @@ namespace MWClass
void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const

View file

@ -53,8 +53,7 @@ namespace MWClass
void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const

View file

@ -39,8 +39,7 @@ namespace MWClass
void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Potion::getModel(const MWWorld::Ptr &ptr) const std::string Potion::getModel(const MWWorld::Ptr &ptr) const

View file

@ -36,8 +36,7 @@ namespace MWClass
void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Probe::getModel(const MWWorld::Ptr &ptr) const std::string Probe::getModel(const MWWorld::Ptr &ptr) const

View file

@ -35,8 +35,7 @@ namespace MWClass
void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Repair::getModel(const MWWorld::Ptr &ptr) const std::string Repair::getModel(const MWWorld::Ptr &ptr) const

View file

@ -39,8 +39,7 @@ namespace MWClass
void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{ {
if(!model.empty()) // TODO: add option somewhere to enable collision for placeable objects
physics.addObject(ptr, model, true);
} }
std::string Weapon::getModel(const MWWorld::Ptr &ptr) const std::string Weapon::getModel(const MWWorld::Ptr &ptr) const

View file

@ -1346,7 +1346,7 @@ void CharacterController::update(float duration)
{ {
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Class &cls = mPtr.getClass(); const MWWorld::Class &cls = mPtr.getClass();
Ogre::Vector3 movement(0.0f); osg::Vec3f movement(0.f, 0.f, 0.f);
updateMagicEffects(); updateMagicEffects();
@ -1403,23 +1403,23 @@ void CharacterController::update(float duration)
} }
} }
Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3());
vec.normalise(); vec.normalize();
if(mHitState != CharState_None && mJumpState == JumpState_None) if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = Ogre::Vector3(0.0f); vec = osg::Vec3f(0.f, 0.f, 0.f);
Ogre::Vector3 rot = cls.getRotationVector(mPtr); Ogre::Vector3 rot = cls.getRotationVector(mPtr);
mMovementSpeed = cls.getSpeed(mPtr); mMovementSpeed = cls.getSpeed(mPtr);
vec.x *= mMovementSpeed; vec.x() *= mMovementSpeed;
vec.y *= mMovementSpeed; vec.y() *= mMovementSpeed;
CharacterState movestate = CharState_None; CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle; CharacterState idlestate = CharState_SpecialIdle;
bool forcestateupdate = false; bool forcestateupdate = false;
mHasMovedInXY = std::abs(vec[0])+std::abs(vec[1]) > 0.0f; mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
isrunning = isrunning && mHasMovedInXY; isrunning = isrunning && mHasMovedInXY;
@ -1482,7 +1482,7 @@ void CharacterController::update(float duration)
cls.getCreatureStats(mPtr).setFatigue(fatigue); cls.getCreatureStats(mPtr).setFatigue(fatigue);
if(sneak || inwater || flying) if(sneak || inwater || flying)
vec.z = 0.0f; vec.z() = 0.0f;
if (inwater || flying) if (inwater || flying)
cls.getCreatureStats(mPtr).land(); cls.getCreatureStats(mPtr).land();
@ -1505,22 +1505,23 @@ void CharacterController::update(float duration)
static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat();
float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f;
factor = std::min(1.f, factor); factor = std::min(1.f, factor);
vec.x *= factor; vec.x() *= factor;
vec.y *= factor; vec.y() *= factor;
vec.z = 0.0f; vec.z() = 0.0f;
} }
else if(vec.z > 0.0f && mJumpState == JumpState_None) else if(vec.z() > 0.0f && mJumpState == JumpState_None)
{ {
// Started a jump. // Started a jump.
float z = cls.getJump(mPtr); float z = cls.getJump(mPtr);
if (z > 0) if (z > 0)
{ {
if(vec.x == 0 && vec.y == 0) if(vec.x() == 0 && vec.y() == 0)
vec = Ogre::Vector3(0.0f, 0.0f, z); vec = osg::Vec3f(0.0f, 0.0f, z);
else else
{ {
Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); osg::Vec3f lat (vec.x(), vec.y(), 0.0f);
vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; lat.normalize();
vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f;
} }
// advance acrobatics // advance acrobatics
@ -1544,7 +1545,7 @@ void CharacterController::update(float duration)
{ {
forcestateupdate = true; forcestateupdate = true;
mJumpState = JumpState_Landing; mJumpState = JumpState_Landing;
vec.z = 0.0f; vec.z() = 0.0f;
float height = cls.getCreatureStats(mPtr).land(); float height = cls.getCreatureStats(mPtr).land();
float healthLost = getFallDamage(mPtr, height); float healthLost = getFallDamage(mPtr, height);
@ -1575,28 +1576,28 @@ void CharacterController::update(float duration)
else else
{ {
mJumpState = JumpState_None; mJumpState = JumpState_None;
vec.z = 0.0f; vec.z() = 0.0f;
inJump = false; inJump = false;
if(std::abs(vec.x/2.0f) > std::abs(vec.y)) if(std::abs(vec.x()/2.0f) > std::abs(vec.y()))
{ {
if(vec.x > 0.0f) if(vec.x() > 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
: (sneak ? CharState_SneakRight : (sneak ? CharState_SneakRight
: (isrunning ? CharState_RunRight : CharState_WalkRight))); : (isrunning ? CharState_RunRight : CharState_WalkRight)));
else if(vec.x < 0.0f) else if(vec.x() < 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft)
: (sneak ? CharState_SneakLeft : (sneak ? CharState_SneakLeft
: (isrunning ? CharState_RunLeft : CharState_WalkLeft))); : (isrunning ? CharState_RunLeft : CharState_WalkLeft)));
} }
else if(vec.y != 0.0f) else if(vec.y() != 0.0f)
{ {
if(vec.y > 0.0f) if(vec.y() > 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
: (sneak ? CharState_SneakForward : (sneak ? CharState_SneakForward
: (isrunning ? CharState_RunForward : CharState_WalkForward))); : (isrunning ? CharState_RunForward : CharState_WalkForward)));
else if(vec.y < 0.0f) else if(vec.y() < 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
: (sneak ? CharState_SneakBack : (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack))); : (isrunning ? CharState_RunBack : CharState_WalkBack)));
@ -1676,7 +1677,7 @@ void CharacterController::update(float duration)
} }
else else
// We must always queue movement, even if there is none, to apply gravity. // We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, Ogre::Vector3(0.0f)); world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
movement = vec; movement = vec;
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
@ -1688,7 +1689,7 @@ void CharacterController::update(float duration)
} }
else if(cls.getCreatureStats(mPtr).isDead()) else if(cls.getCreatureStats(mPtr).isDead())
{ {
world->queueMovement(mPtr, Ogre::Vector3(0.0f)); world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
} }
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration);
@ -1702,15 +1703,15 @@ void CharacterController::update(float duration)
{ {
float l = moved.length(); float l = moved.length();
if((movement.x < 0.0f && movement.x < moved.x()*2.0f) || if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) ||
(movement.x > 0.0f && movement.x > moved.x()*2.0f)) (movement.x() > 0.0f && movement.x() > moved.x()*2.0f))
moved.x() = movement.x; moved.x() = movement.x();
if((movement.y < 0.0f && movement.y < moved.y()*2.0f) || if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) ||
(movement.y > 0.0f && movement.y > moved.y()*2.0f)) (movement.y() > 0.0f && movement.y() > moved.y()*2.0f))
moved.y() = movement.y; moved.y() = movement.y();
if((movement.z < 0.0f && movement.z < moved.z()*2.0f) || if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) ||
(movement.z > 0.0f && movement.z > moved.z()*2.0f)) (movement.z() > 0.0f && movement.z() > moved.z()*2.0f))
moved.z() = movement.z; moved.z() = movement.z();
// but keep the original speed // but keep the original speed
float newLength = moved.length(); float newLength = moved.length();
if (newLength > 0) if (newLength > 0)
@ -1722,7 +1723,7 @@ void CharacterController::update(float duration)
// Update movement // Update movement
if(mMovementAnimationControlled && mPtr.getClass().isActor()) if(mMovementAnimationControlled && mPtr.getClass().isActor())
world->queueMovement(mPtr, Ogre::Vector3(moved.x(), moved.y(), moved.z())); world->queueMovement(mPtr, moved);
mSkipAnim = false; mSkipAnim = false;

View file

@ -1,6 +1,8 @@
#ifndef GAME_MWMECHANICS_MOVEMENT_H #ifndef GAME_MWMECHANICS_MOVEMENT_H
#define GAME_MWMECHANICS_MOVEMENT_H #define GAME_MWMECHANICS_MOVEMENT_H
#include <osg/Vec3f>
namespace MWMechanics namespace MWMechanics
{ {
/// Desired movement for an actor /// Desired movement for an actor
@ -14,6 +16,11 @@ namespace MWMechanics
mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mPosition[0] = mPosition[1] = mPosition[2] = 0.0f;
mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f;
} }
osg::Vec3f asVec3()
{
return osg::Vec3f(mPosition[0], mPosition[1], mPosition[2]);
}
}; };
} }

View file

@ -0,0 +1,152 @@
#include "actor.hpp"
#include <osg/io_utils>
#include <osg/PositionAttitudeTransform>
#include <btBulletCollisionCommon.h>
#include "../mwworld/class.hpp"
#include <components/nifbullet/bulletnifloader.hpp>
#include "convert.hpp"
#include "collisiontype.hpp"
namespace MWPhysics
{
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<NifBullet::BulletShapeInstance> shape, btDynamicsWorld* world)
: mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)
, mDynamicsWorld(world)
{
mPtr = ptr;
mHalfExtents = shape->mCollisionBoxHalfExtents;
mMeshTranslation = shape->mCollisionBoxTranslate;
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
{
// Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work)
mShape.reset(new btCylinderShapeZ(toBullet(mHalfExtents)));
}
else
mShape.reset(new btBoxShape(toBullet(mHalfExtents)));
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
updateRotation();
updateScale();
// already called by updateScale()
//updatePosition();
updateCollisionMask();
}
Actor::~Actor()
{
if (mCollisionObject.get())
mDynamicsWorld->removeCollisionObject(mCollisionObject.get());
}
void Actor::enableCollisionMode(bool collision)
{
mInternalCollisionMode = collision;
}
void Actor::enableCollisionBody(bool collision)
{
if (mExternalCollisionMode != collision)
{
mExternalCollisionMode = collision;
updateCollisionMask();
}
}
void Actor::updateCollisionMask()
{
mDynamicsWorld->removeCollisionObject(mCollisionObject.get());
int collisionMask = CollisionType_World | CollisionType_HeightMap;
if (mExternalCollisionMode)
collisionMask |= CollisionType_Actor | CollisionType_Projectile;
if (mCanWaterWalk)
collisionMask |= CollisionType_Water;
mDynamicsWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
}
void Actor::updatePosition()
{
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
btTransform tr = mCollisionObject->getWorldTransform();
osg::Vec3f scaledTranslation = osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + position;
tr.setOrigin(toBullet(newPosition));
mCollisionObject->setWorldTransform(tr);
}
void Actor::updateRotation ()
{
btTransform tr = mCollisionObject->getWorldTransform();
tr.setRotation(toBullet(mPtr.getRefData().getBaseNode()->getAttitude()));
mCollisionObject->setWorldTransform(tr);
}
void Actor::updateScale()
{
float scale = mPtr.getCellRef().getScale();
osg::Vec3f scaleVec(scale,scale,scale);
if (!mPtr.getClass().isNpc())
mPtr.getClass().adjustScale(mPtr, scaleVec);
mScale = scaleVec;
mShape->setLocalScaling(toBullet(mScale));
updatePosition();
}
osg::Vec3f Actor::getHalfExtents() const
{
return osg::componentMultiply(mHalfExtents, mScale);
}
void Actor::setInertialForce(const osg::Vec3f &force)
{
mForce = force;
}
void Actor::setOnGround(bool grounded)
{
mOnGround = grounded;
}
bool Actor::isWalkingOnWater() const
{
return mWalkingOnWater;
}
void Actor::setWalkingOnWater(bool walkingOnWater)
{
mWalkingOnWater = walkingOnWater;
}
void Actor::setCanWaterWalk(bool waterWalk)
{
if (waterWalk != mCanWaterWalk)
{
mCanWaterWalk = waterWalk;
updateCollisionMask();
}
}
}

View file

@ -0,0 +1,125 @@
#ifndef OPENMW_MWPHYSICS_ACTOR_H
#define OPENMW_MWPHYSICS_ACTOR_H
#include <memory>
#include "../mwworld/ptr.hpp"
#include <osg/Vec3f>
#include <osg/ref_ptr>
class btDynamicsWorld;
class btCollisionShape;
class btCollisionObject;
namespace NifBullet
{
class BulletShapeInstance;
}
namespace MWPhysics
{
class PtrHolder
{
public:
virtual ~PtrHolder() {}
protected:
MWWorld::Ptr mPtr;
};
class Actor : public PtrHolder
{
public:
Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<NifBullet::BulletShapeInstance> shape, btDynamicsWorld* world);
~Actor();
/**
* Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry.
*/
void enableCollisionMode(bool collision);
bool getCollisionMode() const
{
return mInternalCollisionMode;
}
/**
* Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor.
*/
void enableCollisionBody(bool collision);
void updateScale();
void updateRotation();
void updatePosition();
/**
* Returns the (scaled) half extents
*/
osg::Vec3f getHalfExtents() const;
/**
* Sets the current amount of inertial force (incl. gravity) affecting this physic actor
*/
void setInertialForce(const osg::Vec3f &force);
/**
* Gets the current amount of inertial force (incl. gravity) affecting this physic actor
*/
const osg::Vec3f &getInertialForce() const
{
return mForce;
}
void setOnGround(bool grounded);
bool getOnGround() const
{
return mInternalCollisionMode && mOnGround;
}
btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
/// Sets whether this actor should be able to collide with the water surface
void setCanWaterWalk(bool waterWalk);
/// Sets whether this actor has been walking on the water surface in the last frame
void setWalkingOnWater(bool walkingOnWater);
bool isWalkingOnWater() const;
private:
/// Removes then re-adds the collision object to the dynamics world
void updateCollisionMask();
bool mCanWaterWalk;
bool mWalkingOnWater;
std::auto_ptr<btCollisionShape> mShape;
std::auto_ptr<btCollisionObject> mCollisionObject;
osg::Vec3f mMeshTranslation;
osg::Vec3f mHalfExtents;
osg::Vec3f mScale;
osg::Vec3f mPosition;
osg::Vec3f mForce;
bool mOnGround;
bool mInternalCollisionMode;
bool mExternalCollisionMode;
btDynamicsWorld* mDynamicsWorld;
Actor(const Actor&);
Actor& operator=(const Actor&);
};
}
#endif

View file

@ -0,0 +1,17 @@
#ifndef OPENMW_MWPHYSICS_COLLISIONTYPE_H
#define OPENMW_MWPHYSICS_COLLISIONTYPE_H
namespace MWPhysics
{
enum CollisionType {
CollisionType_World = 1<<0,
CollisionType_Actor = 1<<1,
CollisionType_HeightMap = 1<<2,
CollisionType_Projectile = 1<<4,
CollisionType_Water = 1<<5
};
}
#endif

View file

@ -0,0 +1,35 @@
#ifndef OPENMW_MWPHYSICS_CONVERT_H
#define OPENMW_MWPHYSICS_CONVERT_H
#include <LinearMath/btVector3.h>
#include <LinearMath/btQuaternion.h>
#include <osg/Vec3f>
#include <osg/Quat>
namespace MWPhysics
{
inline btVector3 toBullet(const osg::Vec3f& vec)
{
return btVector3(vec.x(), vec.y(), vec.z());
}
inline btQuaternion toBullet(const osg::Quat& quat)
{
return btQuaternion(quat.x(), quat.y(), quat.z(), quat.w());
}
inline osg::Vec3f toOsg(const btVector3& vec)
{
return osg::Vec3f(vec.x(), vec.y(), vec.z());
}
inline osg::Quat toOsg(const btQuaternion& quat)
{
return osg::Quat(quat.x(), quat.y(), quat.z(), quat.w());
}
}
#endif

View file

@ -3,15 +3,17 @@
#include <stdexcept> #include <stdexcept>
#include <osg/Group> #include <osg/Group>
#include <osg/PositionAttitudeTransform>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h> #include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <components/nifbullet/bulletshapemanager.hpp>
#include <components/nifbullet/bulletnifloader.hpp> #include <components/nifbullet/bulletnifloader.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/esm/loadgmst.hpp> #include <components/esm/loadgmst.hpp>
#include "../mwbase/world.hpp" // FIXME #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
@ -28,6 +30,11 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "actor.hpp"
#include "convert.hpp"
#include "trace.h"
namespace namespace
{ {
@ -89,16 +96,17 @@ namespace MWPhysics
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8; static const int sMaxIterations = 8;
// FIXME: move to a separate file
class MovementSolver class MovementSolver
{ {
private: private:
static float getSlope(const Ogre::Vector3 &normal) static float getSlope(const osg::Vec3f &normal)
{ {
return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f)));
} }
static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, static bool stepMove(btCollisionObject *colobj, osg::Vec3f &position,
const Ogre::Vector3 &toMove, float &remainingTime /*, OEngine::Physic::PhysicEngine *engine*/) const osg::Vec3f &toMove, float &remainingTime, btDynamicsWorld* dynamicsWorld)
{ {
/* /*
* Slide up an incline or set of stairs. Should be called only after a * Slide up an incline or set of stairs. Should be called only after a
@ -144,11 +152,9 @@ namespace MWPhysics
* +--+ +-------- * +--+ +--------
* ============================================== * ==============================================
*/ */
return false; ActorTracer tracer, stepper;
#if 0
OEngine::Physic::ActorTracer tracer, stepper;
stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSizeUp), engine); stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), dynamicsWorld);
if(stepper.mFraction < std::numeric_limits<float>::epsilon()) if(stepper.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount return false; // didn't even move the smallest representable amount
// (TODO: shouldn't this be larger? Why bother with such a small amount?) // (TODO: shouldn't this be larger? Why bother with such a small amount?)
@ -166,7 +172,7 @@ namespace MWPhysics
* +--+ * +--+
* ============================================== * ==============================================
*/ */
tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, engine); tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, dynamicsWorld);
if(tracer.mFraction < std::numeric_limits<float>::epsilon()) if(tracer.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount return false; // didn't even move the smallest representable amount
@ -185,7 +191,7 @@ namespace MWPhysics
* +--+ +--+ * +--+ +--+
* ============================================== * ==============================================
*/ */
stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSizeDown), engine); stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), dynamicsWorld);
if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope)
{ {
// don't allow stepping up other actors // don't allow stepping up other actors
@ -201,41 +207,39 @@ namespace MWPhysics
// moved between 0 and just under sStepSize distance but slope was too great, // moved between 0 and just under sStepSize distance but slope was too great,
// or moved full sStepSize distance (FIXME: is this a bug?) // or moved full sStepSize distance (FIXME: is this a bug?)
#endif
return false; return false;
} }
///Project a vector u on another vector v ///Project a vector u on another vector v
static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v) static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
{ {
return v * u.dotProduct(v); return v * (u * v);
// ^ dot product
} }
///Helper for computing the character sliding ///Helper for computing the character sliding
static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal) static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
{ {
return direction - project(direction, planeNormal); return direction - project(direction, planeNormal);
} }
static inline osg::Vec3f reflect(const osg::Vec3& velocity, const osg::Vec3f& normal)
{
return (normal * (normal * velocity)) * 2 - velocity;
}
public: public:
static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, /*OEngine::Physic::PhysicEngine *engine,*/ float maxHeight) static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, Actor* actor, btDynamicsWorld* dynamicsWorld, float maxHeight)
{ {
const ESM::Position &refpos = ptr.getRefData().getPosition(); osg::Vec3f position(ptr.getRefData().getPosition().asVec3());
Ogre::Vector3 position(refpos.pos);
return position; ActorTracer tracer;
#if 0 tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), dynamicsWorld);
OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
if (!physicActor)
return position;
OEngine::Physic::ActorTracer tracer;
tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine);
if(tracer.mFraction >= 1.0f) if(tracer.mFraction >= 1.0f)
{ {
physicActor->setOnGround(false); actor->setOnGround(false);
return position; return position;
} }
else else
@ -243,79 +247,73 @@ namespace MWPhysics
// Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Check if we actually found a valid spawn point (use an infinitely thin ray this time).
// Required for some broken door destinations in Morrowind.esm, where the spawn point // Required for some broken door destinations in Morrowind.esm, where the spawn point
// intersects with other geometry if the actor's base is taken into account // intersects with other geometry if the actor's base is taken into account
btVector3 from = BtOgre::Convert::toBullet(position); btVector3 from = toBullet(position);
btVector3 to = from - btVector3(0,0,maxHeight); btVector3 to = from - btVector3(0,0,maxHeight);
btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
resultCallback1.m_collisionFilterGroup = 0xff; resultCallback1.m_collisionFilterGroup = 0xff;
resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap;
engine->mDynamicsWorld->rayTest(from, to, resultCallback1); dynamicsWorld->rayTest(from, to, resultCallback1);
if (resultCallback1.hasHit() && if (resultCallback1.hasHit() &&
(BtOgre::Convert::toOgre(resultCallback1.m_hitPointWorld).distance(tracer.mEndPos) > 30 ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 30
|| getSlope(tracer.mPlaneNormal) > sMaxSlope)) || getSlope(tracer.mPlaneNormal) > sMaxSlope))
{ {
physicActor->setOnGround(getSlope(BtOgre::Convert::toOgre(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); actor->setOnGround(getSlope(toOsg(resultCallback1.m_hitNormalWorld)) <= sMaxSlope);
return BtOgre::Convert::toOgre(resultCallback1.m_hitPointWorld) + Ogre::Vector3(0,0,1.f); return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f);
} }
physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); actor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope);
return tracer.mEndPos; return tracer.mEndPos;
} }
#endif
} }
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
bool isFlying, float waterlevel, float slowFall /*, OEngine::Physic::PhysicEngine *engine*/ bool isFlying, float waterlevel, float slowFall, btDynamicsWorld* dynamicsWorld
, std::map<std::string, std::string>& collisionTracker , std::map<std::string, std::string>& collisionTracker
, std::map<std::string, std::string>& standingCollisionTracker) , std::map<std::string, std::string>& standingCollisionTracker)
{ {
const ESM::Position &refpos = ptr.getRefData().getPosition(); const ESM::Position& refpos = ptr.getRefData().getPosition();
Ogre::Vector3 position(refpos.pos); osg::Vec3f position(refpos.asVec3());
return position;
#if 0
// Early-out for totally static creatures // Early-out for totally static creatures
// (Not sure if gravity should still apply?) // (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr)) if (!ptr.getClass().isMobile(ptr))
return position; return position;
OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
if (!physicActor)
return position;
// Reset per-frame data // Reset per-frame data
physicActor->setWalkingOnWater(false); physicActor->setWalkingOnWater(false);
// Anything to collide with? // Anything to collide with?
if(!physicActor->getCollisionMode()) if(!physicActor->getCollisionMode())
{ {
return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
* movement * time; ) * movement * time;
} }
btCollisionObject *colobj = physicActor->getCollisionBody(); btCollisionObject *colobj = physicActor->getCollisionObject();
Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); osg::Vec3f halfExtents = physicActor->getHalfExtents();
position.z += halfExtents.z; position.z() += halfExtents.z();
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fSwimHeightScale")->getFloat(); .find("fSwimHeightScale")->getFloat();
float swimlevel = waterlevel + halfExtents.z - (halfExtents.z * 2 * fSwimHeightScale); float swimlevel = waterlevel + halfExtents.z() - (halfExtents.z() * 2 * fSwimHeightScale);
OEngine::Physic::ActorTracer tracer; ActorTracer tracer;
Ogre::Vector3 inertia = physicActor->getInertialForce(); osg::Vec3f inertia = physicActor->getInertialForce();
Ogre::Vector3 velocity; osg::Vec3f velocity;
if(position.z < swimlevel || isFlying) if(position.z() < swimlevel || isFlying)
{ {
velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
} }
else else
{ {
velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
if (velocity.z > 0.f) if (velocity.z() > 0.f)
inertia = velocity; inertia = velocity;
if(!physicActor->getOnGround()) if(!physicActor->getOnGround())
{ {
@ -327,16 +325,16 @@ namespace MWPhysics
// Now that we have the effective movement vector, apply wind forces to it // Now that we have the effective movement vector, apply wind forces to it
if (MWBase::Environment::get().getWorld()->isInStorm()) if (MWBase::Environment::get().getWorld()->isInStorm())
{ {
Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
Ogre::Degree angle = stormDirection.angleBetween(velocity); float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity));
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fStromWalkMult")->getFloat(); .find("fStromWalkMult")->getFloat();
velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f)); velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
} }
Ogre::Vector3 origVelocity = velocity; osg::Vec3f origVelocity = velocity;
Ogre::Vector3 newPosition = position; osg::Vec3f newPosition = position;
/* /*
* A loop to find newPosition using tracer, if successful different from the starting position. * A loop to find newPosition using tracer, if successful different from the starting position.
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
@ -345,27 +343,27 @@ namespace MWPhysics
float remainingTime = time; float remainingTime = time;
for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
{ {
Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; osg::Vec3f nextpos = newPosition + velocity * remainingTime;
// If not able to fly, don't allow to swim up into the air // If not able to fly, don't allow to swim up into the air
if(newPosition.z < swimlevel && if(newPosition.z() < swimlevel &&
!isFlying && // can't fly !isFlying && // can't fly
nextpos.z > swimlevel && // but about to go above water nextpos.z() > swimlevel && // but about to go above water
newPosition.z <= swimlevel) newPosition.z() <= swimlevel)
{ {
const Ogre::Vector3 down(0,0,-1); const osg::Vec3f down(0,0,-1);
Ogre::Real movelen = velocity.normalise(); float movelen = velocity.normalize();
Ogre::Vector3 reflectdir = velocity.reflect(down); osg::Vec3f reflectdir = reflect(velocity, down);
reflectdir.normalise(); reflectdir.normalize();
velocity = slide(reflectdir, down)*movelen; velocity = slide(reflectdir, down)*movelen;
// NOTE: remainingTime is unchanged before the loop continues // NOTE: remainingTime is unchanged before the loop continues
continue; // velocity updated, calculate nextpos again continue; // velocity updated, calculate nextpos again
} }
if(newPosition.squaredDistance(nextpos) > 0.0001) if((newPosition - nextpos).length2() > 0.0001)
{ {
// trace to where character would go if there were no obstructions // trace to where character would go if there were no obstructions
tracer.doTrace(colobj, newPosition, nextpos, engine); tracer.doTrace(colobj, newPosition, nextpos, dynamicsWorld);
// check for obstructions // check for obstructions
if(tracer.mFraction >= 1.0f) if(tracer.mFraction >= 1.0f)
@ -375,11 +373,13 @@ namespace MWPhysics
} }
else else
{ {
/*
const btCollisionObject* standingOn = tracer.mHitObject; const btCollisionObject* standingOn = tracer.mHitObject;
if (const OEngine::Physic::RigidBody* body = dynamic_cast<const OEngine::Physic::RigidBody*>(standingOn)) if (const OEngine::Physic::RigidBody* body = dynamic_cast<const OEngine::Physic::RigidBody*>(standingOn))
{ {
collisionTracker[ptr.getRefData().getHandle()] = body->mName; collisionTracker[ptr.getRefData().getHandle()] = body->mName;
} }
*/
} }
} }
else else
@ -395,62 +395,68 @@ namespace MWPhysics
} }
Ogre::Vector3 oldPosition = newPosition; osg::Vec3f oldPosition = newPosition;
// We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over)
// NOTE: stepMove modifies newPosition if successful // NOTE: stepMove modifies newPosition if successful
bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, engine); bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, dynamicsWorld);
if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent
result = stepMove(colobj, newPosition, velocity.normalisedCopy()*10.f, remainingTime, engine); {
osg::Vec3f normalizedVelocity = velocity;
normalizedVelocity.normalize();
result = stepMove(colobj, newPosition, normalizedVelocity*10.f, remainingTime, dynamicsWorld);
}
if(result) if(result)
{ {
// don't let pure water creatures move out of water after stepMove // don't let pure water creatures move out of water after stepMove
if (ptr.getClass().isPureWaterCreature(ptr) if (ptr.getClass().isPureWaterCreature(ptr)
&& newPosition.z + halfExtents.z > waterlevel) && newPosition.z() + halfExtents.z() > waterlevel)
newPosition = oldPosition; newPosition = oldPosition;
} }
else else
{ {
// Can't move this way, try to find another spot along the plane // Can't move this way, try to find another spot along the plane
Ogre::Vector3 direction = velocity; osg::Vec3f direction = velocity;
Ogre::Real movelen = direction.normalise(); float movelen = direction.normalize();
Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal); osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal);
reflectdir.normalise(); reflectdir.normalize();
Ogre::Vector3 newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; osg::Vec3f newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen;
if ((newVelocity-velocity).squaredLength() < 0.01) if ((newVelocity-velocity).length2() < 0.01)
break;
if (velocity.dotProduct(origVelocity) <= 0.f)
break; break;
if ((velocity * origVelocity) <= 0.f)
break; // ^ dot product
velocity = newVelocity; velocity = newVelocity;
// Do not allow sliding upward if there is gravity. Stepping will have taken // Do not allow sliding upward if there is gravity. Stepping will have taken
// care of that. // care of that.
if(!(newPosition.z < swimlevel || isFlying)) if(!(newPosition.z() < swimlevel || isFlying))
velocity.z = std::min(velocity.z, 0.0f); velocity.z() = std::min(velocity.z(), 0.0f);
} }
} }
bool isOnGround = false; bool isOnGround = false;
if (!(inertia.z > 0.f) && !(newPosition.z < swimlevel)) if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
{ {
Ogre::Vector3 from = newPosition; osg::Vec3f from = newPosition;
Ogre::Vector3 to = newPosition - (physicActor->getOnGround() ? osg::Vec3f to = newPosition - (physicActor->getOnGround() ?
Ogre::Vector3(0,0,sStepSizeDown+2.f) : Ogre::Vector3(0,0,2.f)); osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f));
tracer.doTrace(colobj, from, to, engine); tracer.doTrace(colobj, from, to, dynamicsWorld);
if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope
&& tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor)
{ {
/*
const btCollisionObject* standingOn = tracer.mHitObject; const btCollisionObject* standingOn = tracer.mHitObject;
if (const OEngine::Physic::RigidBody* body = dynamic_cast<const OEngine::Physic::RigidBody*>(standingOn)) if (const OEngine::Physic::RigidBody* body = dynamic_cast<const OEngine::Physic::RigidBody*>(standingOn))
{ {
standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName; standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName;
} }
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true); physicActor->setWalkingOnWater(true);
*/
if (!isFlying) if (!isFlying)
newPosition.z = tracer.mEndPos.z + 1.0f; newPosition.z() = tracer.mEndPos.z() + 1.0f;
isOnGround = true; isOnGround = true;
} }
@ -461,13 +467,13 @@ namespace MWPhysics
// so that we do not stay suspended in air indefinitely. // so that we do not stay suspended in air indefinitely.
if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
{ {
if (Ogre::Vector3(velocity.x, velocity.y, 0).squaredLength() < 100.f*100.f) if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
{ {
btVector3 aabbMin, aabbMax; btVector3 aabbMin, aabbMax;
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
btVector3 center = (aabbMin + aabbMax) / 2.f; btVector3 center = (aabbMin + aabbMax) / 2.f;
inertia = Ogre::Vector3(position.x - center.x(), position.y - center.y(), 0); inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0);
inertia.normalise(); inertia.normalize();
inertia *= 100; inertia *= 100;
} }
} }
@ -476,20 +482,19 @@ namespace MWPhysics
} }
} }
if(isOnGround || newPosition.z < swimlevel || isFlying) if(isOnGround || newPosition.z() < swimlevel || isFlying)
physicActor->setInertialForce(Ogre::Vector3(0.0f)); physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
else else
{ {
inertia.z += time * -627.2f; inertia.z() += time * -627.2f;
if (inertia.z < 0) if (inertia.z() < 0)
inertia.z *= slowFall; inertia.z() *= slowFall;
physicActor->setInertialForce(inertia); physicActor->setInertialForce(inertia);
} }
physicActor->setOnGround(isOnGround); physicActor->setOnGround(isOnGround);
newPosition.z -= halfExtents.z; // remove what was added at the beginning newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
return newPosition; return newPosition;
#endif
} }
}; };
@ -543,11 +548,62 @@ namespace MWPhysics
btCollisionObject* mCollisionObject; btCollisionObject* mCollisionObject;
}; };
// --------------------------------------------------------------
class Object : public PtrHolder
{
public:
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<NifBullet::BulletShapeInstance> shapeInstance)
: mShapeInstance(shapeInstance)
{
mPtr = ptr;
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
setScale(ptr.getCellRef().getScale());
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
const float* pos = ptr.getRefData().getPosition().pos;
setOrigin(btVector3(pos[0], pos[1], pos[2]));
}
void updatePtr(const MWWorld::Ptr& updated)
{
mPtr = updated;
}
void setScale(float scale)
{
mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale));
}
void setRotation(const btQuaternion& quat)
{
mCollisionObject->getWorldTransform().setRotation(quat);
}
void setOrigin(const btVector3& vec)
{
mCollisionObject->getWorldTransform().setOrigin(vec);
}
btCollisionObject* getCollisionObject()
{
return mCollisionObject.get();
}
private:
std::auto_ptr<btCollisionObject> mCollisionObject;
osg::ref_ptr<NifBullet::BulletShapeInstance> mShapeInstance;
};
// --------------------------------------------------------------- // ---------------------------------------------------------------
PhysicsSystem::PhysicsSystem(const VFS::Manager* vfs, osg::ref_ptr<osg::Group> parentNode)
PhysicsSystem::PhysicsSystem(osg::ref_ptr<osg::Group> parentNode) : mShapeManager(new NifBullet::BulletShapeManager(vfs))
: mTimeAccum(0.0f) , mTimeAccum(0.0f)
, mWaterEnabled(false) , mWaterEnabled(false)
, mWaterHeight(0) , mWaterHeight(0)
, mDebugDrawEnabled(false) , mDebugDrawEnabled(false)
@ -577,6 +633,17 @@ namespace MWPhysics
delete it->second; delete it->second;
} }
for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it)
{
mDynamicsWorld->removeCollisionObject(it->second->getCollisionObject());
delete it->second;
}
for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it)
{
delete it->second;
}
delete mDynamicsWorld; delete mDynamicsWorld;
delete mSolver; delete mSolver;
delete mCollisionConfiguration; delete mCollisionConfiguration;
@ -668,9 +735,13 @@ namespace MWPhysics
return std::vector<std::string>();//mEngine->getCollisions(ptr.getRefData().getBaseNodeOld()->getName(), collisionGroup, collisionMask); return std::vector<std::string>();//mEngine->getCollisions(ptr.getRefData().getBaseNodeOld()->getName(), collisionGroup, collisionMask);
} }
Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight)
{ {
return Ogre::Vector3();//MovementSolver::traceDown(ptr, mEngine, maxHeight); ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3();
else
return MovementSolver::traceDown(ptr, found->second, mDynamicsWorld, maxHeight);
} }
void PhysicsSystem::addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts) void PhysicsSystem::addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts)
@ -693,45 +764,115 @@ namespace MWPhysics
} }
} }
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, bool placeable) void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh)
{ {
osg::ref_ptr<NifBullet::BulletShapeInstance> shapeInstance = mShapeManager->createInstance(mesh);
if (!shapeInstance->getCollisionShape())
return;
Object *obj = new Object(ptr, shapeInstance);
mObjects.insert(std::make_pair(ptr, obj));
mDynamicsWorld->addCollisionObject(obj->getCollisionObject(), CollisionType_World,
CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
}
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
mDynamicsWorld->removeCollisionObject(found->second->getCollisionObject());
delete found->second;
mObjects.erase(found);
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
delete foundActor->second;
mActors.erase(foundActor);
}
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
float scale = ptr.getCellRef().getScale();
if (found != mObjects.end())
{
found->second->setScale(scale);
mDynamicsWorld->updateSingleAabb(found->second->getCollisionObject());
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
foundActor->second->updateScale();
// no aabb update needed (DISABLE_DEACTIVATION)
return;
}
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
found->second->setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
mDynamicsWorld->updateSingleAabb(found->second->getCollisionObject());
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
foundActor->second->updateRotation();
// no aabb update needed (DISABLE_DEACTIVATION)
return;
}
}
void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
found->second->setOrigin(toBullet(ptr.getRefData().getPosition().asVec3()));
mDynamicsWorld->updateSingleAabb(found->second->getCollisionObject());
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
foundActor->second->updatePosition();
// no aabb update needed (DISABLE_DEACTIVATION)
return;
}
} }
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
{ {
osg::ref_ptr<NifBullet::BulletShapeInstance> shapeInstance = mShapeManager->createInstance(mesh);
Actor* actor = new Actor(ptr, shapeInstance, mDynamicsWorld);
mActors.insert(std::make_pair(ptr, actor));
} }
bool PhysicsSystem::toggleCollisionMode() bool PhysicsSystem::toggleCollisionMode()
{ {
/* ActorMap::iterator found = mActors.find(MWBase::Environment::get().getWorld()->getPlayerPtr());
for(std::map<std::string,OEngine::Physic::PhysicActor*>::iterator it = mEngine->mActorMap.begin(); it != mEngine->mActorMap.end();++it) if (found != mActors.end())
{ {
if (it->first=="player") bool cmode = found->second->getCollisionMode();
{ cmode = !cmode;
OEngine::Physic::PhysicActor* act = it->second; found->second->enableCollisionMode(cmode);
return cmode;
bool cmode = act->getCollisionMode();
if(cmode)
{
act->enableCollisionMode(false);
return false;
}
else
{
act->enableCollisionMode(true);
return true;
}
}
} }
throw std::logic_error ("can't find player");
*/
return false; return false;
} }
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement) void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
{ {
PtrVelocityList::iterator iter = mMovementQueue.begin(); PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter) for(;iter != mMovementQueue.end();++iter)
@ -764,8 +905,6 @@ namespace MWPhysics
mCollisions.clear(); mCollisions.clear();
mStandingCollisions.clear(); mStandingCollisions.clear();
/*
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
PtrVelocityList::iterator iter = mMovementQueue.begin(); PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter) for(;iter != mMovementQueue.end();++iter)
@ -786,19 +925,20 @@ namespace MWPhysics
Ogre::Vector3(iter->first.getRefData().getPosition().pos))) Ogre::Vector3(iter->first.getRefData().getPosition().pos)))
waterCollision = true; waterCollision = true;
OEngine::Physic::PhysicActor *physicActor = mEngine->getCharacter(iter->first.getRefData().getHandle()); ActorMap::iterator foundActor = mActors.find(iter->first);
if (!physicActor) // actor was already removed from the scene if (foundActor == mActors.end()) // actor was already removed from the scene
continue; continue;
Actor* physicActor = foundActor->second;
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
// Slow fall reduces fall speed by a factor of (effect magnitude / 200) // Slow fall reduces fall speed by a factor of (effect magnitude / 200)
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, osg::Vec3f newpos = MovementSolver::move(iter->first, physicActor, iter->second, mTimeAccum,
world->isFlying(iter->first), world->isFlying(iter->first),
waterlevel, slowFall, mEngine, mCollisions, mStandingCollisions); waterlevel, slowFall, mDynamicsWorld, mCollisions, mStandingCollisions);
float heightDiff = newpos.z - oldHeight; float heightDiff = newpos.z() - oldHeight;
if (heightDiff < 0) if (heightDiff < 0)
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
@ -806,8 +946,6 @@ namespace MWPhysics
mMovementResults.push_back(std::make_pair(iter->first, newpos)); mMovementResults.push_back(std::make_pair(iter->first, newpos));
} }
*/
mTimeAccum = 0.0f; mTimeAccum = 0.0f;
} }
mMovementQueue.clear(); mMovementQueue.clear();

View file

@ -1,5 +1,5 @@
#ifndef GAME_MWWORLD_PHYSICSSYSTEM_H #ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#define GAME_MWWORLD_PHYSICSSYSTEM_H #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#include <memory> #include <memory>
@ -21,34 +21,48 @@ namespace MWRender
class DebugDrawer; class DebugDrawer;
} }
namespace NifBullet
{
class BulletShapeManager;
}
namespace VFS
{
class Manager;
}
class btSequentialImpulseConstraintSolver; class btSequentialImpulseConstraintSolver;
class btDiscreteDynamicsWorld; class btDiscreteDynamicsWorld;
namespace MWPhysics namespace MWPhysics
{ {
typedef std::vector<std::pair<MWWorld::Ptr,Ogre::Vector3> > PtrVelocityList; typedef std::vector<std::pair<MWWorld::Ptr,osg::Vec3f> > PtrVelocityList;
enum CollisionType {
CollisionType_World = 1<<0,
CollisionType_Actor = 1<<1,
CollisionType_HeightMap = 1<<2,
CollisionType_Projectile = 1<<4,
CollisionType_Water = 1<<5
};
class HeightField; class HeightField;
class Object;
class Actor;
class PhysicsSystem class PhysicsSystem
{ {
public: public:
PhysicsSystem (osg::ref_ptr<osg::Group> parentNode); PhysicsSystem (const VFS::Manager* vfs, osg::ref_ptr<osg::Group> parentNode);
~PhysicsSystem (); ~PhysicsSystem ();
void enableWater(float height); void enableWater(float height);
void setWaterHeight(float height); void setWaterHeight(float height);
void disableWater(); void disableWater();
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, bool placeable=false); void addObject (const MWWorld::Ptr& ptr, const std::string& mesh);
// Object or Actor
void remove (const MWWorld::Ptr& ptr);
void updateScale (const MWWorld::Ptr& ptr);
void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (const MWWorld::Ptr& ptr);
// TODO
//void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
@ -61,7 +75,7 @@ namespace MWPhysics
void stepSimulation(float dt); void stepSimulation(float dt);
std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with
Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); osg::Vec3f traceDown(const MWWorld::Ptr &ptr, float maxHeight);
std::pair<std::string,Ogre::Vector3> getHitContact(const std::string &name, std::pair<std::string,Ogre::Vector3> getHitContact(const std::string &name,
const Ogre::Vector3 &origin, const Ogre::Vector3 &origin,
@ -76,7 +90,7 @@ namespace MWPhysics
/// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will
/// be overwritten. Valid until the next call to applyQueuedMovement. /// be overwritten. Valid until the next call to applyQueuedMovement.
void queueObjectMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &velocity); void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list. /// Apply all queued movements, then clear the list.
const PtrVelocityList& applyQueuedMovement(float dt); const PtrVelocityList& applyQueuedMovement(float dt);
@ -111,6 +125,14 @@ namespace MWPhysics
btCollisionDispatcher* mDispatcher; btCollisionDispatcher* mDispatcher;
btDiscreteDynamicsWorld* mDynamicsWorld; btDiscreteDynamicsWorld* mDynamicsWorld;
std::auto_ptr<NifBullet::BulletShapeManager> mShapeManager;
typedef std::map<MWWorld::Ptr, Object*> ObjectMap;
ObjectMap mObjects;
typedef std::map<MWWorld::Ptr, Actor*> ActorMap;
ActorMap mActors;
typedef std::map<std::pair<int, int>, HeightField*> HeightFieldMap; typedef std::map<std::pair<int, int>, HeightField*> HeightFieldMap;
HeightFieldMap mHeightFields; HeightFieldMap mHeightFields;
@ -121,9 +143,9 @@ namespace MWPhysics
// Tracks all movement collisions happening during a single frame. <actor handle, collided handle> // Tracks all movement collisions happening during a single frame. <actor handle, collided handle>
// This will detect e.g. running against a vertical wall. It will not detect climbing up stairs, // This will detect e.g. running against a vertical wall. It will not detect climbing up stairs,
// stepping up small objects, etc. // stepping up small objects, etc.
std::map<std::string, std::string> mCollisions; std::map<std::string, std::string> mCollisions; // FIXME: reimplement
std::map<std::string, std::string> mStandingCollisions; std::map<std::string, std::string> mStandingCollisions; // FIXME: reimplement
PtrVelocityList mMovementQueue; PtrVelocityList mMovementQueue;
PtrVelocityList mMovementResults; PtrVelocityList mMovementResults;

View file

@ -0,0 +1,128 @@
#include "trace.h"
#include <map>
#include <btBulletDynamicsCommon.h>
#include <btBulletCollisionCommon.h>
#include "collisiontype.hpp"
#include "actor.hpp"
#include "convert.hpp"
namespace MWPhysics
{
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
mMe(me), mUp(up), mMinSlopeDot(minSlopeDot)
{
}
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
{
if(convexResult.m_hitCollisionObject == mMe)
return btScalar( 1 );
btVector3 hitNormalWorld;
if(normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal;
else
{
///need to transform normal into worldspace
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
}
btScalar dotUp = mUp.dot(hitNormalWorld);
if(dotUp < mMinSlopeDot)
return btScalar(1);
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
}
protected:
btCollisionObject *mMe;
const btVector3 mUp;
const btScalar mMinSlopeDot;
};
void ActorTracer::doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btDynamicsWorld* world)
{
const btVector3 btstart = toBullet(start);
const btVector3 btend = toBullet(end);
const btTransform &trans = actor->getWorldTransform();
btTransform from(trans);
btTransform to(trans);
from.setOrigin(btstart);
to.setOrigin(btend);
ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0));
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
btCollisionShape *shape = actor->getCollisionShape();
assert(shape->isConvex());
world->convexSweepTest(static_cast<btConvexShape*>(shape),
from, to, newTraceCallback);
// Copy the hit data over to our trace results struct:
if(newTraceCallback.hasHit())
{
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
mFraction = newTraceCallback.m_closestHitFraction;
mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
mEndPos = (end-start)*mFraction + start;
mHitObject = newTraceCallback.m_hitCollisionObject;
}
else
{
mEndPos = end;
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
mFraction = 1.0f;
mHitObject = NULL;
}
}
void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btDynamicsWorld* world)
{
const btVector3 btstart(start.x(), start.y(), start.z()+1.0f);
const btVector3 btend(end.x(), end.y(), end.z()+1.0f);
const btTransform &trans = actor->getCollisionObject()->getWorldTransform();
btTransform from(trans.getBasis(), btstart);
btTransform to(trans.getBasis(), btend);
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), btstart-btend, btScalar(0.0));
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor;
btVector3 halfExtents = toBullet(actor->getHalfExtents());
halfExtents[2] = 1.0f;
btCylinderShapeZ base(halfExtents);
world->convexSweepTest(&base, from, to, newTraceCallback);
if(newTraceCallback.hasHit())
{
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
mFraction = newTraceCallback.m_closestHitFraction;
mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
mEndPos = (end-start)*mFraction + start;
mEndPos[2] += 1.0f;
}
else
{
mEndPos = end;
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
mFraction = 1.0f;
}
}
}

View file

@ -0,0 +1,27 @@
#ifndef OENGINE_BULLET_TRACE_H
#define OENGINE_BULLET_TRACE_H
#include <osg/Vec3f>
class btCollisionObject;
class btDynamicsWorld;
namespace MWPhysics
{
class Actor;
struct ActorTracer
{
osg::Vec3f mEndPos;
osg::Vec3f mPlaneNormal;
const btCollisionObject* mHitObject;
float mFraction;
void doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btDynamicsWorld* world);
void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btDynamicsWorld* world);
};
}
#endif

View file

@ -425,7 +425,7 @@ namespace MWScript
MWWorld::Ptr targetPtr; MWWorld::Ptr targetPtr;
if (creatureStats.getAiSequence().getCombatTarget (targetPtr)) if (creatureStats.getAiSequence().getCombatTarget (targetPtr))
{ {
if (targetPtr.getRefData().getHandle() == testedTargetId) if (targetPtr.getCellRef().getRefId() == testedTargetId)
targetsAreEqual = true; targetsAreEqual = true;
} }
runtime.push(int(targetsAreEqual)); runtime.push(int(targetsAreEqual));

View file

@ -811,10 +811,10 @@ namespace MWScript
const std::string script = ptr.getClass().getScript(ptr); const std::string script = ptr.getClass().getScript(ptr);
if(script.empty()) if(script.empty())
str<< ptr.getCellRef().getRefId()<<" ("<<ptr.getRefData().getHandle()<<") does not have a script."; str<< ptr.getCellRef().getRefId()<<" does not have a script.";
else else
{ {
str<< "Local variables for "<<ptr.getCellRef().getRefId()<<" ("<<ptr.getRefData().getHandle()<<")"; str<< "Local variables for "<<ptr.getCellRef().getRefId();
const Locals &locals = ptr.getRefData().getLocals(); const Locals &locals = ptr.getRefData().getLocals();
const Compiler::Locals &complocals = MWBase::Environment::get().getScriptManager()->getLocals(script); const Compiler::Locals &complocals = MWBase::Environment::get().getScriptManager()->getLocals(script);

View file

@ -6,25 +6,21 @@
#include "ptr.hpp" #include "ptr.hpp"
namespace ESM
{
class CellRef;
}
namespace MWWorld namespace MWWorld
{ {
/// List all (Ogre-)handles, then reset RefData::mBaseNode to 0. struct ListAndResetObjects
struct ListAndResetHandles
{ {
std::vector<Ogre::SceneNode*> mHandles; std::vector<MWWorld::Ptr> mObjects;
bool operator() (MWWorld::Ptr ptr) bool operator() (MWWorld::Ptr ptr)
{ {
Ogre::SceneNode* handle = ptr.getRefData().getBaseNodeOld(); if (ptr.getRefData().getBaseNode())
if (handle) {
mHandles.push_back (handle); ptr.getRefData().setBaseNode(NULL);
mObjects.push_back (ptr);
}
ptr.getRefData().setBaseNodeOld(0);
return true; return true;
} }
}; };

View file

@ -60,7 +60,7 @@ namespace
rot = rot * osg::Quat(y, osg::Vec3(0,-1,0)) * osg::Quat(x, osg::Vec3(-1,0,0)); rot = rot * osg::Quat(y, osg::Vec3(0,-1,0)) * osg::Quat(x, osg::Vec3(-1,0,0));
rendering.rotateObject(ptr, rot * worldRotQuat); rendering.rotateObject(ptr, rot * worldRotQuat);
//physics.rotateObject(ptr); physics.updateRotation(ptr);
} }
} }
@ -190,14 +190,13 @@ namespace MWWorld
void Scene::unloadCell (CellStoreCollection::iterator iter) void Scene::unloadCell (CellStoreCollection::iterator iter)
{ {
std::cout << "Unloading cell\n"; std::cout << "Unloading cell\n";
ListAndResetHandles functor; ListAndResetObjects functor;
(*iter)->forEach<ListAndResetHandles>(functor); (*iter)->forEach<ListAndResetObjects>(functor);
for (std::vector<Ogre::SceneNode*>::const_iterator iter2 (functor.mHandles.begin()); for (std::vector<MWWorld::Ptr>::const_iterator iter2 (functor.mObjects.begin());
iter2!=functor.mHandles.end(); ++iter2) iter2!=functor.mObjects.end(); ++iter2)
{ {
//Ogre::SceneNode* node = *iter2; mPhysics->remove(*iter2);
//mPhysics->removeObject (node->getName());
} }
if ((*iter)->getCell()->isExterior()) if ((*iter)->getCell()->isExterior())
@ -258,17 +257,16 @@ namespace MWWorld
insertCell (*cell, true, loadingListener); insertCell (*cell, true, loadingListener);
mRendering.addCell(cell); mRendering.addCell(cell);
#if 0
bool waterEnabled = cell->getCell()->hasWater(); bool waterEnabled = cell->getCell()->hasWater();
mRendering.setWaterEnabled(waterEnabled); //mRendering.setWaterEnabled(waterEnabled);
if (waterEnabled) if (waterEnabled)
{ {
mPhysics->enableWater(cell->getWaterLevel()); mPhysics->enableWater(cell->getWaterLevel());
mRendering.setWaterHeight(cell->getWaterLevel()); //mRendering.setWaterHeight(cell->getWaterLevel());
} }
else else
mPhysics->disableWater(); mPhysics->disableWater();
#endif
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell()); mRendering.configureAmbient(cell->getCell());
} }
@ -564,8 +562,8 @@ namespace MWWorld
{ {
MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getMechanicsManager()->remove (ptr);
MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr);
//mPhysics->removeObject (ptr.getRefData().getHandle()); mPhysics->remove(ptr);
//mRendering.removeObject (ptr); //Rendering.removeObject (ptr);
} }
bool Scene::isCellActive(const CellStore &cell) bool Scene::isCellActive(const CellStore &cell)

View file

@ -415,11 +415,11 @@ void WeatherManager::update(float duration, bool paused)
if (mIsStorm) if (mIsStorm)
{ {
MWWorld::Ptr player = world->getPlayerPtr(); MWWorld::Ptr player = world->getPlayerPtr();
Ogre::Vector3 playerPos (player.getRefData().getPosition().pos); osg::Vec3f playerPos (player.getRefData().getPosition().asVec3());
Ogre::Vector3 redMountainPos (19950, 72032, 27831); osg::Vec3f redMountainPos (19950, 72032, 27831);
mStormDirection = (playerPos - redMountainPos); mStormDirection = (playerPos - redMountainPos);
mStormDirection.z = 0; mStormDirection.z() = 0;
//mRendering->getSkyManager()->setStormDirection(mStormDirection); //mRendering->getSkyManager()->setStormDirection(mStormDirection);
} }
@ -844,7 +844,7 @@ bool WeatherManager::isInStorm() const
return mIsStorm; return mIsStorm;
} }
Ogre::Vector3 WeatherManager::getStormDirection() const osg::Vec3f WeatherManager::getStormDirection() const
{ {
return mStormDirection; return mStormDirection;
} }

View file

@ -184,7 +184,7 @@ namespace MWWorld
/// Are we in an ash or blight storm? /// Are we in an ash or blight storm?
bool isInStorm() const; bool isInStorm() const;
Ogre::Vector3 getStormDirection() const; osg::Vec3f getStormDirection() const;
void advanceTime(double hours); void advanceTime(double hours);
@ -205,7 +205,7 @@ namespace MWWorld
float mHour; float mHour;
float mWindSpeed; float mWindSpeed;
bool mIsStorm; bool mIsStorm;
Ogre::Vector3 mStormDirection; osg::Vec3f mStormDirection;
MWBase::SoundPtr mAmbientSound; MWBase::SoundPtr mAmbientSound;
std::string mPlayingSoundID; std::string mPlayingSoundID;

View file

@ -7,8 +7,6 @@
#else #else
#include <tr1/unordered_map> #include <tr1/unordered_map>
#endif #endif
#include "../mwbase/scriptmanager.hpp"
#include "../mwscript/globalscripts.hpp"
#include <OgreSceneNode.h> #include <OgreSceneNode.h>
#include <osg/Group> #include <osg/Group>
@ -19,6 +17,7 @@
#include <components/compiler/locals.hpp> #include <components/compiler/locals.hpp>
#include <components/esm/cellid.hpp> #include <components/esm/cellid.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <boost/math/special_functions/sign.hpp> #include <boost/math/special_functions/sign.hpp>
@ -26,6 +25,7 @@
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp" #include "../mwmechanics/movement.hpp"
@ -39,9 +39,12 @@
#include "../mwrender/renderingmanager.hpp" #include "../mwrender/renderingmanager.hpp"
#include "../mwscript/interpretercontext.hpp" #include "../mwscript/interpretercontext.hpp"
#include "../mwscript/globalscripts.hpp"
#include "../mwclass/door.hpp" #include "../mwclass/door.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "player.hpp" #include "player.hpp"
#include "manualref.hpp" #include "manualref.hpp"
#include "cellstore.hpp" #include "cellstore.hpp"
@ -51,7 +54,6 @@
#include "actionteleport.hpp" #include "actionteleport.hpp"
//#include "projectilemanager.hpp" //#include "projectilemanager.hpp"
#include "weather.hpp" #include "weather.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "contentloader.hpp" #include "contentloader.hpp"
#include "esmloader.hpp" #include "esmloader.hpp"
@ -156,7 +158,7 @@ namespace MWWorld
mStartCell (startCell), mStartupScript(startupScript), mStartCell (startCell), mStartupScript(startupScript),
mScriptsEnabled(true) mScriptsEnabled(true)
{ {
mPhysics = new MWPhysics::PhysicsSystem(rootNode); mPhysics = new MWPhysics::PhysicsSystem(resourceSystem->getVFS(), rootNode);
//mPhysEngine = mPhysics->getEngine(); //mPhysEngine = mPhysics->getEngine();
#if 0 #if 0
mProjectileManager.reset(new ProjectileManager(renderer.getScene(), *mPhysEngine)); mProjectileManager.reset(new ProjectileManager(renderer.getScene(), *mPhysEngine));
@ -1216,7 +1218,7 @@ namespace MWWorld
if (haveToMove && ptr.getRefData().getBaseNode()) if (haveToMove && ptr.getRefData().getBaseNode())
{ {
mRendering->moveObject(ptr, osg::Vec3f(vec.x, vec.y, vec.z)); mRendering->moveObject(ptr, osg::Vec3f(vec.x, vec.y, vec.z));
//mPhysics->moveObject (ptr); mPhysics->updatePosition(ptr);
} }
if (isPlayer) if (isPlayer)
{ {
@ -1343,9 +1345,9 @@ namespace MWWorld
if (force || !isFlying(ptr)) if (force || !isFlying(ptr))
{ {
//Ogre::Vector3 traced = mPhysics->traceDown(ptr, 500); osg::Vec3f traced = mPhysics->traceDown(ptr, 500);
//if (traced.z < pos.pos[2]) if (traced.z() < pos.pos[2])
// pos.pos[2] = traced.z; pos.pos[2] = traced.z();
} }
moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]);
@ -1358,8 +1360,8 @@ namespace MWWorld
pos.pos[2] += dist; pos.pos[2] += dist;
actor.getRefData().setPosition(pos); actor.getRefData().setPosition(pos);
Ogre::Vector3 traced;// = mPhysics->traceDown(actor, dist*1.1f); osg::Vec3f traced = mPhysics->traceDown(actor, dist*1.1f);
moveObject(actor, actor.getCell(), traced.x, traced.y, traced.z); moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z());
} }
void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust)
@ -1397,34 +1399,32 @@ namespace MWWorld
cellY = static_cast<int>(std::floor(y / cellSize)); cellY = static_cast<int>(std::floor(y / cellSize));
} }
void World::queueMovement(const Ptr &ptr, const Ogre::Vector3 &velocity) void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity)
{ {
//mPhysics->queueObjectMovement(ptr, velocity); mPhysics->queueObjectMovement(ptr, velocity);
} }
void World::doPhysics(float duration) void World::doPhysics(float duration)
{ {
mPhysics->stepSimulation(duration); mPhysics->stepSimulation(duration);
#if 0
processDoors(duration); processDoors(duration);
mProjectileManager->update(duration); //mProjectileManager->update(duration);
const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); const MWPhysics::PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
PtrVelocityList::const_iterator player(results.end()); MWPhysics::PtrVelocityList::const_iterator player(results.end());
for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter) for(MWPhysics::PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter)
{ {
if(iter->first == getPlayerPtr()) if(iter->first == getPlayerPtr())
{ {
/* Handle player last, in case a cell transition occurs */ // Handle player last, in case a cell transition occurs
player = iter; player = iter;
continue; continue;
} }
moveObjectImp(iter->first, iter->second.x, iter->second.y, iter->second.z); moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z());
} }
if(player != results.end()) if(player != results.end())
moveObjectImp(player->first, player->second.x, player->second.y, player->second.z); moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z());
#endif
} }
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
@ -2312,12 +2312,12 @@ namespace MWWorld
return false; return false;
} }
Ogre::Vector3 World::getStormDirection() const osg::Vec3f World::getStormDirection() const
{ {
if (isCellExterior() || isCellQuasiExterior()) if (isCellExterior() || isCellQuasiExterior())
return mWeatherManager->getStormDirection(); return mWeatherManager->getStormDirection();
else else
return Ogre::Vector3(0,1,0); return osg::Vec3f(0,1,0);
} }
void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out) void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out)

View file

@ -380,7 +380,7 @@ namespace MWWorld
virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const; virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const;
///< Convert position to cell numbers ///< Convert position to cell numbers
virtual void queueMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); virtual void queueMovement(const Ptr &ptr, const osg::Vec3f &velocity);
///< Queues movement for \a ptr (in local space), to be applied in the next call to ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.
@ -650,7 +650,7 @@ namespace MWWorld
virtual bool isInStorm() const; virtual bool isInStorm() const;
/// @see MWWorld::WeatherManager::getStormDirection /// @see MWWorld::WeatherManager::getStormDirection
virtual Ogre::Vector3 getStormDirection() const; virtual osg::Vec3f getStormDirection() const;
/// Resets all actors in the current active cells to their original location within that cell. /// Resets all actors in the current active cells to their original location within that cell.
virtual void resetActors(); virtual void resetActors();

View file

@ -52,9 +52,9 @@ add_component_dir (nifosg
nifloader controller particle userdata nifloader controller particle userdata
) )
#add_component_dir (nifbullet add_component_dir (nifbullet
# bulletnifloader bulletnifloader bulletshapemanager
# ) )
add_component_dir (to_utf8 add_component_dir (to_utf8
to_utf8 to_utf8
@ -171,6 +171,7 @@ target_link_libraries(components
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${OGRE_LIBRARIES} ${OGRE_LIBRARIES}
${OPENSCENEGRAPH_LIBRARIES} ${OPENSCENEGRAPH_LIBRARIES}
${BULLET_LIBRARIES}
# For MyGUI platform # For MyGUI platform
${OPENGL_gl_LIBRARY} ${OPENGL_gl_LIBRARY}
) )

View file

@ -1,31 +1,11 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (ogre_nif_loader.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
#include "bulletnifloader.hpp" #include "bulletnifloader.hpp"
#include <cstdio> #include <cstdio>
#include <vector>
#include <list>
#include <iostream>
#include <stdexcept>
#include <OgreMatrix4.h>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include "../nif/niffile.hpp" #include "../nif/niffile.hpp"
@ -34,12 +14,7 @@ http://www.gnu.org/licenses/ .
#include "../nif/property.hpp" #include "../nif/property.hpp"
#include "../nif/controller.hpp" #include "../nif/controller.hpp"
#include "../nif/extra.hpp" #include "../nif/extra.hpp"
#include <libs/platform/strings.h>
#include <vector>
#include <list>
// For warning messages
#include <iostream>
namespace namespace
{ {
@ -51,53 +26,40 @@ osg::Matrixf getWorldTransform(const Nif::Node *node)
return node->trafo.toMatrix(); return node->trafo.toMatrix();
} }
btVector3 getbtVector(const osg::Vec3f &v)
{
return btVector3(v.x(), v.y(), v.z());
}
} }
namespace NifBullet namespace NifBullet
{ {
ManualBulletShapeLoader::~ManualBulletShapeLoader() BulletNifLoader::BulletNifLoader()
: mCompoundShape(NULL)
, mStaticMesh(NULL)
{ {
} }
BulletNifLoader::~BulletNifLoader()
btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 const &v)
{ {
return btVector3(v[0], v[1], v[2]);
} }
void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) osg::ref_ptr<BulletShape> BulletNifLoader::load(const Nif::NIFFilePtr nif)
{ {
mShape = static_cast<OEngine::Physic::BulletShape *>(resource); mShape = new BulletShape;
mResourceName = mShape->getName();
mBoundingBox = NULL;
mShape->mBoxTranslation = osg::Vec3f(0,0,0);
mShape->mBoxRotation = osg::Quat();
mCompoundShape = NULL; mCompoundShape = NULL;
mStaticMesh = NULL; mStaticMesh = NULL;
Nif::NIFFilePtr pnif;// (Nif::Cache::getInstance().load(mResourceName.substr(0, mResourceName.length()-7))); if (nif->numRoots() < 1)
Nif::NIFFile & nif = *pnif.get ();
if (nif.numRoots() < 1)
{ {
warn("Found no root nodes in NIF."); warn("Found no root nodes in NIF.");
return; return mShape;
} }
// Have to load controlled nodes from the .kf Nif::Record *r = nif->getRoot(0);
// FIXME: the .kf has to be loaded both for rendering and physics, ideally it should be opened once and then reused
mControlledNodes.clear();
std::string kfname = mResourceName.substr(0, mResourceName.length()-7);
Misc::StringUtils::toLower(kfname);
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
kfname.replace(kfname.size()-4, 4, ".kf");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname))
{
Nif::NIFFilePtr kf;// (Nif::Cache::getInstance().load(kfname));
//extractControlledNodes(kf, mControlledNodes);
}
Nif::Record *r = nif.getRoot(0);
assert(r != NULL); assert(r != NULL);
Nif::Node *node = dynamic_cast<Nif::Node*>(r); Nif::Node *node = dynamic_cast<Nif::Node*>(r);
@ -105,29 +67,40 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
{ {
warn("First root in file was not a node, but a " + warn("First root in file was not a node, but a " +
r->recName + ". Skipping file."); r->recName + ". Skipping file.");
return; return mShape;
} }
mShape->mAutogenerated = hasAutoGeneratedCollision(node); if (findBoundingBox(node))
//do a first pass
handleNode(node,0,false,false);
if(mBoundingBox != NULL)
{ {
mShape->mCollisionShape = mBoundingBox; std::auto_ptr<btCompoundShape> compound (new btCompoundShape);
delete mStaticMesh;
if (mCompoundShape) btBoxShape* boxShape = new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents));
{ btTransform transform = btTransform::getIdentity();
int n = mCompoundShape->getNumChildShapes(); transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate));
for(int i=0; i <n;i++) compound->addChildShape(transform, boxShape);
delete (mCompoundShape->getChildShape(i));
delete mCompoundShape; mShape->mCollisionShape = compound.release();
mShape->mAnimatedShapes.clear(); return mShape;
}
} }
else else
{ {
/*
// Have to load controlled nodes from the .kf
mControlledNodes.clear();
std::string kfname = mResourceName.substr(0, mResourceName.length()-7);
Misc::StringUtils::toLower(kfname);
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
kfname.replace(kfname.size()-4, 4, ".kf");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname))
{
Nif::NIFFilePtr kf;// (Nif::Cache::getInstance().load(kfname));
//extractControlledNodes(kf, mControlledNodes);
}
*/
bool autogenerated = hasAutoGeneratedCollision(node);
handleNode(node, 0, autogenerated, false, autogenerated);
if (mCompoundShape) if (mCompoundShape)
{ {
mShape->mCollisionShape = mCompoundShape; mShape->mCollisionShape = mCompoundShape;
@ -140,10 +113,46 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
} }
else if (mStaticMesh) else if (mStaticMesh)
mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true); mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true);
return mShape;
} }
} }
bool ManualBulletShapeLoader::hasAutoGeneratedCollision(Nif::Node const * rootNode) // Find a boundingBox in the node hierarchy.
// Return: use bounding box for collision?
bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags)
{
flags |= node->flags;
if (node->hasBounds)
{
mShape->mCollisionBoxHalfExtents = node->boundXYZ;
mShape->mCollisionBoxTranslate = node->boundPos;
if (flags & Nif::NiNode::Flag_BBoxCollision)
{
return true;
}
}
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
if(ninode)
{
const Nif::NodeList &list = ninode->children;
for(size_t i = 0;i < list.length();i++)
{
if(!list[i].empty())
{
bool found = findBoundingBox (list[i].getPtr());
if (found)
return true;
}
}
}
return false;
}
bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode)
{ {
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(rootNode); const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(rootNode);
if(ninode) if(ninode)
@ -161,8 +170,8 @@ bool ManualBulletShapeLoader::hasAutoGeneratedCollision(Nif::Node const * rootNo
return true; return true;
} }
void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, void BulletNifLoader::handleNode(const Nif::Node *node, int flags,
bool isCollisionNode, bool isAnimated) bool isCollisionNode, bool isAnimated, bool autogenerated)
{ {
// Accumulate the flags from all the child nodes. This works for all // Accumulate the flags from all the child nodes. This works for all
// the flags we currently use, at least. // the flags we currently use, at least.
@ -201,24 +210,21 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags,
// No collision. Use an internal flag setting to mark this. // No collision. Use an internal flag setting to mark this.
flags |= 0x800; flags |= 0x800;
} }
else if (sd->string == "MRK" && autogenerated)
{
// Marker can still have collision if the model explicitely specifies it via a RootCollisionNode.
return;
}
} }
} }
if (isCollisionNode || (mShape->mAutogenerated)) if (isCollisionNode)
{ {
// NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
// It must be ignored completely. // It must be ignored completely.
// (occurs in tr_ex_imp_wall_arch_04.nif) // (occurs in tr_ex_imp_wall_arch_04.nif)
if(node->hasBounds) if(!node->hasBounds && node->recType == Nif::RC_NiTriShape)
{
if (flags & Nif::NiNode::Flag_BBoxCollision)
{
mShape->mBoxTranslation = node->boundPos;
//mShape->mBoxRotation = node->boundRot;
//mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ));
}
}
else if(node->recType == Nif::RC_NiTriShape)
{ {
handleNiTriShape(static_cast<const Nif::NiTriShape*>(node), flags, getWorldTransform(node), isAnimated); handleNiTriShape(static_cast<const Nif::NiTriShape*>(node), flags, getWorldTransform(node), isAnimated);
} }
@ -237,7 +243,7 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags,
} }
} }
void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated) void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated)
{ {
assert(shape != NULL); assert(shape != NULL);
@ -261,6 +267,7 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int
if (!shape->skin.empty()) if (!shape->skin.empty())
isAnimated = false; isAnimated = false;
/*
if (isAnimated) if (isAnimated)
{ {
if (!mCompoundShape) if (!mCompoundShape)
@ -281,7 +288,7 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int
osg::Vec3f b1 = vertices[triangles[i+0]]; osg::Vec3f b1 = vertices[triangles[i+0]];
osg::Vec3f b2 = vertices[triangles[i+1]]; osg::Vec3f b2 = vertices[triangles[i+1]];
osg::Vec3f b3 = vertices[triangles[i+2]]; osg::Vec3f b3 = vertices[triangles[i+2]];
childMesh->addTriangle(btVector3(b1.x(),b1.y(),b1.z()),btVector3(b2.x(),b2.y(),b2.z()),btVector3(b3.x(),b3.y(),b3.z())); childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3));
} }
TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true);
@ -304,9 +311,10 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int
mCompoundShape->addChildShape(trans, childShape); mCompoundShape->addChildShape(trans, childShape);
} }
else else
*/
{ {
if (!mStaticMesh) if (!mStaticMesh)
mStaticMesh = new btTriangleMesh(); mStaticMesh = new btTriangleMesh(false);
// Static shape, just transform all vertices into position // Static shape, just transform all vertices into position
const Nif::NiTriShapeData *data = shape->data.getPtr(); const Nif::NiTriShapeData *data = shape->data.getPtr();
@ -315,61 +323,99 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int
for(size_t i = 0;i < data->triangles.size();i+=3) for(size_t i = 0;i < data->triangles.size();i+=3)
{ {
osg::Vec3f b1 = transform*vertices[triangles[i+0]]; osg::Vec3f b1 = vertices[triangles[i+0]]*transform;
osg::Vec3f b2 = transform*vertices[triangles[i+1]]; osg::Vec3f b2 = vertices[triangles[i+1]]*transform;
osg::Vec3f b3 = transform*vertices[triangles[i+2]]; osg::Vec3f b3 = vertices[triangles[i+2]]*transform;
mStaticMesh->addTriangle(btVector3(b1.x(),b1.y(),b1.z()),btVector3(b2.x(),b2.y(),b2.z()),btVector3(b3.x(),b3.y(),b3.z())); mStaticMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3));
} }
} }
} }
bool findBoundingBox (const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) BulletShape::BulletShape()
: mCollisionShape(NULL)
{ {
if(node->hasBounds)
{
if (!(node->flags & Nif::NiNode::Flag_Hidden))
{
//translation = node->boundPos;
//orientation = node->boundRot;
//halfExtents = node->boundXYZ;
return true;
}
}
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
if(ninode)
{
const Nif::NodeList &list = ninode->children;
for(size_t i = 0;i < list.length();i++)
{
if(!list[i].empty())
if (findBoundingBox(list[i].getPtr(), halfExtents, translation, orientation))
return true;
}
}
return false;
} }
bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) BulletShape::~BulletShape()
{ {
Nif::NIFFilePtr pnif;// (Nif::Cache::getInstance().load(nifFile)); deleteShape(mCollisionShape);
Nif::NIFFile & nif = *pnif.get (); }
if (nif.numRoots() < 1) void BulletShape::deleteShape(btCollisionShape* shape)
{
if(shape!=NULL)
{ {
return false; if(shape->isCompound())
{
btCompoundShape* ms = static_cast<btCompoundShape*>(shape);
int a = ms->getNumChildShapes();
for(int i=0; i <a;i++)
deleteShape(ms->getChildShape(i));
}
delete shape;
}
}
btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const
{
if(shape->isCompound())
{
btCompoundShape *comp = static_cast<btCompoundShape*>(shape);
btCompoundShape *newShape = new btCompoundShape;
int numShapes = comp->getNumChildShapes();
for(int i = 0;i < numShapes;++i)
{
btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i));
btTransform trans = comp->getChildTransform(i);
newShape->addChildShape(trans, child);
}
return newShape;
} }
Nif::Record *r = nif.getRoot(0); if(btBvhTriangleMeshShape* trishape = dynamic_cast<btBvhTriangleMeshShape*>(shape))
assert(r != NULL);
Nif::Node *node = dynamic_cast<Nif::Node*>(r);
if (node == NULL)
{ {
return false; #if BT_BULLET_VERSION >= 283
btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f));
#else
// work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions
btTriangleMesh* oldMesh = static_cast<btTriangleMesh*>(trishape->getMeshInterface());
btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh);
NifBullet::TriangleMeshShape* newShape = new NifBullet::TriangleMeshShape(newMesh, true);
#endif
return newShape;
} }
return findBoundingBox(node, halfExtents, translation, orientation); if (btBoxShape* boxshape = dynamic_cast<btBoxShape*>(shape))
{
return new btBoxShape(*boxshape);
}
throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName());
}
btCollisionShape *BulletShape::getCollisionShape()
{
return mCollisionShape;
}
osg::ref_ptr<BulletShapeInstance> BulletShape::makeInstance()
{
osg::ref_ptr<BulletShapeInstance> instance (new BulletShapeInstance(this));
return instance;
}
BulletShapeInstance::BulletShapeInstance(osg::ref_ptr<BulletShape> source)
: BulletShape()
, mSource(source)
{
mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents;
mCollisionBoxTranslate = source->mCollisionBoxTranslate;
if (source->mCollisionShape)
mCollisionShape = duplicateCollisionShape(source->mCollisionShape);
} }
} // namespace NifBullet } // namespace NifBullet

View file

@ -1,41 +1,22 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (ogre_nif_loader.h) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
#ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP
#define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP
#include <cassert> #include <cassert>
#include <string> #include <string>
#include <set> #include <set>
#include <iostream>
#include <map>
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h> #include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btConvexTriangleMeshShape.h> #include <BulletCollision/CollisionShapes/btConvexTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <btBulletDynamicsCommon.h> #include <btBulletDynamicsCommon.h>
#include <osg/Matrixf> #include <osg/Matrixf>
#include <osg/BoundingBox>
#include <osg/ref_ptr>
#include <osg/Referenced>
// For warning messages #include <components/nif/niffile.hpp>
#include <iostream>
namespace Nif namespace Nif
{ {
@ -47,6 +28,48 @@ namespace Nif
namespace NifBullet namespace NifBullet
{ {
class BulletShapeInstance;
class BulletShape : public osg::Referenced
{
public:
BulletShape();
virtual ~BulletShape();
btCollisionShape* mCollisionShape;
// Used for actors. Note, ideally actors would use a separate loader - as it is
// we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used.
// For now, use one file <-> one resource for simplicity.
osg::Vec3f mCollisionBoxHalfExtents;
osg::Vec3f mCollisionBoxTranslate;
// Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape
// will be a btCompoundShape (which consists of one or more child shapes).
// In this map, for each animated collision shape,
// we store the node's record index mapped to the child index of the shape in the btCompoundShape.
std::map<int, int> mAnimatedShapes;
osg::ref_ptr<BulletShapeInstance> makeInstance();
btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const;
btCollisionShape* getCollisionShape();
private:
void deleteShape(btCollisionShape* shape);
};
// An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape.
// Vertex data is shallow-copied where possible. A ref_ptr to the original shape needs to be held to keep vertex pointers intact.
class BulletShapeInstance : public BulletShape
{
public:
BulletShapeInstance(osg::ref_ptr<BulletShape> source);
private:
osg::ref_ptr<BulletShape> mSource;
};
// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface
struct TriangleMeshShape : public btBvhTriangleMeshShape struct TriangleMeshShape : public btBvhTriangleMeshShape
{ {
@ -66,18 +89,12 @@ struct TriangleMeshShape : public btBvhTriangleMeshShape
/** /**
*Load bulletShape from NIF files. *Load bulletShape from NIF files.
*/ */
class ManualBulletShapeLoader class BulletNifLoader
{ {
public: public:
ManualBulletShapeLoader(bool showMarkers=false) BulletNifLoader();
: mCompoundShape(NULL)
, mStaticMesh(NULL)
, mBoundingBox(NULL)
, mShowMarkers(showMarkers)
{
}
virtual ~ManualBulletShapeLoader(); virtual ~BulletNifLoader();
void warn(const std::string &msg) void warn(const std::string &msg)
{ {
@ -90,44 +107,26 @@ public:
abort(); abort();
} }
osg::ref_ptr<BulletShape> load(const Nif::NIFFilePtr file);
private: private:
btVector3 getbtVector(Ogre::Vector3 const &v); bool findBoundingBox(const Nif::Node* node, int flags = 0);
/** void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false);
*Parse a node.
*/
void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false);
/**
*Helper function
*/
bool hasAutoGeneratedCollision(const Nif::Node *rootNode); bool hasAutoGeneratedCollision(const Nif::Node *rootNode);
/**
*convert a NiTriShape to a bullet trishape.
*/
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated);
std::string mResourceName;
//OEngine::Physic::BulletShape* mShape;//current shape
btCompoundShape* mCompoundShape; btCompoundShape* mCompoundShape;
btTriangleMesh* mStaticMesh; btTriangleMesh* mStaticMesh;
btBoxShape *mBoundingBox;
std::set<std::string> mControlledNodes; std::set<std::string> mControlledNodes;
bool mShowMarkers; osg::ref_ptr<BulletShape> mShape;
}; };
bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation);
bool findBoundingBox(const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation);
} }
#endif #endif

View file

@ -0,0 +1,47 @@
#include "bulletshapemanager.hpp"
#include <components/vfs/manager.hpp>
#include <components/nifbullet/bulletnifloader.hpp>
namespace NifBullet
{
BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs)
: mVFS(vfs)
{
}
BulletShapeManager::~BulletShapeManager()
{
}
osg::ref_ptr<BulletShapeInstance> BulletShapeManager::createInstance(const std::string &name)
{
std::string normalized = name;
mVFS->normalizeFilename(normalized);
osg::ref_ptr<BulletShape> shape;
Index::iterator it = mIndex.find(normalized);
if (it == mIndex.end())
{
Files::IStreamPtr file = mVFS->get(normalized);
// TODO: add support for non-NIF formats
BulletNifLoader loader;
// might be worth sharing NIFFiles with SceneManager in some way
shape = loader.load(Nif::NIFFilePtr(new Nif::NIFFile(file, normalized)));
mIndex[normalized] = shape;
}
else
shape = it->second;
osg::ref_ptr<BulletShapeInstance> instance = shape->makeInstance();
return instance;
}
}

View file

@ -0,0 +1,37 @@
#ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H
#define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H
#include <map>
#include <string>
#include <osg/ref_ptr>
namespace VFS
{
class Manager;
}
namespace NifBullet
{
class BulletShape;
class BulletShapeInstance;
class BulletShapeManager
{
public:
BulletShapeManager(const VFS::Manager* vfs);
~BulletShapeManager();
osg::ref_ptr<BulletShapeInstance> createInstance(const std::string& name);
private:
const VFS::Manager* mVFS;
typedef std::map<std::string, osg::ref_ptr<BulletShape> > Index;
Index mIndex;
};
}
#endif