#include #include #include "GameData.h" #include "character.h" #include "controller.h" #if 0 #define CAM_HEIGHT 1.6f // height of camera above character's center of mass CharacterController::CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam, Ogre::SceneManager *scnMgr, Character *character) : mCameraNode(camNode) , mCamera(cam) , mScnMgr(scnMgr) , mPivotPitch(0) , mVerticalVelocity(0) , mRunning(false) , mCharacter(character) , mCameraPivot(nullptr) { setupCamera(); } CharacterController::~CharacterController() { } 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; std::cout << "ORIGINAL: " << mCameraNode->getPosition() << "\n"; } bool CharacterController::keyPressed(const OgreBites::KeyboardEvent &evt) { OgreBites::Keycode key = evt.keysym.sym; if (key == 'q') { /* ... */ } 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() && mCharacter->isIdle()) { if (mRunning) mCharacter->act_run(); else mCharacter->act_walk(); // std::cout << "Walking\n"; } else if (!mKeyDirection.isZeroLength() && mCharacter->isWalking() && mRunning) mCharacter->act_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() && mCharacter->isMoving()) mCharacter->act_idle(); else if (!mKeyDirection.isZeroLength() && mCharacter->isRunning() && !mRunning) mCharacter->act_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) { Ogre::Vector3 goalDirection = Ogre::Vector3::ZERO; updateCamera(evt.timeSinceLastFrame); if (mKeyDirection != Ogre::Vector3::ZERO) { // calculate actually goal direction in world based on player's key directions goalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis(); goalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis(); goalDirection.y = 0; goalDirection.normalise(); mCharacter->setGoalDirection(goalDirection); } } bool CharacterController::frameStarted(const Ogre::FrameEvent &evt) { return true; } void CharacterController::updateCameraGoal(Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::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; } Ogre::Real dist = mCameraGoal->_getDerivedPosition().distance( mCameraPivot->_getDerivedPosition()); Ogre::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::updateCamera(Ogre::Real delta) { // static int count = 0; // place the camera pivot roughly at the character's shoulder Ogre::Vector3 pivotOffset = mCharacter->getPosition() - mCameraPivot->_getDerivedPosition(); Ogre::Vector3 pivotPos = mCameraPivot->_getDerivedPosition(); mCameraPivot->setPosition(pivotPos + pivotOffset * 0.1f * delta + 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); } #endif #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 CharacterController::CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam, Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world) : mCameraNode(camNode) , mCamera(cam) , mScnMgr(scnMgr) , mPivotPitch(0) , mVerticalVelocity(0) , mAnimID(ANIM_NONE) , mRunning(false) , mWorld(world) , mCollisionShape(nullptr) , mGhostObject(nullptr) , gvelocity(0.0f, 0.0f, 0.0f) , mUpdate(false) { setupBody(); setupCamera(); setupAnimations(); Ogre::Root::getSingleton().addFrameListener(this); } 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); mWorld->attachCollisionObject(mGhostObject, mBodyEnt, 1, 0x7FFFFFF); mController = new Ogre::Bullet::KinematicMotionSimple(mGhostObject, mBodyNode); OgreAssert(mGhostObject, "Need GhostObject"); OgreAssert(mController, "Need controller"); mWorld->getBtWorld()->addAction(mController); OgreAssert(mCollisionShape, "No collision shape"); #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 OgreAssert(mSkeleton->hasBone("Root"), "No root bone"); mRootBone = mSkeleton->getBone("Root"); OgreAssert(mRootBone, "No root bone"); } 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) { ECS::Input &input = ECS::get().get_mut(); 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) { if (mUpdate) { 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(Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::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; } Ogre::Real dist = mCameraGoal->_getDerivedPosition().distance( mCameraPivot->_getDerivedPosition()); Ogre::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(Ogre::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 Ogre::Real yawToGoal = toGoal.getYaw().valueDegrees(); // this is how much the character CAN turn this frame Ogre::Real yawAtSpeed = yawToGoal / Ogre::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(Ogre::Real delta) { int i, j, k; Ogre::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); } struct EntityCollisionListener { const Ogre::MovableObject *entity; Ogre::Bullet::CollisionListener *listener; }; void CharacterController::updateRootMotion(Ogre::Real delta) { int i, j, k; Ogre::Quaternion rot = mBodyNode->getOrientation(); Ogre::Vector3 pos = mBodyNode->getPosition(); Ogre::Vector3 boneMotion = mRootBone->getPosition(); Ogre::Vector3 velocity = rot * boneMotion / delta; OgreAssert(delta > 0.0f, "Zero delta"); int maxPen = 0; Ogre::Vector3 colNormal; bool is_on_floor = false; bool penetration = false; Ogre::Vector3 gravity(0, -9.8, 0); gvelocity += gravity * delta; velocity += gvelocity; Ogre::Vector3 rotMotion = velocity * delta; btVector3 currentPosition = mGhostObject->getWorldTransform().getOrigin(); is_on_floor = mController->isOnFloor(); penetration = mController->isPenetrating(); if (is_on_floor) gvelocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); btTransform from(convert(mBodyNode->getOrientation()), convert(mBodyNode->getPosition())); mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion); #if 0 std::cout << "rotMotion: " << rotMotion << " isOnFloor: " << is_on_floor << " pairs: " << mGhostObject->getOverlappingPairCache()->getNumOverlappingPairs() << " manifolds: " << mController->getManifolds() << " penetration: " << penetration << "\n"; std::cout << " position: " << mBodyNode->getPosition() << "\n"; #endif #if 0 std::cout << " velocity: " << velocity << " penetration: " << penetration << " rotMotion: " << rotMotion << "\n"; std::cout << "old position: " << pos << " new position: " << mBodyNode->getPosition() << "\n"; #endif } void CharacterController::fadeAnimations(Ogre::Real delta) { int i; for (i = 0; i < NUM_ANIMS; i++) { if (mFadingIn[i]) { // slowly fade this animation in until it has full weight Ogre::Real newWeight = mAnims[i]->getWeight() + delta * ANIM_FADE_SPEED; mAnims[i]->setWeight( Ogre::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 Ogre::Real newWeight = mAnims[i]->getWeight() - delta * ANIM_FADE_SPEED; mAnims[i]->setWeight( Ogre::Math::Clamp(newWeight, 0, 1)); if (newWeight <= 0) { mAnims[i]->setEnabled(false); mFadingOut[i] = false; } } } } void CharacterController::updateCamera(Ogre::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); } }