mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-14 01:19:59 +00:00
Merge branch 'IK' into 'master'
Draft: Inverse Kinematics for legs and overall better Actor gound alignment. See merge request OpenMW/openmw!4548
This commit is contained in:
commit
c32e8c092e
@ -23,7 +23,7 @@ add_openmw_dir (mwrender
|
||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
|
||||
postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples
|
||||
actorutil distortion animationpriority bonegroup blendmask animblendcontroller
|
||||
actorutil distortion easyik animationpriority bonegroup blendmask animblendcontroller
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
@ -197,6 +197,9 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime)
|
||||
|
||||
mEnvironment.setFrameDuration(frametime);
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
|
||||
try
|
||||
{
|
||||
// update input
|
||||
@ -285,16 +288,6 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime)
|
||||
}
|
||||
}
|
||||
|
||||
// update physics
|
||||
{
|
||||
ScopedProfile<UserStatsType::Physics> profile(frameStart, frameNumber, *timer, *stats);
|
||||
|
||||
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mWorld->updatePhysics(frametime, paused, frameStart, frameNumber, *stats);
|
||||
}
|
||||
}
|
||||
|
||||
// update world
|
||||
{
|
||||
ScopedProfile<UserStatsType::World> profile(frameStart, frameNumber, *timer, *stats);
|
||||
@ -305,6 +298,16 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime)
|
||||
}
|
||||
}
|
||||
|
||||
// update physics
|
||||
{
|
||||
ScopedProfile<UserStatsType::Physics> profile(frameStart, frameNumber, *timer, *stats);
|
||||
|
||||
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mWorld->updatePhysics(frametime, paused, frameStart, frameNumber, *stats);
|
||||
}
|
||||
}
|
||||
|
||||
// update GUI
|
||||
{
|
||||
ScopedProfile<UserStatsType::Gui> profile(frameStart, frameNumber, *timer, *stats);
|
||||
@ -339,9 +342,6 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime)
|
||||
|
||||
mStereoManager->updateSettings(Settings::camera().mNearClip, Settings::camera().mViewingDistance);
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
|
||||
// update GUI by world data
|
||||
{
|
||||
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats);
|
||||
|
@ -36,6 +36,7 @@ namespace MWPhysics
|
||||
, mExternalCollisionMode(true)
|
||||
, mActive(false)
|
||||
, mTaskScheduler(scheduler)
|
||||
, mCurrentDeltaZ(0.0f)
|
||||
{
|
||||
// We can not create actor without collisions - he will fall through the ground.
|
||||
// In this case we should autogenerate collision box based on mesh shape
|
||||
|
@ -146,6 +146,8 @@ namespace MWPhysics
|
||||
|
||||
void setActive(bool value) { mActive = value; }
|
||||
|
||||
float mCurrentDeltaZ;
|
||||
|
||||
DetourNavigator::CollisionShapeType getCollisionShapeType() const { return mCollisionShapeType; }
|
||||
|
||||
private:
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <LinearMath/btQuickprof.h>
|
||||
#include <LinearMath/btVector3.h>
|
||||
|
||||
#include <components/debug/debugdraw.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/loadgmst.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
@ -27,6 +28,7 @@
|
||||
#include <components/misc/strings/conversion.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/settings/values.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
@ -41,6 +43,8 @@
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "../mwrender/bulletdebugdraw.hpp"
|
||||
#include "../mwrender/easyik.hpp"
|
||||
#include "../mwrender/renderingmanager.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
@ -744,18 +748,162 @@ namespace MWPhysics
|
||||
mActorsPositions.clear();
|
||||
if (!mActors.empty())
|
||||
mActorsPositions.reserve(mActors.size() - 1);
|
||||
for (const auto& [ptr, physicActor] : mActors)
|
||||
|
||||
for (const auto& [ref, physicActor] : mActors)
|
||||
{
|
||||
if (physicActor.get() == player)
|
||||
continue;
|
||||
mActorsPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition());
|
||||
auto colliderPos = physicActor->getSimulationPosition();
|
||||
auto actptr = physicActor->getPtr();
|
||||
|
||||
// Reset actor armature to collider's position before doing any additional adjustments
|
||||
auto visualPos = actptr.getRefData().getBaseNode()->getPosition();
|
||||
world->moveObject(actptr, colliderPos, false, false);
|
||||
|
||||
// IK config and variables
|
||||
osg::Vec3 desiredPos = colliderPos;
|
||||
float armatureZLerpSpeed = 5;
|
||||
float maxVisualZDelta = physicActor.get()->getHalfExtents().z() * 2 / 4;
|
||||
MWPhysics::RayCastingResult leftRayCast;
|
||||
MWPhysics::RayCastingResult rightRayCast;
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>> leftLegBoneChain;
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>> rightLegBoneChain;
|
||||
|
||||
float leftFootHeight
|
||||
= estimateFootHeight(actptr.getRefData().getBaseNode()->asGroup(), "Bip01 L Foot", "Bip01 L Toe0");
|
||||
float rightFootHeight
|
||||
= estimateFootHeight(actptr.getRefData().getBaseNode()->asGroup(), "Bip01 R Foot", "Bip01 R Toe0");
|
||||
|
||||
osg::ref_ptr<osg::MatrixTransform> leftFootBone;
|
||||
osg::ref_ptr<osg::MatrixTransform> rightFootBone;
|
||||
|
||||
float desiredDeltaZ = 0.0f;
|
||||
|
||||
if (physicActor.get()->getOnGround())
|
||||
{
|
||||
// Finding leg bones
|
||||
std::vector<std::string> leftLegBoneChainNames = { "Bip01 L Thigh", "Bip01 L Calf", "Bip01 L Foot" };
|
||||
std::vector<std::string> rightLegBoneChainNames = { "Bip01 R Thigh", "Bip01 R Calf", "Bip01 R Foot" };
|
||||
gatherMatchingBones(
|
||||
actptr.getRefData().getBaseNode()->asGroup(), leftLegBoneChainNames, leftLegBoneChain);
|
||||
gatherMatchingBones(
|
||||
actptr.getRefData().getBaseNode()->asGroup(), rightLegBoneChainNames, rightLegBoneChain);
|
||||
|
||||
// Compute world transformation for foot bones
|
||||
osg::Matrix leftFootWorldMatrix, rightFootWorldMatrix;
|
||||
if (leftLegBoneChain.size() == leftLegBoneChainNames.size())
|
||||
leftFootBone = leftLegBoneChain.back();
|
||||
if (leftFootBone)
|
||||
leftFootWorldMatrix = osg::computeLocalToWorld(leftFootBone->getParentalNodePaths()[0]);
|
||||
osg::Vec3f leftFootPos
|
||||
= leftFootBone ? static_cast<osg::Vec3f>(leftFootWorldMatrix.getTrans()) : colliderPos;
|
||||
|
||||
if (rightLegBoneChain.size() == rightLegBoneChainNames.size())
|
||||
rightFootBone = rightLegBoneChain.back();
|
||||
if (rightFootBone)
|
||||
rightFootWorldMatrix = osg::computeLocalToWorld(rightFootBone->getParentalNodePaths()[0]);
|
||||
osg::Vec3f rightFootPos
|
||||
= rightFootBone ? static_cast<osg::Vec3f>(rightFootWorldMatrix.getTrans()) : colliderPos;
|
||||
|
||||
// Perform raycasts
|
||||
leftRayCast = castRay(leftFootPos, leftFootPos - osg::Vec3f(0.0f, 0.0f, 50.0f),
|
||||
CollisionType_HeightMap + CollisionType_World);
|
||||
rightRayCast = castRay(rightFootPos, rightFootPos - osg::Vec3f(0.0f, 0.0f, 50.0f),
|
||||
CollisionType_HeightMap + CollisionType_World);
|
||||
|
||||
// Determine the desired delta Z
|
||||
if (leftRayCast.mHit && rightRayCast.mHit)
|
||||
{
|
||||
desiredDeltaZ = std::min(leftRayCast.mHitPos.z(), rightRayCast.mHitPos.z()) - colliderPos.z();
|
||||
}
|
||||
else if (leftRayCast.mHit)
|
||||
{
|
||||
desiredDeltaZ = leftRayCast.mHitPos.z() - colliderPos.z();
|
||||
}
|
||||
else if (rightRayCast.mHit)
|
||||
{
|
||||
desiredDeltaZ = rightRayCast.mHitPos.z() - colliderPos.z();
|
||||
}
|
||||
}
|
||||
|
||||
// Lerp the current delta Z towards the desired delta Z
|
||||
physicActor->mCurrentDeltaZ
|
||||
= std::lerp(physicActor->mCurrentDeltaZ, desiredDeltaZ, mPhysicsDt * armatureZLerpSpeed);
|
||||
|
||||
// Calculate the new armature position using the lerped delta Z
|
||||
osg::Vec3 armaturePos = colliderPos;
|
||||
armaturePos.z() += physicActor->mCurrentDeltaZ;
|
||||
|
||||
// For some reason player and actors supposed to be kept separately
|
||||
if (physicActor.get() != player)
|
||||
mActorsPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition());
|
||||
|
||||
// Finally adjust position based on the new armature position
|
||||
world->moveObject(actptr, armaturePos, false, false);
|
||||
|
||||
// Casting up from feet to detect clipping
|
||||
MWPhysics::RayCastingResult rightRayUpCast, leftRayUpCast;
|
||||
if (rightFootBone)
|
||||
{
|
||||
auto rightFootWorldMatrix = osg::computeLocalToWorld(rightFootBone->getParentalNodePaths()[0]);
|
||||
osg::Vec3f rightFootPos = static_cast<osg::Vec3f>(rightFootWorldMatrix.getTrans());
|
||||
rightRayUpCast = castRay(rightFootPos, rightFootPos + osg::Vec3f(0.0f, 0.0f, 50.0f),
|
||||
CollisionType_HeightMap + CollisionType_World);
|
||||
}
|
||||
|
||||
if (rightRayUpCast.mHit)
|
||||
{
|
||||
auto& debugRender = world->getRenderingManager()->getDebugDrawer();
|
||||
/*debugRender.drawCube(rightRayCast.mHitPos, osg::Vec3(10.0, 10.0, 10.0), Debug::colorBlue);*/
|
||||
|
||||
// Perform IK for right leg
|
||||
if (rightLegBoneChain.size() > 0)
|
||||
{
|
||||
// Note that its possible to provide a pole direction to specify in which direction a knee should
|
||||
// bend. Good guesstimate for bipedal actors might be providing actor facing direction (in world
|
||||
// space) as a pole direction (not done here, I haven't figured how to find actor facing direction)
|
||||
EasyIK rightLegIK(rightLegBoneChain,
|
||||
rightRayUpCast.mHitPos + osg::Vec3f(0.0f, 0.0f, rightFootHeight), 10, 0.05f, std::nullopt,
|
||||
&debugRender);
|
||||
rightLegIK.solveIK();
|
||||
rightLegIK.alignFoot(rightRayUpCast.mHitNormal * -1);
|
||||
if (mDebugDrawEnabled)
|
||||
rightLegIK.debugDraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (leftFootBone)
|
||||
{
|
||||
auto leftFootWorldMatrix = osg::computeLocalToWorld(leftFootBone->getParentalNodePaths()[0]);
|
||||
osg::Vec3f leftFootPos = static_cast<osg::Vec3f>(leftFootWorldMatrix.getTrans());
|
||||
leftRayUpCast = castRay(leftFootPos, leftFootPos + osg::Vec3f(0.0f, 0.0f, 50.0f),
|
||||
CollisionType_HeightMap + CollisionType_World);
|
||||
}
|
||||
|
||||
if (leftRayUpCast.mHit)
|
||||
{
|
||||
auto& debugRender = world->getRenderingManager()->getDebugDrawer();
|
||||
/*debugRender.drawCube(leftRayCast.mHitPos, osg::Vec3(10.0, 10.0, 10.0), Debug::colorBlue);*/
|
||||
|
||||
// Perform IK for left leg
|
||||
if (leftLegBoneChain.size() > 0)
|
||||
{
|
||||
EasyIK leftLegIK(leftLegBoneChain, leftRayUpCast.mHitPos + osg::Vec3f(0.0f, 0.0f, leftFootHeight),
|
||||
10, 0.05f, std::nullopt, &debugRender);
|
||||
leftLegIK.solveIK();
|
||||
leftLegIK.alignFoot(leftRayUpCast.mHitNormal * -1);
|
||||
if (mDebugDrawEnabled)
|
||||
leftLegIK.debugDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [ptr, pos] : mActorsPositions)
|
||||
world->moveObject(ptr, pos, false, false);
|
||||
/*for (const auto& [ptr, pos] : mActorsPositions)
|
||||
{
|
||||
|
||||
world->moveObject(ptr, desiredPos, false, false);
|
||||
}
|
||||
|
||||
if (player != nullptr)
|
||||
world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false);
|
||||
world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false);*/
|
||||
}
|
||||
|
||||
void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object)
|
||||
@ -973,4 +1121,47 @@ namespace MWPhysics
|
||||
{
|
||||
return lhs.mRawActors == rhs.mRawActors;
|
||||
}
|
||||
|
||||
void PhysicsSystem::gatherMatchingBones(osg::Group* parentNode, const std::vector<std::string>& boneChainNames,
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>>& boneChain)
|
||||
{
|
||||
if (!parentNode)
|
||||
return;
|
||||
|
||||
for (unsigned int i = 0; i < parentNode->getNumChildren(); ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::Node> child = parentNode->getChild(i);
|
||||
|
||||
// asMatrixTransform will break if its not a bone
|
||||
if (std::find(boneChainNames.begin(), boneChainNames.end(), child->getName()) != boneChainNames.end())
|
||||
{
|
||||
boneChain.push_back(child->asTransform()->asMatrixTransform());
|
||||
}
|
||||
|
||||
osg::Group* group = child->asGroup();
|
||||
if (group)
|
||||
{
|
||||
gatherMatchingBones(group, boneChainNames, boneChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float PhysicsSystem::estimateFootHeight(
|
||||
osg::Group* parentNode, const std::string& footBoneName, const std::string& toeBoneName)
|
||||
{
|
||||
std::vector<std::string> boneNames = { footBoneName, toeBoneName };
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>> bones;
|
||||
gatherMatchingBones(parentNode, boneNames, bones);
|
||||
|
||||
if (bones.size() == 2)
|
||||
{
|
||||
osg::Vec3f footPos = bones[0]->getMatrix().getTrans();
|
||||
osg::Vec3f toePos = bones[1]->getMatrix().getTrans();
|
||||
return std::abs(footPos.z() - toePos.z());
|
||||
}
|
||||
|
||||
// Return a fixed constant value as a fallback
|
||||
return 8.0f; // Adjust this value as needed
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <osg/Quat>
|
||||
#include <osg/Timer>
|
||||
#include <osg/ref_ptr>
|
||||
#include <osgAnimation/Bone>
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
@ -287,6 +288,12 @@ namespace MWPhysics
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
void reportCollision(const btVector3& position, const btVector3& normal);
|
||||
|
||||
void gatherMatchingBones(osg::Group* parentNode, const std::vector<std::string>& boneChainNames,
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>>& boneChain);
|
||||
|
||||
float estimateFootHeight(
|
||||
osg::Group* parentNode, const std::string& footBoneName, const std::string& toeBoneName);
|
||||
|
||||
private:
|
||||
void updateWater();
|
||||
|
||||
|
206
apps/openmw/mwrender/easyik.cpp
Normal file
206
apps/openmw/mwrender/easyik.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
// Authors: Joar Engberg, GPT4o, Maksim Eremenko :)
|
||||
// Ported with a significant GPT4o (Copilot) assistance from a C# (Unity script) Inverse Kinematic solver implementation
|
||||
// by Joar Engberg (EasyIK) https://github.com/joaen/EasyIK/blob/master/EasyIK/Assets/Scripts/EasyIK.cs
|
||||
|
||||
#include "easyik.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Quat>
|
||||
#include <osg/Vec3>
|
||||
#include <vector>
|
||||
|
||||
// Note: armatureBones are in local space, poleDirection and ikTarget are in world space.
|
||||
EasyIK::EasyIK(const std::vector<osg::ref_ptr<osg::MatrixTransform>>& armatureBones, const osg::Vec3f& ikTarget,
|
||||
int iterations, float tolerance, std::optional<osg::Vec3f> poleDirection, Debug::DebugDrawer* debugDrawer)
|
||||
: mNumberOfJoints(armatureBones.size()) // Number of joints is one more than the number of bones
|
||||
, mIkTarget(ikTarget)
|
||||
, mIterations(iterations)
|
||||
, mTolerance(tolerance)
|
||||
, mPoleDirection(poleDirection)
|
||||
, mDebugDrawer(debugDrawer)
|
||||
, mArmatureBones(armatureBones)
|
||||
{
|
||||
initialize();
|
||||
}
|
||||
|
||||
void EasyIK::initialize()
|
||||
{
|
||||
mJointChainLength = 0;
|
||||
mJointPositions.resize(mNumberOfJoints);
|
||||
mLocalJointPositions.resize(mNumberOfJoints);
|
||||
mBoneLengths.resize(mNumberOfJoints - 1);
|
||||
|
||||
for (int i = 0; i < mNumberOfJoints - 1; ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::MatrixTransform> current = mArmatureBones[i];
|
||||
osg::ref_ptr<osg::MatrixTransform> child = mArmatureBones[i + 1];
|
||||
mBoneLengths[i] = child->getMatrix().getTrans().length();
|
||||
mJointChainLength += mBoneLengths[i];
|
||||
}
|
||||
}
|
||||
|
||||
void EasyIK::poleConstraint()
|
||||
{
|
||||
if (mNumberOfJoints != 3 || !mPoleDirection.has_value())
|
||||
return;
|
||||
|
||||
osg::Vec3 limbAxis = mJointPositions[2] - mJointPositions[0];
|
||||
limbAxis.normalize();
|
||||
osg::Vec3 poleDirection = mPoleDirection.value();
|
||||
poleDirection.normalize();
|
||||
osg::Vec3 boneDirection = mJointPositions[1] - mJointPositions[0];
|
||||
boneDirection.normalize();
|
||||
|
||||
// Orthonormalize the vectors
|
||||
poleDirection -= limbAxis * (poleDirection * limbAxis);
|
||||
poleDirection.normalize();
|
||||
boneDirection -= limbAxis * (boneDirection * limbAxis);
|
||||
boneDirection.normalize();
|
||||
|
||||
osg::Quat angle;
|
||||
angle.makeRotate(boneDirection, poleDirection);
|
||||
|
||||
mJointPositions[1] = angle * (mJointPositions[1] - mJointPositions[0]) + mJointPositions[0];
|
||||
}
|
||||
|
||||
void EasyIK::backward()
|
||||
{
|
||||
for (int i = mNumberOfJoints - 1; i >= 0; --i)
|
||||
{
|
||||
if (i == mNumberOfJoints - 1)
|
||||
{
|
||||
mJointPositions[i] = mIkTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3 pos = mJointPositions[i] - mJointPositions[i + 1];
|
||||
pos.normalize();
|
||||
mJointPositions[i] = mJointPositions[i + 1] + pos * mBoneLengths[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EasyIK::forward()
|
||||
{
|
||||
for (int i = 0; i < mNumberOfJoints; ++i)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
mJointPositions[i] = osg::computeLocalToWorld(mArmatureBones[0]->getParentalNodePaths()[0]).getTrans();
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3 pos = mJointPositions[i] - mJointPositions[i - 1];
|
||||
pos.normalize();
|
||||
mJointPositions[i] = mJointPositions[i - 1] + pos * mBoneLengths[i - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EasyIK::solveIK()
|
||||
{
|
||||
// Initialize joint positions in world space
|
||||
for (int i = 0; i < mNumberOfJoints; ++i)
|
||||
{
|
||||
mJointPositions[i] = osg::computeLocalToWorld(mArmatureBones[i]->getParentalNodePaths()[0]).getTrans();
|
||||
}
|
||||
|
||||
// Calculate distance to target
|
||||
mDistanceToTarget = (mJointPositions[0] - mIkTarget).length();
|
||||
|
||||
// If the target is out of reach, stretch the chain towards the target
|
||||
if (mDistanceToTarget > mJointChainLength)
|
||||
{
|
||||
osg::Vec3 direction = mIkTarget - mJointPositions[0];
|
||||
direction.normalize();
|
||||
for (int i = 1; i < mNumberOfJoints; ++i)
|
||||
{
|
||||
mJointPositions[i] = mJointPositions[i - 1] + direction * mBoneLengths[i - 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Iteratively solve the IK problem
|
||||
float distToTarget = (mJointPositions[mNumberOfJoints - 1] - mIkTarget).length();
|
||||
int counter = 0;
|
||||
while (distToTarget > mTolerance)
|
||||
{
|
||||
backward();
|
||||
forward();
|
||||
distToTarget = (mJointPositions[mNumberOfJoints - 1] - mIkTarget).length();
|
||||
counter++;
|
||||
if (counter > mIterations)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pole constraint
|
||||
poleConstraint();
|
||||
|
||||
// Apply the calculated positions to the bones in local space
|
||||
for (int i = 0; i < mNumberOfJoints - 1; ++i)
|
||||
{
|
||||
auto boneName = mArmatureBones[i]->getName();
|
||||
|
||||
// World/local conversion data
|
||||
osg::Matrix worldToLocal = osg::computeWorldToLocal(mArmatureBones[i]->getParentalNodePaths()[0]);
|
||||
osg::Quat worldToLocalRot = worldToLocal.getRotate();
|
||||
|
||||
// Directions should be in local space, so after deriving them from joint positions - we convert them to the
|
||||
// local space of the bone. Start direction IS a direction in which the bone is
|
||||
// currently pointing (in its own local space!), an assumption here is made that the bone is always pointing
|
||||
// along 1.0 0 0 in its own local space, so far - this assumption seem to stand correct.
|
||||
osg::Vec3f startDirection = osg::Vec3f(1.0f, 0.0f, 0.0f);
|
||||
osg::Vec3f solvedDirection = worldToLocalRot * (mJointPositions[i + 1] - mJointPositions[i]);
|
||||
solvedDirection.normalize();
|
||||
|
||||
// Find a rotation from startDirection to solvedDirection
|
||||
osg::Quat targetRotation;
|
||||
targetRotation.makeRotate(startDirection, solvedDirection);
|
||||
|
||||
// Rotate the bone
|
||||
osg::Matrix currentMatrix = mArmatureBones[i]->getMatrix();
|
||||
currentMatrix.preMultRotate(targetRotation);
|
||||
mArmatureBones[i]->setMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void EasyIK::alignFoot(const osg::Vec3f& normal)
|
||||
{
|
||||
// This alignes a foot to the ground based on provided normal. Probably angular limits should be introduced.
|
||||
if (mNumberOfJoints < 1)
|
||||
return;
|
||||
|
||||
osg::Vec3f invertedNormal = -normal;
|
||||
osg::Matrix worldToLocal = osg::computeWorldToLocal(mArmatureBones.back()->getParentalNodePaths()[0]);
|
||||
osg::Vec3f localNormal = worldToLocal.getRotate() * invertedNormal;
|
||||
|
||||
osg::Vec3f startDirection = osg::Vec3f(1.0f, 0.0f, 0.0f);
|
||||
osg::Quat rotation;
|
||||
rotation.makeRotate(startDirection, localNormal);
|
||||
|
||||
osg::Matrix currentMatrix = mArmatureBones.back()->getMatrix();
|
||||
currentMatrix.preMultRotate(rotation);
|
||||
mArmatureBones.back()->setMatrix(currentMatrix);
|
||||
}
|
||||
|
||||
void EasyIK::debugDraw()
|
||||
{
|
||||
for (const auto& jointPosition : mJointPositions)
|
||||
{
|
||||
mDebugDrawer->drawCube(jointPosition, osg::Vec3f(10.0f, 10.0f, 10.0f), Debug::colorCyan);
|
||||
}
|
||||
|
||||
if (mPoleDirection.has_value())
|
||||
{
|
||||
osg::Vec3f polePosition
|
||||
= mJointPositions[0] + mPoleDirection.value() * 10.0f; // Arbitrary distance for visualization
|
||||
mDebugDrawer->drawCube(polePosition, osg::Vec3f(5.0f, 5.0f, 5.0f), Debug::colorYellow);
|
||||
}
|
||||
|
||||
mDebugDrawer->drawCube(mIkTarget, osg::Vec3f(5.0f, 5.0f, 5.0f), Debug::colorGreen);
|
||||
}
|
42
apps/openmw/mwrender/easyik.hpp
Normal file
42
apps/openmw/mwrender/easyik.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef OPENMW_MWRENDER_EASYIK_HPP
|
||||
#define OPENMW_MWRENDER_EASYIK_HPP
|
||||
|
||||
#include <components/debug/debugdraw.hpp>
|
||||
#include <optional>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Quat>
|
||||
#include <osg/Vec3>
|
||||
#include <vector>
|
||||
|
||||
class EasyIK
|
||||
{
|
||||
public:
|
||||
EasyIK(const std::vector<osg::ref_ptr<osg::MatrixTransform>>& armatureBones, const osg::Vec3f& ikTarget,
|
||||
int iterations, float tolerance, std::optional<osg::Vec3f> poleDirection, Debug::DebugDrawer* debugDrawer);
|
||||
|
||||
void solveIK();
|
||||
void debugDraw();
|
||||
void alignFoot(const osg::Vec3f& normal);
|
||||
|
||||
private:
|
||||
int mNumberOfJoints;
|
||||
int mIterations;
|
||||
float mTolerance;
|
||||
osg::Vec3f mIkTarget; // As a position in world space
|
||||
std::optional<osg::Vec3f> mPoleDirection; // As a direction vector in world space
|
||||
Debug::DebugDrawer* mDebugDrawer;
|
||||
|
||||
std::vector<osg::ref_ptr<osg::MatrixTransform>> mArmatureBones;
|
||||
std::vector<osg::Vec3f> mLocalJointPositions;
|
||||
std::vector<osg::Vec3f> mJointPositions;
|
||||
std::vector<float> mBoneLengths;
|
||||
float mJointChainLength;
|
||||
float mDistanceToTarget;
|
||||
|
||||
void initialize();
|
||||
void poleConstraint();
|
||||
void backward();
|
||||
void forward();
|
||||
};
|
||||
|
||||
#endif // OPENMW_MWRENDER_EASYIK_HPP
|
Loading…
x
Reference in New Issue
Block a user