载入中...
搜索中...
未找到
FootIKSolver.cpp
浏览该文件的文档.
4#include "common/Exception.h"
5#include <algorithm>
6#include <cmath>
7
8namespace eve::animation {
9namespace {
10float clamp01(float v) { return std::clamp(v,0.f,1.f); }
11float blendRate(float response,float dt) {
12 return response<=0.f?1.f:1.f-std::exp(-response*std::max(dt,0.f));
13}
14}
15FootIKSolver::FootIKSolver(AnimSkeleton* skeleton):skeleton_(skeleton) {
16 if(!skeleton_) throw Exception("FootIKSolver: skeleton is null");
17}
20 if(skeleton_==skeleton) return;
21 skeleton_=skeleton; pelvisBone_=-1; left_={}; right_={};
22}
24 if(!skeleton_||bone<0||bone>=skeleton_->getBoneCount())
25 throw Exception("FootIKSolver.setPelvisBone: invalid bone");
26 pelvisBone_=bone;
27}
28void FootIKSolver::configure(Leg& leg,int hip,int knee,int foot,float soleOffset) {
29 if(!skeleton_||hip<0||knee<0||foot<0||hip>=skeleton_->getBoneCount()||
30 knee>=skeleton_->getBoneCount()||foot>=skeleton_->getBoneCount()||
31 skeleton_->getParent(knee)!=hip||skeleton_->getParent(foot)!=knee)
32 throw Exception("FootIKSolver.configure: invalid leg hierarchy");
33 leg={}; leg.hip=hip; leg.knee=knee; leg.foot=foot;
34 leg.soleOffset=soleOffset; leg.configured=true;
35}
36void FootIKSolver::configureLeftLeg(int h,int k,int f,float o){configure(left_,h,k,f,o);}
37void FootIKSolver::configureRightLeg(int h,int k,int f,float o){configure(right_,h,k,f,o);}
38void FootIKSolver::configureToe(Leg& leg,int toe,float soleOffset) {
39 if(!leg.configured||!skeleton_||toe<0||toe>=skeleton_->getBoneCount()||skeleton_->getParent(toe)!=leg.foot)
40 throw Exception("FootIKSolver.configureToe: invalid toe hierarchy");
41 leg.toe=toe; leg.toeSoleOffset=soleOffset; leg.toeConfigured=true; leg.toeInitialized=false;
42}
43void FootIKSolver::configureLeftToe(int toe,float offset){configureToe(left_,toe,offset);}
44void FootIKSolver::configureRightToe(int toe,float offset){configureToe(right_,toe,offset);}
45void FootIKSolver::setGroundQuery(float startHeight,float distance){groundStartHeight_=std::max(startHeight,0.f);groundQueryDistance_=std::max(distance,0.f);}
46void FootIKSolver::contact(Leg& leg,bool hit,float x,float y,float z,float nx,float ny,float nz,float weight) {
47 const float n=std::sqrt(nx*nx+ny*ny+nz*nz);
48 if(hit&&n<1e-6f) throw Exception("FootIKSolver.contact: ground normal is zero");
49 leg.target.x=x; leg.target.y=y; leg.target.z=z;
50 leg.target.weight=hit&&ny/n>=minGroundNormalY_?clamp01(weight):0.f;
51 if(n>=1e-6f){leg.target.nx=nx/n;leg.target.ny=ny/n;leg.target.nz=nz/n;}
52}
53void FootIKSolver::setLeftContact(bool h,float x,float y,float z,float nx,float ny,float nz,float w){
54 contact(left_,h,x,y,z,nx,ny,nz,w);
55}
56void FootIKSolver::setRightContact(bool h,float x,float y,float z,float nx,float ny,float nz,float w){
57 contact(right_,h,x,y,z,nx,ny,nz,w);
58}
59void FootIKSolver::setMaxPelvisOffset(float v){maxPelvisOffset_=std::max(v,0.f);}
60void FootIKSolver::setMinGroundNormalY(float v){minGroundNormalY_=clamp01(v);}
61void FootIKSolver::setPositionResponse(float v){positionResponse_=std::max(v,0.f);}
62void FootIKSolver::setRotationResponse(float v){rotationResponse_=std::max(v,0.f);}
63void FootIKSolver::setContactGraceTime(float v){contactGraceTime_=std::max(v,0.f);}
64void FootIKSolver::setFootLockEnabled(bool enabled){footLockEnabled_=enabled;if(!enabled){left_.locked=false;right_.locked=false;}}
65void FootIKSolver::setFootLockThresholds(float enter,float exit){lockEnterWeight_=clamp01(enter);lockExitWeight_=std::min(clamp01(exit),lockEnterWeight_);}
66void FootIKSolver::reset(){left_.initialized=right_.initialized=false;left_.toeInitialized=right_.toeInitialized=false;left_.locked=right_.locked=false;left_.missingTime=right_.missingTime=0.f;}
67void FootIKSolver::updateLock(Leg& leg) {
68 if(!footLockEnabled_){leg.locked=false;return;}
69 if(leg.locked&&leg.target.weight<=lockExitWeight_)leg.locked=false;
70 if(!leg.locked&&leg.target.weight>=lockEnterWeight_){leg.locked=true;leg.lockedContact=leg.target;}
71 if(leg.locked)leg.target=leg.lockedContact;
72}
73void FootIKSolver::queryGround(Leg& leg,const AnimPose& pose,float dt) {
74 if(!groundProvider_||!leg.configured)return;
75 const auto& foot=pose.world(leg.foot);float x=0.f,y=0.f,z=0.f,nx=0.f,ny=1.f,nz=0.f;
76 const auto status=groundProvider_->queryGround(foot.px,foot.py+groundStartHeight_,foot.pz,groundStartHeight_+groundQueryDistance_,x,y,z,nx,ny,nz);
77 if(status==FootIKGroundQueryStatus::Hit){leg.missingTime=0.f;contact(leg,true,x,y,z,nx,ny,nz,1.f);}
78 else if(status==FootIKGroundQueryStatus::NoHit){leg.missingTime+=std::max(dt,0.f);if(leg.missingTime>contactGraceTime_)contact(leg,false,0.f,0.f,0.f,0.f,1.f,0.f,0.f);}
79 if(leg.toeConfigured&&status==FootIKGroundQueryStatus::Hit){
80 const auto& toe=pose.world(leg.toe);
81 const auto toeStatus=groundProvider_->queryGround(toe.px,toe.py+groundStartHeight_,toe.pz,groundStartHeight_+groundQueryDistance_,x,y,z,nx,ny,nz);
82 if(toeStatus==FootIKGroundQueryStatus::Hit){const float n=std::sqrt(nx*nx+ny*ny+nz*nz);if(n>1e-6f){leg.toeTarget={x,y,z,nx/n,ny/n,nz/n,ny/n>=minGroundNormalY_?1.f:0.f};}}
83 else leg.toeTarget.weight=0.f;
84 }
85 updateLock(leg);
86}
87void FootIKSolver::interpolate(Leg& leg,float dt) {
88 if(!leg.configured)return;
89 if(!leg.initialized){leg.smooth=leg.target;leg.initialized=true;return;}
90 const float p=blendRate(positionResponse_,dt),r=blendRate(rotationResponse_,dt);
91 leg.smooth.x+=(leg.target.x-leg.smooth.x)*p; leg.smooth.y+=(leg.target.y-leg.smooth.y)*p;
92 leg.smooth.z+=(leg.target.z-leg.smooth.z)*p; leg.smooth.weight+=(leg.target.weight-leg.smooth.weight)*p;
93 leg.smooth.nx+=(leg.target.nx-leg.smooth.nx)*r; leg.smooth.ny+=(leg.target.ny-leg.smooth.ny)*r;
94 leg.smooth.nz+=(leg.target.nz-leg.smooth.nz)*r;
95 const float n=std::sqrt(leg.smooth.nx*leg.smooth.nx+leg.smooth.ny*leg.smooth.ny+leg.smooth.nz*leg.smooth.nz);
96 if(n>1e-6f){leg.smooth.nx/=n;leg.smooth.ny/=n;leg.smooth.nz/=n;}
97}
98float FootIKSolver::pelvisOffset(const Leg& leg,const AnimPose& pose) const {
99 if(!leg.configured||leg.smooth.weight<=0.f)return 0.f;
100 return std::min(0.f,(leg.smooth.y+leg.soleOffset-pose.world(leg.foot).py)*leg.smooth.weight);
101}
102void FootIKSolver::solve(const Leg& leg,AnimPose& pose,float rotationWeight) const {
103 if(!leg.configured||leg.smooth.weight<=1e-4f)return;
104 pose.solveTwoBoneIK(skeleton_,leg.hip,leg.knee,leg.foot,leg.smooth.x,
105 leg.smooth.y+leg.soleOffset,leg.smooth.z,leg.smooth.weight);
106 pose.computeWorld(skeleton_);
107 const auto& foot=pose.world(leg.foot);
108 pose.aimBone(skeleton_,leg.foot,foot.px+leg.smooth.nx,foot.py+leg.smooth.ny,
109 foot.pz+leg.smooth.nz,rotationWeight*leg.smooth.weight);
110 if(leg.toeConfigured&&leg.toeSmooth.weight>1e-4f){pose.computeWorld(skeleton_);pose.aimBone(skeleton_,leg.toe,leg.toeSmooth.x,leg.toeSmooth.y+leg.toeSoleOffset,leg.toeSmooth.z,rotationWeight*leg.toeSmooth.weight);}
111}
113 if(!pose||!skeleton_)throw Exception("FootIKSolver.apply: pose or skeleton is null");
114 if(!std::isfinite(dt)||dt<0.f)throw Exception("FootIKSolver.apply: delta time is invalid");
115 if(pose->getBoneCount()!=skeleton_->getBoneCount())throw Exception("FootIKSolver.apply: bone count mismatch");
116 pose->computeWorld(skeleton_); queryGround(left_,*pose,dt); queryGround(right_,*pose,dt);
117 interpolate(left_,dt); interpolate(right_,dt);
118 auto interpolateToe=[&](Leg& leg){if(!leg.toeConfigured)return;if(!leg.toeInitialized){leg.toeSmooth=leg.toeTarget;leg.toeInitialized=true;return;}const float p=blendRate(positionResponse_,dt);leg.toeSmooth.x+=(leg.toeTarget.x-leg.toeSmooth.x)*p;leg.toeSmooth.y+=(leg.toeTarget.y-leg.toeSmooth.y)*p;leg.toeSmooth.z+=(leg.toeTarget.z-leg.toeSmooth.z)*p;leg.toeSmooth.weight+=(leg.toeTarget.weight-leg.toeSmooth.weight)*p;};
119 interpolateToe(left_);interpolateToe(right_);
120 if(pelvisBone_>=0){
121 const float offset=std::clamp(std::min(pelvisOffset(left_,*pose),pelvisOffset(right_,*pose)),
122 -maxPelvisOffset_,maxPelvisOffset_);
123 pose->local(pelvisBone_).py+=offset; pose->computeWorld(skeleton_);
124 }
125 const float rotationWeight=blendRate(rotationResponse_,dt);
126 solve(left_,*pose,rotationWeight); solve(right_,*pose,rotationWeight);
127 pose->computeWorld(skeleton_);
128}
130 auto capture=[](const Leg& leg){FootIKDebugFoot out;out.configured=leg.configured;out.contact=leg.smooth.weight>1e-4f;out.locked=leg.locked;out.toeConfigured=leg.toeConfigured;out.x=leg.smooth.x;out.y=leg.smooth.y;out.z=leg.smooth.z;out.nx=leg.smooth.nx;out.ny=leg.smooth.ny;out.nz=leg.smooth.nz;out.toeX=leg.toeSmooth.x;out.toeY=leg.toeSmooth.y;out.toeZ=leg.toeSmooth.z;return out;};
131 return {capture(left_),capture(right_)};
132}
133} // namespace eve::animation
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
eve::EntitySpatialPose pose
float nx
float nz
float ny
glm::vec4 p[6]
wgpu::PopErrorScopeStatus status
glm::vec3 n
Definition Grass.cpp:63
double r
float v
int h
size_t offset
std::uint32_t bone
float distance
float f
bool hit
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int getBoneCount() const
Returns the bone count.
int getParent(int boneIndex) const
Returns the parent.
virtual FootIKGroundQueryStatus queryGround(float originX, float originY, float originZ, float maxDistance, float &hitX, float &hitY, float &hitZ, float &normalX, float &normalY, float &normalZ) const =0
Query ground below an origin and return model-space contact data.
void setMinGroundNormalY(float value)
Set minimum accepted upward normal cosine in [0, 1].
FootIKDebugSnapshot debugSnapshot() const
Copy smoothed contacts and lock state for tooling visualization.
void reset()
Clear interpolated contacts.
void setMaxPelvisOffset(float distance)
Set maximum pelvis compensation distance.
void setGroundQuery(float startHeight, float distance)
Set ray origin height and downward query distance.
FootIKSolver(AnimSkeleton *skeleton)
Construct for a non-null borrowed skeleton.
void configureLeftToe(int toe, float soleOffset=0.f)
Configure an optional left toe child and its sole offset.
void configureRightLeg(int hip, int knee, int foot, float soleOffset=0.f)
Configure the right hip-knee-foot chain and sole offset.
void setFootLockEnabled(bool enabled)
Enable or disable world-space foot locking.
void setPelvisBone(int bone)
Configure the pelvis bone.
~FootIKSolver()
Foot ik solver.
void setRightContact(bool hit, float x, float y, float z, float nx, float ny, float nz, float weight=1.f)
Supply the right model-space ground contact.
void setPositionResponse(float value)
Set contact interpolation response in inverse seconds.
void setRotationResponse(float value)
Set foot-normal interpolation response in inverse seconds.
void setContactGraceTime(float seconds)
Set how long a missing automatic contact retains its prior target.
void apply(AnimPose *pose, float dt)
Apply using injected delta time and owning contact values.
void setFootLockThresholds(float enterWeight, float exitWeight)
Set lock enter and release contact-weight thresholds.
void configureLeftLeg(int hip, int knee, int foot, float soleOffset=0.f)
Configure the left hip-knee-foot chain and sole offset.
void configureRightToe(int toe, float soleOffset=0.f)
Configure an optional right toe child and its sole offset.
void setLeftContact(bool hit, float x, float y, float z, float nx, float ny, float nz, float weight=1.f)
Supply the left model-space ground contact.
void setSkeleton(AnimSkeleton *skeleton)
Rebind; changing skeleton clears all bone-index configuration.
bool enabled
Immutable model-space state for one foot in a tooling snapshot.
Owning renderer-neutral paired-foot tooling snapshot.