// This file is part of the OGRE project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at https://www.ogre3d.org/licensing. // SPDX-License-Identifier: MIT // #include #include "Ogre.h" #include "OgreApplicationContext.h" #include "Bullet/OgreBullet.h" #include "BulletCollision/CollisionDispatch/btGhostObject.h" #include "btKinematicCharacterController.h" #include "LinearMath/btTransform.h" #include "OgrePageManager.h" #define CAM_HEIGHT 1.6f // height of camera above character's center of mass #define RUN_SPEED 17 // character running speed in units per second #define TURN_SPEED 500.0f // character turning in degrees per second #define ANIM_FADE_SPEED 7.5f // animation crossfade speed in % of full weight per second using Real = Ogre::Real; using Math = Ogre::Math; class WorldData { std::unique_ptr mDynWorld; std::unique_ptr mDbgDraw; std::unique_ptr mRoot; std::unique_ptr mScnMgr; std::unique_ptr mbtWorld; std::unique_ptr mPageManager; Ogre::PagedWorld *mPagedWorld; private: static WorldData *singleton; class DummyPageProvider : public Ogre::PageProvider { public: bool prepareProceduralPage(Ogre::Page* page, Ogre::PagedWorldSection* section) override { return true; } bool loadProceduralPage(Ogre::Page* page, Ogre::PagedWorldSection* section) override { return true; } bool unloadProceduralPage(Ogre::Page* page, Ogre::PagedWorldSection* section) override { return true; } bool unprepareProceduralPage(Ogre::Page* page, Ogre::PagedWorldSection* section) override { return true; } }; DummyPageProvider mDummyPageProvider; WorldData(Ogre::Root *root, Ogre::SceneManager *scnMgr) : mDynWorld(new Ogre::Bullet::DynamicsWorld(Ogre::Vector3(0, -9.8, 0))) , mDbgDraw(new Ogre::Bullet::DebugDrawer(scnMgr->getRootSceneNode(), mDynWorld->getBtWorld())) , mRoot(root) , mScnMgr(scnMgr) , mbtWorld(mDynWorld->getBtWorld()) , mPageManager(nullptr) , mPagedWorld(nullptr) { } public: static void init(Ogre::Root *root, Ogre::SceneManager *scnMgr) { singleton = new WorldData(root, scnMgr); } static WorldData *get_singleton() { return singleton; } static void cleanup() { if (singleton) delete singleton; singleton = nullptr; } Ogre::SceneManager *getSceneManager() { return mScnMgr.get(); } Ogre::Root *getRoot() { return mRoot.get(); } void createTrimesh(Ogre::Entity *entity) { } btPairCachingGhostObject *addGhostObject(Ogre::Entity *ent, btCollisionShape *shape, int group = 1, int mask = 0xFFFF) { btDynamicsWorld *world = mDynWorld->getBtWorld(); Ogre::SceneNode *node = ent->getParentSceneNode(); btPairCachingGhostObject *ghost = new btPairCachingGhostObject(); ghost->setCollisionShape(shape); ghost->setCollisionFlags(ghost->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE | btCollisionObject::CF_CHARACTER_OBJECT ); getWorld()->attachCollisionObject(ghost, ent, group, mask); #if 0 getBtWorld() ->getBroadphase()->getOverlappingPairCache() ->setInternalGhostPairCallback(new btGhostPairCallback()); ghost->setUserPointer(new EntityCollisionListener{ent, nullptr}); #endif return ghost; } btRigidBody *addRigidBody(float mass, Ogre::Entity *ent, Ogre::Bullet::ColliderType ct, int group = 1, int mask = 0xFFFF) { btDynamicsWorld *world = mDynWorld->getBtWorld(); Ogre::SceneNode *node = ent->getParentSceneNode(); Ogre::Bullet::RigidBodyState *state = new Ogre::Bullet::RigidBodyState(node); btCollisionShape *cs; btCollisionShape *shape; btVector3 inertia(0, 0, 0); switch(ct) { case Ogre::Bullet::CT_TRIMESH: { cs = Ogre::Bullet::createTrimeshCollider(ent); if (mass != 0) cs->calculateLocalInertia(mass, inertia); } break; case Ogre::Bullet::CT_CAPSULE: { cs = new btCompoundShape(); btScalar height = 1.0f; btScalar radius = 0.3f; shape = new btCapsuleShape(radius, 2 * height - 2 * radius); btTransform transform; transform.setIdentity(); transform.setOrigin(btVector3(0, 1, 0)); static_cast(cs)->addChildShape(transform, shape); btScalar masses[1] = {mass}; btTransform principal; static_cast(cs)->calculatePrincipalAxisTransform(masses, principal, inertia); } break; default: assert(false); break; } btRigidBody *body = new btRigidBody(mass, state, cs, inertia); getWorld()->attachRigidBody(body, ent, nullptr, group, mask); #if 0 body->setUserPointer(new EntityCollisionListener{ent, nullptr}); // btRigidBody *body = mDynWorld->addRigidBody(0, ent, Ogre::Bullet::CT_TRIMESH); #endif return body; } btRigidBody *addKinematicRigidBody(float mass, Ogre::Entity *ent, Ogre::Bullet::ColliderType ct, int group = 1, int mask = 0xFFFF) { return mDynWorld->addKinematicRigidBody(ent, ct, group, mask); } btDynamicsWorld *getBtWorld() { return mDynWorld->getBtWorld(); } Ogre::Bullet::DynamicsWorld *getWorld() { return mDynWorld.get(); } void update(float delta) { WorldData::get_singleton() ->getBtWorld()->stepSimulation(delta, 10); mDbgDraw->update(); } void initPagedWorld(Ogre::Camera *camera) { mPageManager = std::make_unique(); mPageManager->setPageProvider(&mDummyPageProvider); mPageManager->addCamera(camera); mPageManager->setDebugDisplayLevel(0); mPagedWorld = mPageManager->createWorld(); } }; WorldData *WorldData::singleton = nullptr; class MainWorld : public Ogre::FrameListener { btRigidBody *mFloorBody; public: void setup() { // mScnMgr = scnMgr; // mDynWorld.reset(new Ogre::Bullet::DynamicsWorld(Ogre::Vector3(0, -9.8, 0))); // mDbgDraw.reset(new Ogre::Bullet::DebugDrawer(mScnMgr->getRootSceneNode(), mDynWorld->getBtWorld())); Ogre::MeshManager::getSingleton().createPlane("floor", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::Plane(Ogre::Vector3::UNIT_Y, 0), 100, 100, 10, 10, true, 1, 10, 10, Ogre::Vector3::UNIT_Z); // create a floor entity, give it a material, and place it at the origin Ogre::SceneManager *scnMgr = WorldData::get_singleton()->getSceneManager(); Ogre::Entity *floor = scnMgr->createEntity("Floor", "floor"); scnMgr->getRootSceneNode()->attachObject(floor); mFloorBody = WorldData::get_singleton()->addRigidBody(0, floor, Ogre::Bullet::CT_TRIMESH); } btRigidBody *addCharacter(Ogre::Entity *ent, float mass) { return WorldData::get_singleton()->addKinematicRigidBody(mass, ent, Ogre::Bullet::CT_COMPOUND); } bool frameStarted(const Ogre::FrameEvent& evt) override; }; class CharacterController : public OgreBites::InputListener, Ogre::FrameListener { enum AnimID { ANIM_IDLE = 0, ANIM_WALK, ANIM_RUN, NUM_ANIMS, ANIM_NONE = NUM_ANIMS }; Ogre::Node *mRootBone; Ogre::SceneNode *mCameraNode; Ogre::Camera *mCamera; Ogre::SceneManager *mScnMgr; Ogre::SceneNode *mCameraPivot; Ogre::SceneNode *mCameraGoal, *mBodyNode; Ogre::Entity *mBodyEnt; Real mPivotPitch; Real mVerticalVelocity; Ogre::Vector3 mKeyDirection; // player's local intended direction based on WASD keys Ogre::Vector3 mGoalDirection; // actual intended direction in world-space Ogre::AnimationState *mAnims[NUM_ANIMS]; // master animation list Ogre::Animation *mSkelAnimations[NUM_ANIMS]; Ogre::NodeAnimationTrack *mHipsTracks[NUM_ANIMS]; Ogre::NodeAnimationTrack *mRootTracks[NUM_ANIMS]; AnimID mAnimID; bool mFadingIn[NUM_ANIMS]; // which animations are fading in bool mFadingOut[NUM_ANIMS]; // which animations are fading out Real mTimer; // general timer to see how long animations have been playing Ogre::Skeleton *mSkeleton; bool mRunning; MainWorld *world; Ogre::Vector3 rootMotion; Ogre::Quaternion rootRotation; // btRigidBody *mRigidBody; btCompoundShape *mCollisionShape; btPairCachingGhostObject *mGhostObject; btKinematicCharacterController *mController; public: CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam, Ogre::SceneManager *scnMgr, MainWorld *world); ~CharacterController(); private: void setupBody(); void setupCamera(); void setupAnimations(); public: bool keyPressed(const OgreBites::KeyboardEvent& evt) override; bool keyReleased(const OgreBites::KeyboardEvent& evt) override; bool mouseMoved(const OgreBites::MouseMotionEvent& evt) override; bool mouseWheelRolled(const OgreBites::MouseWheelEvent& evt) override; bool mousePressed(const OgreBites::MouseButtonEvent& evt) override; bool frameStarted(const Ogre::FrameEvent& evt) override; void frameRendered(const Ogre::FrameEvent& evt) override; private: void updateBody(Ogre::Real deltaTime); void updateAnimations(Real deltaTime); void updateRootMotion(Real deltaTime); void fadeAnimations(Real deltaTime); void updateCamera(Real deltaTime); void updateCameraGoal(Real deltaYaw, Real deltaPitch, Real deltaZoom); void setAnimation(AnimID id, bool reset = false); #if 0 struct testMotionResult { }; struct recoverResult { }; bool bodyTestMotion(btRigidBody *body, const btTransform &from, const btVector3 &motion, bool infinite_inertia, textMotionResult *result, bool excludeRaycastShapes, const std::set &exclude); bool recoverFromPenetration(btRigidBody *body, const btTransform &body_position, btScalar recover_movement_scale, bool infinite_inertia, btVector3 &delta_recover_movement, recoverResult *recover_result, const std::set &exclude); #endif inline btQuaternion convert(const Ogre::Quaternion& q) { return btQuaternion(q.x, q.y, q.z, q.w); } inline btVector3 convert(const Ogre::Vector3& v) { return btVector3(v.x, v.y, v.z); } inline btTransform convert(const Ogre::Quaternion& q, const Ogre::Vector3& v) { btQuaternion mq = convert(q); btVector3 mv = convert(v); return btTransform(mq, mv); } inline Ogre::Quaternion convert(const btQuaternion& q) { return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z()); } inline Ogre::Vector3 convert(const btVector3& v) { return Ogre::Vector3(v.x(), v.y(), v.z()); } inline void convert(const btTransform &from, Ogre::Quaternion &q, Ogre::Vector3 &v) { q = convert(from.getRotation()); v = convert(from.getOrigin()); } }; CharacterController::CharacterController( Ogre::SceneNode *camNode, Ogre::Camera *cam, Ogre::SceneManager *scnMgr, MainWorld *world) : mCameraNode(camNode) , mCamera(cam) , mScnMgr(scnMgr) , mPivotPitch(0) , mVerticalVelocity(0) , mAnimID(ANIM_NONE) , mRunning(false) , world(world) , mCollisionShape(nullptr) , mGhostObject(nullptr) , mController(nullptr) { setupBody(); setupCamera(); setupAnimations(); } CharacterController::~CharacterController() { } void CharacterController::setupBody() { mBodyEnt = mScnMgr->createEntity("normal-male.glb"); mBodyNode = mScnMgr->getRootSceneNode()->createChildSceneNode(); mBodyNode->attachObject(mBodyEnt); mSkeleton = mBodyEnt->getSkeleton(); // mRigidBody = world->addCharacter(mBodyEnt, 0); // mCollisionShape = static_cast(mRigidBody->getCollisionShape()); mGhostObject = new btPairCachingGhostObject(); mCollisionShape = new btCompoundShape; mGhostObject->setCollisionShape(mCollisionShape); { btVector3 inertia(0, 0, 0); // mCollisionShape = new btCompoundShape(); btScalar height = 1.0f; btScalar radius = 0.3f; btCapsuleShape *shape = new btCapsuleShape(radius, 2 * height - 2 * radius); btTransform transform; transform.setIdentity(); transform.setOrigin(btVector3(0, 1, 0)); static_cast(mCollisionShape)->addChildShape(transform, shape); btScalar masses[1] = {0}; btTransform principal; static_cast(mCollisionShape)->calculatePrincipalAxisTransform(masses, principal, inertia); } mGhostObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE); mGhostObject->setActivationState(DISABLE_DEACTIVATION); Ogre::Bullet::KinematicMotionSimple *controller = new Ogre::Bullet::KinematicMotionSimple(mGhostObject, mBodyNode); WorldData::get_singleton()->getWorld()->attachCollisionObject(mGhostObject, mBodyEnt, btBroadphaseProxy::AllFilter, btBroadphaseProxy::AllFilter); WorldData::get_singleton()->getBtWorld()->addAction(controller); assert(mCollisionShape); #if 0 if (mRigidBody->getMass() == 0) { #if 0 mRigidBody->setCollisionFlags(mRigidBody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE ); #endif #if 0 mGhostObject->setWorldTransform(mRigidBody->getWorldTransform()); WorldData::get_singleton()->getBtWorld() ->getBroadphase()->getOverlappingPairCache() ->setInternalGhostPairCallback(new btGhostPairCallback()); #endif } #endif #if 0 mRigidBody->setActivationState(DISABLE_DEACTIVATION); #endif #if 0 { Ogre::Entity *e2 = mScnMgr->createEntity("normal-male.glb"); Ogre::SceneNode *e2node = mScnMgr->getRootSceneNode()->createChildSceneNode(); e2node->attachObject(e2); mGhostObject = WorldData::get_singleton()->addGhostObject(e2, mCollisionShape); mController = new btKinematicCharacterController(mGhostObject, mCollisionShape, 0.5f); WorldData::get_singleton()->getBtWorld()->addAction(mController); } #endif assert(mSkeleton->hasBone("Root")); mRootBone = mSkeleton->getBone("Root"); assert(mRootBone); } void CharacterController::setupCamera() { // create a pivot at roughly the character's shoulder mCameraPivot = mScnMgr->getRootSceneNode()->createChildSceneNode(); mCameraGoal = mCameraPivot->createChildSceneNode(Ogre::Vector3(0, 2, 3)); mCameraNode->setPosition(mCameraPivot->getPosition() + mCameraGoal->getPosition()); mCameraPivot->setFixedYawAxis(true); mCameraGoal->setFixedYawAxis(true); mCameraNode->setFixedYawAxis(true); // our model is quite small, so reduce the clipping planes mCamera->setNearClipDistance(0.1f); mCamera->setFarClipDistance(700); mPivotPitch = 0; mKeyDirection = Ogre::Vector3::ZERO; mVerticalVelocity = 0; } void CharacterController::setupAnimations() { int i, j; mSkeleton->setBlendMode(Ogre::ANIMBLEND_CUMULATIVE); Ogre::String animNames[NUM_ANIMS] = {"idle", "walking", "running"}; for (i = 0; i < NUM_ANIMS; i++) { mAnims[i] = mBodyEnt->getAnimationState(animNames[i]); mAnims[i]->setLoop(true); mAnims[i]->setEnabled(true); mAnims[i]->setWeight(0); mFadingIn[i] = false; mFadingOut[i] = false; mSkelAnimations[i] = mSkeleton->getAnimation(animNames[i]); for (const auto& it : mSkelAnimations[i]->_getNodeTrackList()) { Ogre::NodeAnimationTrack* track = it.second; Ogre::String trackName = track->getAssociatedNode()->getName(); if (trackName == "mixamorig:Hips") { mHipsTracks[i] = track; } else if (trackName == "Root") { mRootTracks[i] = track; // mRootTracks[i]->removeAllKeyFrames(); } } Ogre::Vector3 delta = Ogre::Vector3::ZERO; Ogre::Vector3 motion = Ogre::Vector3::ZERO; for(j = 0; j < mRootTracks[i]->getNumKeyFrames(); j++) { Ogre::Vector3 trans = mRootTracks[i]->getNodeKeyFrame(j)->getTranslate(); if (j == 0) delta = trans; else delta = trans - motion; mRootTracks[i]->getNodeKeyFrame(j)->setTranslate(delta); motion = trans; } } #if 0 for(i = 0; i < NUM_ANIMS - 1; i++) { // need to cache int j; Ogre::String animName = mAnims[i]->getAnimationName(); Ogre::Animation *anim = mSkeleton->getAnimation(animName); Ogre::NodeAnimationTrack *hips_track = nullptr, *root_track = nullptr; Ogre::Node *root_node = nullptr; for (const auto& it : anim->_getNodeTrackList()) { Ogre::NodeAnimationTrack* track = it.second; Ogre::String trackName = track->getAssociatedNode()->getName(); std::cout << animName << " track: " << trackName << "\n"; if (trackName == "mixamorig:Hips") hips_track = track; else if (trackName == "Root") { root_track = track; root_node = track->getAssociatedNode(); } } assert(false); root_track->removeAllKeyFrames(); std::cout << hips_track << " " << root_track << "\n"; std::cout << hips_track->getNumKeyFrames() << " " << root_track->getNumKeyFrames() << "\n"; assert(hips_track && root_track); Ogre::Vector3 delta = Ogre::Vector3::ZERO; for(j = 0; j < hips_track->getNumKeyFrames(); j++) { float timePos = hips_track->getNodeKeyFrame(j)->getTime(); Ogre::Vector3 trans = hips_track->getNodeKeyFrame(j)->getTranslate(); Ogre::Vector3 hips_trans(0, 0, 0); Ogre::Vector3 root_trans(0, 0, 0); hips_track->getNodeKeyFrame(j)->setTranslate(hips_trans); Ogre::TransformKeyFrame *nk = root_track->createNodeKeyFrame(timePos); nk->setTranslate(root_trans - delta); nk->setScale(Ogre::Vector3(1, 1, 1)); nk->setRotation(Ogre::Quaternion()); std::cout << animName << " delta: " << j << " " << timePos << " " << root_trans - delta << "\n"; delta = root_trans; } for(j = 0; j < root_track->getNumKeyFrames(); j++) { float timePos = hips_track->getNodeKeyFrame(j)->getTime(); Ogre::Vector3 root_trans = hips_track->getNodeKeyFrame(j)->getTranslate(); std::cout << animName << " delta: root: " << j << " " << timePos << " " << root_trans << "\n"; } } // assert(false); #endif setAnimation(ANIM_IDLE); } bool CharacterController::keyPressed(const OgreBites::KeyboardEvent& evt) { OgreBites::Keycode key = evt.keysym.sym; if (key == 'q' && (mAnimID == ANIM_IDLE)) { /* ... */ mTimer = 0; } else if (key == 'e') { } else if (key == 'w') mKeyDirection.z = -1; else if (key == 'a') mKeyDirection.x = -1; else if (key == 's') mKeyDirection.z = 1; else if (key == 'd') mKeyDirection.x = 1; if (key == OgreBites::SDLK_LSHIFT) mRunning = true; if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_IDLE) { if (mRunning) setAnimation(ANIM_RUN, true); else setAnimation(ANIM_WALK, true); // std::cout << "Walking\n"; } else if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_WALK && mRunning) setAnimation(ANIM_RUN); return true; } bool CharacterController::keyReleased(const OgreBites::KeyboardEvent& evt) { OgreBites::Keycode key = evt.keysym.sym; if (key == 'w' && mKeyDirection.z == -1) mKeyDirection.z = 0; else if (key == 'a' && mKeyDirection.x == -1) mKeyDirection.x = 0; else if (key == 's' && mKeyDirection.z == 1) mKeyDirection.z = 0; else if (key == 'd' && mKeyDirection.x == 1) mKeyDirection.x = 0; if (key == OgreBites::SDLK_LSHIFT) mRunning = false; if (mKeyDirection.isZeroLength() && (mAnimID == ANIM_WALK || mAnimID == ANIM_RUN)) setAnimation(ANIM_IDLE); else if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_RUN && !mRunning) setAnimation(ANIM_WALK); return true; } bool CharacterController::mouseMoved(const OgreBites::MouseMotionEvent& evt) { // update camera goal based on mouse movement updateCameraGoal(-0.18f * evt.xrel, -0.12f * evt.yrel, 0); return true; } bool CharacterController::mouseWheelRolled(const OgreBites::MouseWheelEvent& evt) { // update camera goal based on mouse movement updateCameraGoal(0, 0, -0.15f * evt.y); return true; } bool CharacterController::mousePressed(const OgreBites::MouseButtonEvent& evt) { std::cout << "Mouse press\n"; return false; } void CharacterController::frameRendered(const Ogre::FrameEvent& evt) { updateBody(evt.timeSinceLastFrame); updateAnimations(evt.timeSinceLastFrame); updateCamera(evt.timeSinceLastFrame); if (evt.timeSinceLastFrame > 0) updateRootMotion(evt.timeSinceLastFrame); } bool CharacterController::frameStarted(const Ogre::FrameEvent& evt) { return true; } void CharacterController::updateCameraGoal(Real deltaYaw, Real deltaPitch, Real deltaZoom) { mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT); if (!(mPivotPitch + deltaPitch > 25 && deltaPitch > 0) && !(mPivotPitch + deltaPitch < -60 && deltaPitch < 0)) { mCameraPivot->pitch(Ogre::Degree(deltaPitch), Ogre::Node::TS_LOCAL); mPivotPitch += deltaPitch; } Real dist = mCameraGoal->_getDerivedPosition().distance(mCameraPivot->_getDerivedPosition()); Real distChange = deltaZoom * dist; // bound the zoom if (!(dist + distChange < 8 && distChange < 0) && !(dist + distChange > 25 && distChange > 0)) mCameraGoal->translate(0, 0, distChange, Ogre::Node::TS_LOCAL); } void CharacterController::updateBody(Real delta) { mGoalDirection = Ogre::Vector3::ZERO; if (mKeyDirection != Ogre::Vector3::ZERO) { // calculate actually goal direction in world based on player's key directions mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis(); mGoalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis(); mGoalDirection.y = 0; mGoalDirection.normalise(); Ogre::Quaternion toGoal = mBodyNode->getOrientation().zAxis().getRotationTo(mGoalDirection); // calculate how much the character has to turn to face goal direction Real yawToGoal = toGoal.getYaw().valueDegrees(); // this is how much the character CAN turn this frame Real yawAtSpeed = yawToGoal / Math::Abs(yawToGoal) * delta * TURN_SPEED; // reduce "turnability" if we're in midair // if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f; if (yawToGoal < 0) yawToGoal = std::min(0, std::max(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp(yawToGoal, yawAtSpeed, 0); else if (yawToGoal > 0) yawToGoal = std::max(0, std::min(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp(yawToGoal, 0, yawAtSpeed); mBodyNode->yaw(Ogre::Degree(yawToGoal)); // move in current body direction (not the goal direction) // mBodyNode->translate(0, 0, delta * RUN_SPEED * mAnims[mAnimID]->getWeight(), // Ogre::Node::TS_LOCAL); #if 0 if (mBaseAnimID == ANIM_JUMP_LOOP) { // if we're jumping, add a vertical offset too, and apply gravity mBodyNode->translate(0, mVerticalVelocity * deltaTime, 0, Node::TS_LOCAL); mVerticalVelocity -= GRAVITY * deltaTime; Vector3 pos = mBodyNode->getPosition(); if (pos.y <= CHAR_HEIGHT) { // if we've hit the ground, change to landing state pos.y = CHAR_HEIGHT; mBodyNode->setPosition(pos); setBaseAnimation(ANIM_JUMP_END, true); mTimer = 0; } } #endif } } void CharacterController::updateAnimations(Real delta) { int i, j, k; Real animSpeed = 1; mTimer += delta; if (mAnimID != ANIM_NONE) { if (mAnimID == ANIM_WALK) mAnims[mAnimID]->addTime(delta * 1.0f); else mAnims[mAnimID]->addTime(delta * animSpeed); } fadeAnimations(delta); } void CharacterController::updateRootMotion(Real delta) { Ogre::Vector3 boneMotion = mRootBone->getPosition(); OgreAssert(delta > 0.0f, "Zero delta"); #if 0 Ogre::Vector3 motion = boneMotion - rootMotion; if (motion.squaredLength() > 0.1f * 0.1f) motion = Ogre::Vector3(); rootMotion = boneMotion; #endif #if 0 float mass = mRigidBody->getMass(); std::cout << "Root bone position: " << boneMotion << "\n"; std::cout << "body mass: " << mass << "\n"; #endif /* Kinematic motion */ Ogre::Quaternion rot = mBodyNode->getOrientation(); Ogre::Vector3 gravity(0, -9.8, 0); Ogre::Vector3 velocity = rot * boneMotion / delta; velocity += gravity * delta; Ogre::Vector3 rotMotion = velocity * delta; btTransform from(convert(mBodyNode->getOrientation()), convert(mBodyNode->getPosition())); mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion); // WorldData::get_singleton()->getWorld()->testBodyMotion(mRigidBody, from, Ogre::Bullet::convert(rotMotion), true, // nullptr, false, std::set()); } void CharacterController::fadeAnimations(Real delta) { int i; for (i = 0; i < NUM_ANIMS; i++) { if (mFadingIn[i]) { // slowly fade this animation in until it has full weight Real newWeight = mAnims[i]->getWeight() + delta * ANIM_FADE_SPEED; mAnims[i]->setWeight(Math::Clamp(newWeight, 0, 1)); if (newWeight >= 1) mFadingIn[i] = false; } else if (mFadingOut[i]) { // slowly fade this animation out until it has no weight, and then disable it Real newWeight = mAnims[i]->getWeight() - delta * ANIM_FADE_SPEED; mAnims[i]->setWeight(Math::Clamp(newWeight, 0, 1)); if (newWeight <= 0) { mAnims[i]->setEnabled(false); mFadingOut[i] = false; } } } } void CharacterController::updateCamera(Real delta) { // place the camera pivot roughly at the character's shoulder mCameraPivot->setPosition(mBodyNode->getPosition() + Ogre::Vector3::UNIT_Y * CAM_HEIGHT); // move the camera smoothly to the goal Ogre::Vector3 goalOffset = mCameraGoal->_getDerivedPosition() - mCameraNode->getPosition(); mCameraNode->translate(goalOffset * delta * 9.0f); // always look at the pivot mCameraNode->lookAt(mCameraPivot->_getDerivedPosition(), Ogre::Node::TS_PARENT); } void CharacterController::setAnimation(AnimID id, bool reset) { assert(id >= 0 && id < NUM_ANIMS); if (mAnimID != ANIM_NONE) { mFadingIn[mAnimID] = false; mFadingOut[mAnimID] = true; } mAnimID = id; if (id != ANIM_NONE) { mAnims[id]->setEnabled(true); mAnims[id]->setWeight(0); mFadingOut[id] = false; mFadingIn[id] = true; if (reset) mAnims[id]->setTimePosition(0); } } #if 0 bool CharacterController::recoverFromPenetration(btRigidBody *body, const btTransform &body_position, btScalar recover_movement_scale, bool infinite_inertia, btVector3 &delta_recover_movement, recoverResult *recover_result, const std::set &exclude) { return false; } bool CharacterController::bodyTestMotion(btRigidBody *body, const btTransform &from, const btVector3 &motion, bool infinite_inertia, textMotionResult *result, bool excludeRaycastShapes, std::set exclude) { int t; btTransform body_transform; btVector3 initial_recover_motion(0, 0, 0); // phase one - depenetration for (t = 0; t < RECOVERING_MOVEMENT_CYCLES; t++) if (!recoverFromPenetration(body, body_transform, RECOVERING_MOVEMENT_SCALE, infinite_inertia, initial_recover_motion, nullptr, exclude)) break; return false; } #endif class KeyHandler : public OgreBites::InputListener { bool keyPressed(const OgreBites::KeyboardEvent& evt) override { if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) { Ogre::Root::getSingleton().queueEndRendering(); } return true; } }; bool MainWorld::frameStarted(const Ogre::FrameEvent& evt) { WorldData::get_singleton()->update(evt.timeSinceLastFrame); return true; } class App : public OgreBites::ApplicationContext { Ogre::SceneNode *mCameraNode; Ogre::SceneManager *mScnMgr; std::unique_ptr mCharacter; KeyHandler mKeyHandler; MainWorld mWorld; public: App(); virtual ~App(); void setup(); void locateResources(); void initCamera(); Ogre::SceneManager *getSceneManager() { return mScnMgr; } void createContent(); void createCharacter(); void setupWorld(); }; App::App() : OgreBites::ApplicationContext("App") { } void App::setup() { OgreBites::ApplicationContext::setup(); Ogre::Root *root = getRoot(); Ogre::SceneManager *scnMgr = root->createSceneManager(); mScnMgr = scnMgr; } void App::locateResources() { OgreBites::ApplicationContext::locateResources(); } App::~App() { } void App::initCamera() { // also need to tell where we are mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode(); mCameraNode->setPosition(0, 2, 3); mCameraNode->lookAt(Ogre::Vector3(0, 1, -1), Ogre::Node::TS_PARENT); // create the camera Ogre::Camera *cam = mScnMgr->createCamera("tps_camera"); cam->setNearClipDistance(0.1f); // specific to this sample cam->setAutoAspectRatio(true); mCameraNode->attachObject(cam); // and tell it to render into the main window getRenderWindow()->addViewport(cam); } void App::setupWorld() { addInputListener(&mKeyHandler); mWorld.setup(); getRoot()->addFrameListener(&mWorld); } void App::createCharacter() { Ogre::Camera *cam = static_cast(mCameraNode->getAttachedObject("tps_camera")); mCharacter = std::make_unique(mCameraNode, cam, mScnMgr, &mWorld); // mInputListenerChain = TouchAgnosticInputListenerChain(getRenderWindow(), {&mKeyHandler, mCharacter.get()}); addInputListener(mCharacter.get()); WorldData::get_singleton()->initPagedWorld(cam); } void App::createContent() { // without light we would just get a black screen Ogre::Light* light = mScnMgr->createLight("MainLight"); Ogre::SceneNode* lightNode = mScnMgr->getRootSceneNode()->createChildSceneNode(); // lightNode->setPosition(0, 10, 15); lightNode->setDirection(Ogre::Vector3(0.55, -0.3, 0.75).normalisedCopy()); lightNode->attachObject(light); light->setType(Ogre::Light::LT_DIRECTIONAL); light->setDiffuseColour(Ogre::ColourValue::White); light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4)); mScnMgr->setSkyBox(true, "Skybox", 490); } int main(int argc, char *argv[]) { App ctx; ctx.initApp(); // get a pointer to the already created root Ogre::Root* root = ctx.getRoot(); Ogre::SceneManager* scnMgr = ctx.getSceneManager(); // register our scene with the RTSS Ogre::RTShader::ShaderGenerator* shadergen = Ogre::RTShader::ShaderGenerator::getSingletonPtr(); shadergen->addSceneManager(scnMgr); WorldData::init(root, scnMgr); ctx.setWindowGrab(true); ctx.createContent(); ctx.initCamera(); ctx.setupWorld(); ctx.createCharacter(); // register for input events // KeyHandler keyHandler; // ctx.addInputListener(&keyHandler); ctx.getRoot()->startRendering(); ctx.setWindowGrab(false); ctx.closeApp(); WorldData::cleanup(); return 0; }