载入中...
搜索中...
未找到
MotionMatcher.cpp
浏览该文件的文档.
5
6#include "common/Exception.h"
7
8#include <cmath>
9#include <limits>
10
11namespace eve::animation {
12
14 : skeleton_(skeleton), database_(database) {
15 if (!skeleton_) throw Exception("MotionMatcher: skeleton is null");
16 if (!database_) throw Exception("MotionMatcher: database is null");
17 if (database_->getSkeleton() != skeleton_) {
18 throw Exception("MotionMatcher: database skeleton mismatch");
19 }
20 pose_.resize(skeleton_->getBoneCount());
21 fromPose_.resize(skeleton_->getBoneCount());
22 matchedPose_.resize(skeleton_->getBoneCount());
23 skeleton_->applyBindPose(&pose_);
24}
25
27 desiredVelX_ = x;
28 desiredVelZ_ = z;
29}
30
31void MotionMatcher::setDesiredYaw(float yaw) { desiredYaw_ = yaw; }
32
34 if (seconds < 0.f) throw Exception("MotionMatcher.setSearchInterval: must be >= 0");
35 searchInterval_ = seconds;
36}
37
38void MotionMatcher::setBlendTime(float seconds) {
39 if (seconds < 0.f) throw Exception("MotionMatcher.setBlendTime: must be >= 0");
40 blendTime_ = seconds;
41}
42
44 if (w < 0.f) throw Exception("MotionMatcher.setTrajectoryWeight: must be >= 0");
45 trajWeight_ = w;
46}
47
49 if (w < 0.f) throw Exception("MotionMatcher.setPoseWeight: must be >= 0");
50 poseWeight_ = w;
51}
52
54 if (w < 0.f) throw Exception("MotionMatcher.setVelocityWeight: must be >= 0");
55 velWeight_ = w;
56}
57
59 if (frames < 0) throw Exception("MotionMatcher.setIgnoreRadius: must be >= 0");
60 ignoreRadius_ = frames;
61}
62
64 if (matchedFrame_ < 0 || !database_->isBaked()) return -1;
65 return database_->getFrameClipIndex(matchedFrame_);
66}
67
68AnimPose *MotionMatcher::getPose() { return &pose_; }
69
70void MotionMatcher::buildQuery(std::vector<float> &query) const {
71 if (!database_->isBaked()) throw Exception("MotionMatcher: database not baked");
72 const int n = database_->getFeatureSize();
73 query.assign(static_cast<size_t>(n), 0.f);
74
75 // Character-space desired velocity (assume current facing = desiredYaw for query).
76 const float cs = std::cos(desiredYaw_);
77 const float sn = std::sin(desiredYaw_);
78 query[0] = desiredVelX_ * cs + desiredVelZ_ * sn;
79 query[1] = -desiredVelX_ * sn + desiredVelZ_ * cs;
80
81 // Desired trajectory: integrate constant velocity for 0.33/0.66/1.0s in char space.
82 const float horizons[3] = {0.33f, 0.66f, 1.0f};
83 for (int h = 0; h < 3; ++h) {
84 query[static_cast<size_t>(2 + h * 2)] = query[0] * horizons[h];
85 query[static_cast<size_t>(2 + h * 2 + 1)] = query[1] * horizons[h];
86 }
87 // Facing at +1s: same as desired (relative facing 0 → forward +Z in char space).
88 float fx, fz;
89 yawToForward(0.f, fx, fz);
90 query[8] = fx;
91 query[9] = fz;
92
93 // Pose features from current pose (character-relative).
94 AnimPose cur;
95 cur.copyFrom(&pose_);
96 cur.computeWorld(skeleton_);
97 const int root = database_->getRootBone();
98 const float rootX = cur.getWorldPositionX(root);
99 const float rootZ = cur.getWorldPositionZ(root);
100 // Estimate current yaw from root rotation.
101 const float qy = cur.getWorldRotationY(root);
102 const float qw = cur.getWorldRotationW(root);
103 const float yaw =
104 std::atan2(2.f * (qw * qy), 1.f - 2.f * (qy * qy));
105 const float c2 = std::cos(yaw);
106 const float s2 = std::sin(yaw);
107
108 int base = 10;
109 for (int i = 0; i < database_->getFeatureBoneCount(); ++i) {
110 const int b = database_->getFeatureBone(i);
111 const float dx = cur.getWorldPositionX(b) - rootX;
112 const float dz = cur.getWorldPositionZ(b) - rootZ;
113 const float lx = dx * c2 + dz * s2;
114 const float lz = -dx * s2 + dz * c2;
115 query[static_cast<size_t>(base)] = lx;
116 query[static_cast<size_t>(base + 1)] = cur.getWorldPositionY(b);
117 query[static_cast<size_t>(base + 2)] = lz;
118 base += 3;
119 }
120}
121
122float MotionMatcher::cost(const std::vector<float> &query,
123 const std::vector<float> &cand) const {
124 const int n = static_cast<int>(query.size());
125 float sum = 0.f;
126 for (int i = 0; i < n; ++i) {
127 const float d = query[static_cast<size_t>(i)] - cand[static_cast<size_t>(i)];
128 float w = poseWeight_;
129 if (i < 2) w = velWeight_;
130 else if (i < 10) w = trajWeight_;
131 sum += w * d * d;
132 }
133 return sum;
134}
135
136void MotionMatcher::sampleMatched(AnimPose *out) const {
137 if (matchedFrame_ < 0) {
138 skeleton_->applyBindPose(out);
139 return;
140 }
141 const int ci = database_->getFrameClipIndex(matchedFrame_);
142 AnimClip *clip = database_->getClip(ci);
143 clip->sample(matchedTime_, out, skeleton_);
144}
145
147 if (!database_->isBaked()) throw Exception("MotionMatcher.search: database not baked");
148 if (database_->getFrameCount() == 0) {
149 throw Exception("MotionMatcher.search: empty database");
150 }
151
152 std::vector<float> query;
153 buildQuery(query);
154
155 float bestCost = std::numeric_limits<float>::infinity();
156 int best = 0;
157 for (int i = 0; i < database_->getFrameCount(); ++i) {
158 if (matchedFrame_ >= 0 && std::abs(i - matchedFrame_) <= ignoreRadius_) {
159 // Still allow if different clip continues — but prefer diversity slightly.
160 // Skip only exact-neighborhood of current match when already playing.
161 if (playing_ && database_->getFrameClipIndex(i) == getMatchedClipIndex()) {
162 continue;
163 }
164 }
165 const auto &f = database_->frameAt(i);
166 const float c = cost(query, f.feature);
167 if (c < bestCost) {
168 bestCost = c;
169 best = i;
170 }
171 }
172
173 // If everything skipped, fall back to exhaustive.
174 if (!std::isfinite(bestCost)) {
175 for (int i = 0; i < database_->getFrameCount(); ++i) {
176 const auto &f = database_->frameAt(i);
177 const float c = cost(query, f.feature);
178 if (c < bestCost) {
179 bestCost = c;
180 best = i;
181 }
182 }
183 }
184
185 lastCost_ = bestCost;
186 if (best != matchedFrame_) {
187 fromPose_.copyFrom(&pose_);
188 matchedFrame_ = best;
189 matchedTime_ = database_->getFrameTime(best);
190 sampleMatched(&matchedPose_);
191 if (blendTime_ > 1e-6f && playing_) {
192 blending_ = true;
193 blendElapsed_ = 0.f;
194 } else {
195 pose_.copyFrom(&matchedPose_);
196 blending_ = false;
197 }
198 playing_ = true;
199 }
200}
201
202void MotionMatcher::update(float dt) {
203 if (dt < 0.f) throw Exception("MotionMatcher.update: dt must be >= 0");
204 if (!database_->isBaked()) return;
205
206 if (!playing_) {
207 search();
208 searchTimer_ = 0.f;
209 } else {
210 searchTimer_ += dt;
211 if (searchTimer_ >= searchInterval_) {
212 searchTimer_ = 0.f;
213 search();
214 }
215 }
216
217 if (matchedFrame_ >= 0) {
218 matchedTime_ += dt;
219 sampleMatched(&matchedPose_);
220 }
221
222 if (blending_) {
223 blendElapsed_ += dt;
224 float t = blendTime_ > 1e-8f ? blendElapsed_ / blendTime_ : 1.f;
225 if (t >= 1.f) {
226 blending_ = false;
227 pose_.copyFrom(&matchedPose_);
228 } else {
229 pose_.blendFrom(&fromPose_, &matchedPose_, t);
230 }
231 } else if (matchedFrame_ >= 0) {
232 pose_.copyFrom(&matchedPose_);
233 }
234}
235
236} // namespace eve::animation
int z
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
uint32_t b
uint32_t c
std::vector< float > cost
float f
int d
void sample(float time, AnimPose *out, const AnimSkeleton *skeleton=nullptr) const
Sample local pose at time (seconds). If skeleton non-null, missing tracks fall back to bind pose; oth...
Definition AnimClip.cpp:276
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:15
void blendFrom(const AnimPose *a, const AnimPose *b, float t)
Definition AnimPose.cpp:62
void copyFrom(const AnimPose *other)
Definition AnimPose.cpp:56
void resize(int boneCount)
Definition AnimPose.cpp:44
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
void applyBindPose(class AnimPose *pose) const
Fill pose locals with bind pose.
Baked motion-matching feature database from one or more AnimClips. Feature layout per frame: [0....
AnimSkeleton * getSkeleton() const
float getFrameTime(int frameIndex) const
AnimClip * getClip(int clipIndex) const
const Frame & frameAt(int index) const
int getFeatureBone(int index) const
int getFrameClipIndex(int frameIndex) const
void search()
Force an immediate search (also called periodically from update).
void setBlendTime(float seconds)
MotionMatcher(AnimSkeleton *skeleton, MotionDatabase *database)
void setDesiredYaw(float yaw)
Desired facing yaw (radians, Y-up).
void setDesiredVelocity(float x, float z)
Desired planar velocity in character/world XZ (units/sec).
void setIgnoreRadius(int frames)
Skip rematching the same frame / nearby frames to reduce jitter.
void setSearchInterval(float seconds)
void yawToForward(float yaw, float &fx, float &fz)
Rotate unit +Z by yaw (radians) around Y — used for planar facing.
Definition AnimMath.h:86
I * query()
Definition Capability.h:77